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