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-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.replication.common; 028 029import static org.opends.messages.ReplicationMessages.*; 030 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.Iterator; 034import java.util.List; 035import java.util.Map; 036import java.util.Map.Entry; 037import java.util.TreeMap; 038import java.util.concurrent.ConcurrentMap; 039import java.util.concurrent.ConcurrentSkipListMap; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.opendj.ldap.ResultCode; 043import org.forgerock.util.Pair; 044import org.opends.server.types.DN; 045import org.opends.server.types.DirectoryException; 046 047/** 048 * This object is used to store a list of ServerState object, one by replication 049 * domain. Globally, it is the generalization of ServerState (that applies to 050 * one domain) to a list of domains. 051 * <p> 052 * MultiDomainServerState is also known as "cookie" and is used with the 053 * cookie-based changelog. 054 */ 055public class MultiDomainServerState implements Iterable<DN> 056{ 057 /** The list of (domain service id, ServerState). */ 058 private final ConcurrentMap<DN, ServerState> list; 059 060 /** Creates a new empty object. */ 061 public MultiDomainServerState() 062 { 063 list = new ConcurrentSkipListMap<>(); 064 } 065 066 /** 067 * Copy constructor. 068 * 069 * @param cookie 070 * the cookie to copy 071 */ 072 public MultiDomainServerState(MultiDomainServerState cookie) 073 { 074 list = new ConcurrentSkipListMap<>(); 075 076 for (Map.Entry<DN, ServerState> mapEntry : cookie.list.entrySet()) 077 { 078 DN dn = mapEntry.getKey(); 079 ServerState state = mapEntry.getValue(); 080 list.put(dn, state.duplicate()); 081 } 082 } 083 084 /** 085 * Create an object from a string representation. 086 * @param cookie The provided string representation of the state. 087 * @throws DirectoryException when the string has an invalid format 088 */ 089 public MultiDomainServerState(String cookie) throws DirectoryException 090 { 091 list = new ConcurrentSkipListMap<>(splitGenStateToServerStates(cookie)); 092 } 093 094 /** 095 * Empty the object.. 096 * After this call the object will be in the same state as if it 097 * was just created. 098 */ 099 public void clear() 100 { 101 list.clear(); 102 } 103 104 /** 105 * Update the ServerState of the provided baseDN with the replication 106 * {@link CSN} provided. 107 * 108 * @param baseDN The provided baseDN. 109 * @param csn The provided CSN. 110 * 111 * @return a boolean indicating if the update was meaningful. 112 */ 113 public boolean update(DN baseDN, CSN csn) 114 { 115 if (csn == null) 116 { 117 return false; 118 } 119 120 ServerState serverState = list.get(baseDN); 121 if (serverState == null) 122 { 123 serverState = new ServerState(); 124 final ServerState existingSS = list.putIfAbsent(baseDN, serverState); 125 if (existingSS != null) 126 { 127 serverState = existingSS; 128 } 129 } 130 return serverState.update(csn); 131 } 132 133 /** 134 * Update the ServerState of the provided baseDN with the provided server 135 * state. 136 * 137 * @param baseDN 138 * The provided baseDN. 139 * @param serverState 140 * The provided serverState. 141 */ 142 public void update(DN baseDN, ServerState serverState) 143 { 144 for (CSN csn : serverState) 145 { 146 update(baseDN, csn); 147 } 148 } 149 150 /** 151 * Replace the ServerState of the provided baseDN with the provided server 152 * state. The provided server state will be owned by this instance, so care 153 * must be taken by calling code to duplicate it if needed. 154 * 155 * @param baseDN 156 * The provided baseDN. 157 * @param serverState 158 * The provided serverState. 159 */ 160 public void replace(DN baseDN, ServerState serverState) 161 { 162 if (serverState == null) 163 { 164 throw new IllegalArgumentException("ServerState must not be null"); 165 } 166 list.put(baseDN, serverState); 167 } 168 169 /** 170 * Update the current object with the provided multi domain server state. 171 * 172 * @param state 173 * The provided multi domain server state. 174 */ 175 public void update(MultiDomainServerState state) 176 { 177 for (Entry<DN, ServerState> entry : state.list.entrySet()) 178 { 179 update(entry.getKey(), entry.getValue()); 180 } 181 } 182 183 /** 184 * Returns a snapshot of this object. 185 * 186 * @return an unmodifiable Map representing a snapshot of this object. 187 */ 188 public Map<DN, List<CSN>> getSnapshot() 189 { 190 if (list.isEmpty()) 191 { 192 return Collections.emptyMap(); 193 } 194 final Map<DN, List<CSN>> map = new HashMap<>(); 195 for (Entry<DN, ServerState> entry : list.entrySet()) 196 { 197 final List<CSN> l = entry.getValue().getSnapshot(); 198 if (!l.isEmpty()) 199 { 200 map.put(entry.getKey(), l); 201 } 202 } 203 return Collections.unmodifiableMap(map); 204 } 205 206 /** 207 * Returns a string representation of this object. 208 * 209 * @return The string representation. 210 */ 211 @Override 212 public String toString() 213 { 214 final StringBuilder buffer = new StringBuilder(); 215 toString(buffer); 216 return buffer.toString(); 217 } 218 219 /** 220 * Dump a string representation in the provided buffer. 221 * @param buffer The provided buffer. 222 */ 223 public void toString(StringBuilder buffer) 224 { 225 if (list != null && !list.isEmpty()) 226 { 227 for (Entry<DN, ServerState> entry : list.entrySet()) 228 { 229 entry.getKey().toString(buffer); 230 buffer.append(":"); 231 entry.getValue().toString(buffer); 232 buffer.append(";"); 233 } 234 } 235 } 236 237 /** 238 * Tests if the state is empty. 239 * 240 * @return True if the state is empty. 241 */ 242 public boolean isEmpty() 243 { 244 return list.isEmpty(); 245 } 246 247 /** {@inheritDoc} */ 248 @Override 249 public Iterator<DN> iterator() 250 { 251 return list.keySet().iterator(); 252 } 253 254 /** 255 * Returns the ServerState associated to the provided replication domain's 256 * baseDN. 257 * 258 * @param baseDN 259 * the replication domain's baseDN 260 * @return the associated ServerState 261 */ 262 public ServerState getServerState(DN baseDN) 263 { 264 return list.get(baseDN); 265 } 266 267 /** 268 * Returns the CSN associated to the provided replication domain's baseDN and 269 * serverId. 270 * 271 * @param baseDN 272 * the replication domain's baseDN 273 * @param serverId 274 * the serverId 275 * @return the associated CSN 276 */ 277 public CSN getCSN(DN baseDN, int serverId) 278 { 279 final ServerState ss = list.get(baseDN); 280 if (ss != null) 281 { 282 return ss.getCSN(serverId); 283 } 284 return null; 285 } 286 287 /** 288 * Returns the oldest Pair<DN, CSN> held in current object, excluding 289 * the provided CSNs. Said otherwise, the value returned is the oldest 290 * Pair<DN, CSN> included in the current object, that is not part of the 291 * excludedCSNs. 292 * 293 * @param excludedCSNs 294 * the CSNs that cannot be returned 295 * @return the oldest Pair<DN, CSN> included in the current object that 296 * is not part of the excludedCSNs, or {@link Pair#EMPTY} if no such 297 * older CSN exists. 298 */ 299 public Pair<DN, CSN> getOldestCSNExcluding(MultiDomainServerState excludedCSNs) 300 { 301 Pair<DN, CSN> oldest = Pair.empty(); 302 for (Entry<DN, ServerState> entry : list.entrySet()) 303 { 304 final DN baseDN = entry.getKey(); 305 final ServerState value = entry.getValue(); 306 for (Entry<Integer, CSN> entry2 : value.getServerIdToCSNMap().entrySet()) 307 { 308 final CSN csn = entry2.getValue(); 309 if (!isReplicaExcluded(excludedCSNs, baseDN, csn) 310 && (oldest == Pair.EMPTY || csn.isOlderThan(oldest.getSecond()))) 311 { 312 oldest = Pair.of(baseDN, csn); 313 } 314 } 315 } 316 return oldest; 317 } 318 319 private boolean isReplicaExcluded(MultiDomainServerState excluded, DN baseDN, 320 CSN csn) 321 { 322 return excluded != null 323 && csn.equals(excluded.getCSN(baseDN, csn.getServerId())); 324 } 325 326 /** 327 * Removes the mapping to the provided CSN if it is present in this 328 * MultiDomainServerState. 329 * 330 * @param baseDN 331 * the replication domain's baseDN 332 * @param expectedCSN 333 * the CSN to be removed 334 * @return true if the CSN could be removed, false otherwise. 335 */ 336 public boolean removeCSN(DN baseDN, CSN expectedCSN) 337 { 338 final ServerState ss = list.get(baseDN); 339 return ss != null && ss.removeCSN(expectedCSN); 340 } 341 342 /** 343 * Test if this object equals the provided other object. 344 * @param other The other object with which we want to test equality. 345 * @return Returns True if this equals other, else return false. 346 */ 347 public boolean equalsTo(MultiDomainServerState other) 348 { 349 return cover(other) && other.cover(this); 350 } 351 352 /** 353 * Test if this object covers the provided covered object. 354 * @param covered The provided object. 355 * @return true when this covers the provided object. 356 */ 357 public boolean cover(MultiDomainServerState covered) 358 { 359 for (DN baseDN : covered.list.keySet()) 360 { 361 ServerState state = list.get(baseDN); 362 ServerState coveredState = covered.list.get(baseDN); 363 if (state == null || coveredState == null || !state.cover(coveredState)) 364 { 365 return false; 366 } 367 } 368 return true; 369 } 370 371 /** 372 * Test if this object covers the provided CSN for the provided baseDN. 373 * 374 * @param baseDN 375 * The provided baseDN. 376 * @param csn 377 * The provided CSN. 378 * @return true when this object covers the provided CSN for the provided 379 * baseDN. 380 */ 381 public boolean cover(DN baseDN, CSN csn) 382 { 383 final ServerState state = list.get(baseDN); 384 return state != null && state.cover(csn); 385 } 386 387 /** 388 * Splits the provided generalizedServerState being a String with the 389 * following syntax: "domain1:state1;domain2:state2;..." to a Map of (domain 390 * DN, domain ServerState). 391 * 392 * @param multiDomainServerState 393 * the provided multi domain server state also known as cookie 394 * @exception DirectoryException 395 * when an error occurs 396 * @return the split state. 397 */ 398 private static Map<DN, ServerState> splitGenStateToServerStates( 399 String multiDomainServerState) throws DirectoryException 400 { 401 Map<DN, ServerState> startStates = new TreeMap<>(); 402 if (multiDomainServerState != null && multiDomainServerState.length() > 0) 403 { 404 try 405 { 406 // Split the provided multiDomainServerState into domains 407 String[] domains = multiDomainServerState.split(";"); 408 for (String domain : domains) 409 { 410 // For each domain, split the CSNs by server 411 // and build a server state (SHOULD BE OPTIMIZED) 412 final ServerState serverStateByDomain = new ServerState(); 413 414 final String[] fields = domain.split(":"); 415 if (fields.length == 0) 416 { 417 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 418 ERR_INVALID_COOKIE_SYNTAX.get(multiDomainServerState)); 419 } 420 final String domainBaseDN = fields[0]; 421 if (fields.length > 1) 422 { 423 final String serverStateStr = fields[1]; 424 for (String csnStr : serverStateStr.split(" ")) 425 { 426 final CSN csn = new CSN(csnStr); 427 serverStateByDomain.update(csn); 428 } 429 } 430 startStates.put(DN.valueOf(domainBaseDN), serverStateByDomain); 431 } 432 } 433 catch (DirectoryException de) 434 { 435 throw de; 436 } 437 catch (Exception e) 438 { 439 throw new DirectoryException( 440 ResultCode.PROTOCOL_ERROR, 441 LocalizableMessage.raw("Exception raised: " + e), 442 e); 443 } 444 } 445 return startStates; 446 } 447}