001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.replication.common; 028 029import java.io.IOException; 030import java.util.*; 031import java.util.concurrent.ConcurrentMap; 032import java.util.concurrent.ConcurrentSkipListMap; 033 034import org.forgerock.opendj.io.ASN1Writer; 035import org.opends.server.replication.protocol.ProtocolVersion; 036import org.forgerock.opendj.ldap.ByteString; 037import org.forgerock.util.Utils; 038 039/** 040 * This class is used to associate serverIds with {@link CSN}s. 041 * <p> 042 * For example, it is exchanged with the replication servers at connection 043 * establishment time to communicate "which CSNs was last seen by a serverId". 044 */ 045public class ServerState implements Iterable<CSN> 046{ 047 048 /** Associates a serverId with a CSN. */ 049 private final ConcurrentMap<Integer, CSN> serverIdToCSN = new ConcurrentSkipListMap<>(); 050 /** 051 * Whether the state has been saved to persistent storage. It starts at true, 052 * and moves to false when an update is made to the current object. 053 */ 054 private volatile boolean saved = true; 055 056 /** 057 * Creates a new empty ServerState. 058 */ 059 public ServerState() 060 { 061 super(); 062 } 063 064 /** 065 * Empty the ServerState. 066 * After this call the Server State will be in the same state 067 * as if it was just created. 068 */ 069 public void clear() 070 { 071 serverIdToCSN.clear(); 072 } 073 074 /** 075 * Forward update the Server State with a CSN. The provided CSN will be put on 076 * the current object only if it is newer than the existing CSN for the same 077 * serverId or if there is no existing CSN. 078 * 079 * @param csn 080 * The committed CSN. 081 * @return a boolean indicating if the update was meaningful. 082 */ 083 public boolean update(CSN csn) 084 { 085 if (csn == null) 086 { 087 return false; 088 } 089 090 saved = false; 091 092 final int serverId = csn.getServerId(); 093 while (true) 094 { 095 final CSN existingCSN = serverIdToCSN.get(serverId); 096 if (existingCSN == null) 097 { 098 if (serverIdToCSN.putIfAbsent(serverId, csn) == null) 099 { 100 return true; 101 } 102 // oops, a concurrent modification happened, run the same process again 103 continue; 104 } 105 else if (csn.isNewerThan(existingCSN)) 106 { 107 if (serverIdToCSN.replace(serverId, existingCSN, csn)) 108 { 109 return true; 110 } 111 // oops, a concurrent modification happened, run the same process again 112 continue; 113 } 114 return false; 115 } 116 } 117 118 /** 119 * Update the Server State with a Server State. Every CSN of this object is 120 * updated with the CSN of the passed server state if it is newer. 121 * 122 * @param serverState the server state to use for the update. 123 * @return a boolean indicating if the update was meaningful. 124 */ 125 public boolean update(ServerState serverState) 126 { 127 if (serverState == null) 128 { 129 return false; 130 } 131 132 boolean updated = false; 133 for (CSN csn : serverState.serverIdToCSN.values()) 134 { 135 if (update(csn)) 136 { 137 updated = true; 138 } 139 } 140 return updated; 141 } 142 143 /** 144 * Removes the mapping to the provided CSN if it is present in this 145 * ServerState. 146 * 147 * @param expectedCSN 148 * the CSN to be removed 149 * @return true if the CSN could be removed, false otherwise. 150 */ 151 public boolean removeCSN(CSN expectedCSN) 152 { 153 if (expectedCSN == null) 154 { 155 return false; 156 } 157 158 if (serverIdToCSN.remove(expectedCSN.getServerId(), expectedCSN)) 159 { 160 saved = false; 161 return true; 162 } 163 return false; 164 } 165 166 /** 167 * Replace the Server State with another ServerState. 168 * 169 * @param serverState The ServerState. 170 * 171 * @return a boolean indicating if the update was meaningful. 172 */ 173 public boolean reload(ServerState serverState) { 174 if (serverState == null) { 175 return false; 176 } 177 178 clear(); 179 return update(serverState); 180 } 181 182 /** 183 * Return a Set of String usable as a textual representation of 184 * a Server state. 185 * format : time seqnum id 186 * 187 * example : 188 * 1 00000109e4666da600220001 189 * 2 00000109e44567a600220002 190 * 191 * @return the representation of the Server state 192 */ 193 public Set<String> toStringSet() 194 { 195 final Set<String> result = new HashSet<>(); 196 for (CSN change : serverIdToCSN.values()) 197 { 198 Date date = new Date(change.getTime()); 199 result.add(change + " " + date + " " + change.getTime()); 200 } 201 return result; 202 } 203 204 /** 205 * Return an ArrayList of ANS1OctetString encoding the CSNs 206 * contained in the ServerState. 207 * @return an ArrayList of ANS1OctetString encoding the CSNs 208 * contained in the ServerState. 209 */ 210 public ArrayList<ByteString> toASN1ArrayList() 211 { 212 final ArrayList<ByteString> values = new ArrayList<>(0); 213 for (CSN csn : serverIdToCSN.values()) 214 { 215 values.add(ByteString.valueOf(csn.toString())); 216 } 217 return values; 218 } 219 220 221 222 /** 223 * Encodes this server state to the provided ASN1 writer. 224 * 225 * @param writer 226 * The ASN1 writer. 227 * @param protocolVersion 228 * The replication protocol version. 229 * @throws IOException 230 * If an error occurred during encoding. 231 */ 232 public void writeTo(ASN1Writer writer, short protocolVersion) 233 throws IOException 234 { 235 if (protocolVersion >= ProtocolVersion.REPLICATION_PROTOCOL_V7) 236 { 237 for (CSN csn : serverIdToCSN.values()) 238 { 239 writer.writeOctetString(csn.toByteString()); 240 } 241 } 242 else 243 { 244 for (CSN csn : serverIdToCSN.values()) 245 { 246 writer.writeOctetString(csn.toString()); 247 } 248 } 249 } 250 251 /** 252 * Returns a snapshot of this object. 253 * 254 * @return an unmodifiable List representing a snapshot of this object. 255 */ 256 public List<CSN> getSnapshot() 257 { 258 if (serverIdToCSN.isEmpty()) 259 { 260 return Collections.emptyList(); 261 } 262 return Collections.unmodifiableList(new ArrayList<CSN>(serverIdToCSN.values())); 263 } 264 265 /** 266 * Return the text representation of ServerState. 267 * @return the text representation of ServerState 268 */ 269 @Override 270 public String toString() 271 { 272 return Utils.joinAsString(" ", serverIdToCSN.values()); 273 } 274 275 /** 276 * Returns the {@code CSN} contained in this server state which corresponds to 277 * the provided server ID. 278 * 279 * @param serverId 280 * The server ID. 281 * @return The {@code CSN} contained in this server state which 282 * corresponds to the provided server ID. 283 */ 284 public CSN getCSN(int serverId) 285 { 286 return serverIdToCSN.get(serverId); 287 } 288 289 /** 290 * Returns a copy of this ServerState's content as a Map of serverId => CSN. 291 * 292 * @return a copy of this ServerState's content as a Map of serverId => CSN. 293 */ 294 public Map<Integer, CSN> getServerIdToCSNMap() 295 { 296 // copy to protect from concurrent updates 297 // that could change the number of elements in the Map 298 return new HashMap<>(serverIdToCSN); 299 } 300 301 /** {@inheritDoc} */ 302 @Override 303 public Iterator<CSN> iterator() 304 { 305 return serverIdToCSN.values().iterator(); 306 } 307 308 /** 309 * Check that all the CSNs in the covered serverState are also in this 310 * serverState. 311 * 312 * @param covered The ServerState that needs to be checked. 313 * @return A boolean indicating if this ServerState covers the ServerState 314 * given in parameter. 315 */ 316 public boolean cover(ServerState covered) 317 { 318 for (CSN coveredChange : covered.serverIdToCSN.values()) 319 { 320 if (!cover(coveredChange)) 321 { 322 return false; 323 } 324 } 325 return true; 326 } 327 328 /** 329 * Checks that the CSN given as a parameter is in this ServerState. 330 * 331 * @param covered The CSN that should be checked. 332 * @return A boolean indicating if this ServerState contains the CSN given in 333 * parameter. 334 */ 335 public boolean cover(CSN covered) 336 { 337 final CSN csn = this.serverIdToCSN.get(covered.getServerId()); 338 return csn != null && !csn.isOlderThan(covered); 339 } 340 341 /** 342 * Tests if the state is empty. 343 * 344 * @return True if the state is empty. 345 */ 346 public boolean isEmpty() 347 { 348 return serverIdToCSN.isEmpty(); 349 } 350 351 /** 352 * Make a duplicate of this state. 353 * @return The duplicate of this state. 354 */ 355 public ServerState duplicate() 356 { 357 final ServerState newState = new ServerState(); 358 newState.serverIdToCSN.putAll(serverIdToCSN); 359 return newState; 360 } 361 362 /** 363 * Computes the number of changes a first server state has in advance 364 * compared to a second server state. 365 * @param ss1 The server state supposed to be newer than the second one 366 * @param ss2 The server state supposed to be older than the first one 367 * @return The difference of changes (sum of the differences for each server 368 * id changes). 0 If no gap between 2 states. 369 * @throws IllegalArgumentException If one of the passed state is null 370 */ 371 public static int diffChanges(ServerState ss1, ServerState ss2) 372 throws IllegalArgumentException 373 { 374 if (ss1 == null || ss2 == null) 375 { 376 throw new IllegalArgumentException("Null server state(s)"); 377 } 378 379 int diff = 0; 380 for (Integer serverId : ss1.serverIdToCSN.keySet()) 381 { 382 CSN csn1 = ss1.serverIdToCSN.get(serverId); 383 if (csn1 != null) 384 { 385 CSN csn2 = ss2.serverIdToCSN.get(serverId); 386 if (csn2 != null) 387 { 388 diff += CSN.diffSeqNum(csn1, csn2); 389 } 390 else 391 { 392 // ss2 does not have a change for this server id but ss1, so the 393 // server holding ss1 has every changes represented in csn1 in advance 394 // compared to server holding ss2, add this amount 395 diff += csn1.getSeqnum(); 396 } 397 } 398 } 399 400 return diff; 401 } 402 403 /** 404 * Set the saved status of this ServerState. 405 * 406 * @param b A boolean indicating if the State has been safely stored. 407 */ 408 public void setSaved(boolean b) 409 { 410 saved = b; 411 } 412 413 /** 414 * Get the saved status of this ServerState. 415 * 416 * @return The saved status of this ServerState. 417 */ 418 public boolean isSaved() 419 { 420 return saved; 421 } 422 423}