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