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