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}