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}