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