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 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.replication.common;
028
029import java.io.Serializable;
030import java.util.Date;
031
032import org.forgerock.opendj.ldap.ByteSequence;
033import org.forgerock.opendj.ldap.ByteSequenceReader;
034import org.forgerock.opendj.ldap.ByteString;
035import org.forgerock.opendj.ldap.ByteStringBuilder;
036
037/**
038 * Class used to represent Change Sequence Numbers.
039 *
040 * @see <a href="http://tools.ietf.org/html/draft-ietf-ldup-infomod-08"
041 * >Inspiration for this class comes from LDAPChangeSequenceNumber</a>
042 */
043public class CSN implements Serializable, Comparable<CSN>
044{
045  /**
046   * The number of bytes used by the byte string representation of a change
047   * number.
048   *
049   * @see #valueOf(ByteSequence)
050   * @see #toByteString()
051   * @see #toByteString(ByteStringBuilder)
052   */
053  public static final int BYTE_ENCODING_LENGTH = 14;
054
055  /**
056   * The number of characters used by the string representation of a change
057   * number.
058   *
059   * @see #valueOf(String)
060   * @see #toString()
061   */
062  public static final int STRING_ENCODING_LENGTH = 28;
063
064  /** The maximum possible value for a CSN. */
065  public static final CSN MAX_CSN_VALUE = new CSN(Long.MAX_VALUE, Integer.MAX_VALUE, Short.MAX_VALUE);
066
067  private static final long serialVersionUID = -8802722277749190740L;
068  private final long timeStamp;
069  /**
070   * The sequence number is set to zero at the start of each millisecond, and
071   * incremented by one for each update operation that occurs within that
072   * millisecond. It allows to distinguish changes that have been done in the
073   * same millisecond.
074   */
075  private final int seqnum;
076  private final int serverId;
077
078  /**
079   * Parses the provided {@link #toString()} representation of a CSN.
080   *
081   * @param s
082   *          The string to be parsed.
083   * @return The parsed CSN.
084   * @see #toString()
085   */
086  public static CSN valueOf(String s)
087  {
088    return new CSN(s);
089  }
090
091  /**
092   * Decodes the provided {@link #toByteString()} representation of a change
093   * number.
094   *
095   * @param bs
096   *          The byte sequence to be parsed.
097   * @return The decoded CSN.
098   * @see #toByteString()
099   */
100  public static CSN valueOf(ByteSequence bs)
101  {
102    ByteSequenceReader reader = bs.asReader();
103    long timeStamp = reader.readLong();
104    int serverId = reader.readShort() & 0xffff;
105    int seqnum = reader.readInt();
106    return new CSN(timeStamp, seqnum, serverId);
107  }
108
109  /**
110   * Create a new {@link CSN} from a String.
111   *
112   * @param str
113   *          the string from which to create a {@link CSN}
114   */
115  public CSN(String str)
116  {
117    String temp = str.substring(0, 16);
118    timeStamp = Long.parseLong(temp, 16);
119
120    temp = str.substring(16, 20);
121    serverId = Integer.parseInt(temp, 16);
122
123    temp = str.substring(20, 28);
124    seqnum = Integer.parseInt(temp, 16);
125  }
126
127  /**
128   * Create a new {@link CSN}.
129   *
130   * @param timeStamp
131   *          timeStamp for the {@link CSN}
132   * @param seqNum
133   *          sequence number
134   * @param serverId
135   *          identity of server
136   */
137  public CSN(long timeStamp, int seqNum, int serverId)
138  {
139    this.serverId = serverId;
140    this.timeStamp = timeStamp;
141    this.seqnum = seqNum;
142  }
143
144  /**
145   * Getter for the time.
146   *
147   * @return the time
148   */
149  public long getTime()
150  {
151    return timeStamp;
152  }
153
154  /**
155   * Get the timestamp associated to this {@link CSN} in seconds.
156   *
157   * @return timestamp associated to this {@link CSN} in seconds
158   */
159  public long getTimeSec()
160  {
161    return timeStamp / 1000;
162  }
163
164  /**
165   * Getter for the sequence number.
166   *
167   * @return the sequence number
168   */
169  public int getSeqnum()
170  {
171    return seqnum;
172  }
173
174  /**
175   * Getter for the server ID.
176   *
177   * @return the server ID
178   */
179  public int getServerId()
180  {
181    return serverId;
182  }
183
184  @Override
185  public boolean equals(Object obj)
186  {
187    if (this == obj)
188    {
189      return true;
190    }
191    else if (obj instanceof CSN)
192    {
193      final CSN csn = (CSN) obj;
194      return this.seqnum == csn.seqnum && this.serverId == csn.serverId
195          && this.timeStamp == csn.timeStamp;
196    }
197    else
198    {
199      return false;
200    }
201  }
202
203  @Override
204  public int hashCode()
205  {
206    return this.seqnum + this.serverId + Long.valueOf(timeStamp).hashCode();
207  }
208
209  /**
210   * Encodes this CSN as a byte string.
211   * <p>
212   * NOTE: this representation must not be modified otherwise interop with
213   * earlier protocol versions will be broken.
214   *
215   * @return The encoded representation of this CSN.
216   * @see #valueOf(ByteSequence)
217   */
218  public ByteString toByteString()
219  {
220    final ByteStringBuilder builder = new ByteStringBuilder(BYTE_ENCODING_LENGTH);
221    toByteString(builder);
222    return builder.toByteString();
223  }
224
225  /**
226   * Encodes this CSN into the provided byte string builder.
227   * <p>
228   * NOTE: this representation must not be modified otherwise interop with
229   * earlier protocol versions will be broken.
230   *
231   * @param builder
232   *          The byte string builder.
233   * @see #valueOf(ByteSequence)
234   */
235  public void toByteString(ByteStringBuilder builder)
236  {
237    builder.appendLong(timeStamp).appendShort(serverId & 0xffff).appendInt(seqnum);
238  }
239
240  /**
241   * Convert the {@link CSN} to a printable String.
242   * <p>
243   * NOTE: this representation must not be modified otherwise interop with
244   * earlier protocol versions will be broken.
245   *
246   * @return the string
247   */
248  @Override
249  public String toString()
250  {
251    final StringBuilder buffer = new StringBuilder();
252    toString(buffer);
253    return buffer.toString();
254  }
255
256  /**
257   * Appends the text representation of this {@link CSN} into the provided StringBuilder.
258   * <p>
259   * NOTE: this representation must not be modified otherwise interop with
260   * earlier protocol versions will be broken.
261   *
262   * @param buffer the StringBuilder where to output the CSN text representation
263   */
264  void toString(final StringBuilder buffer)
265  {
266    leftPadWithZeros(buffer, 16, Long.toHexString(timeStamp));
267    leftPadWithZeros(buffer, 4, Integer.toHexString(serverId));
268    leftPadWithZeros(buffer, 8, Integer.toHexString(seqnum));
269  }
270
271  private void leftPadWithZeros(StringBuilder buffer, int nbChars, String toAppend)
272  {
273    final int padding = nbChars - toAppend.length();
274    for (int i = 0; i < padding; i++)
275    {
276      buffer.append('0');
277    }
278    buffer.append(toAppend);
279  }
280
281  /**
282   * Convert the {@link CSN} to a printable String with a user friendly format.
283   *
284   * @return the string
285   */
286  public String toStringUI()
287  {
288    final StringBuilder buffer = new StringBuilder();
289    toStringUI(buffer);
290    return buffer.toString();
291  }
292
293  private void toStringUI(final StringBuilder buffer)
294  {
295    toString(buffer);
296    buffer.append(" (sid=").append(serverId)
297          .append(",tsd=").append(new Date(timeStamp))
298          .append(",ts=").append(timeStamp)
299          .append(",seqnum=").append(seqnum)
300          .append(")");
301  }
302
303  /**
304   * Compares this CSN with the provided CSN for order and returns a negative
305   * number if {@code csn1} is older than {@code csn2}, zero if they have the
306   * same age, or a positive number if {@code csn1} is newer than {@code csn2}.
307   *
308   * @param csn1
309   *          The first CSN to be compared, which may be {@code null}.
310   * @param csn2
311   *          The second CSN to be compared, which may be {@code null}.
312   * @return A negative number if {@code csn1} is older than {@code csn2}, zero
313   *         if they have the same age, or a positive number if {@code csn1} is
314   *         newer than {@code csn2}.
315   */
316  public static int compare(CSN csn1, CSN csn2)
317  {
318    if (csn1 == null)
319    {
320      return csn2 == null ? 0 : -1;
321    }
322    else if (csn2 == null)
323    {
324      return 1;
325    }
326    else if (csn1.timeStamp != csn2.timeStamp)
327    {
328      return csn1.timeStamp < csn2.timeStamp ? -1 : 1;
329    }
330    else if (csn1.seqnum != csn2.seqnum)
331    {
332      return csn1.seqnum - csn2.seqnum;
333    }
334    else
335    {
336      return csn1.serverId - csn2.serverId;
337    }
338  }
339
340  /**
341   * Computes the difference in number of changes between 2 CSNs. First one is
342   * expected to be newer than second one. If this is not the case, 0 will be
343   * returned.
344   *
345   * @param csn1
346   *          the first {@link CSN}
347   * @param csn2
348   *          the second {@link CSN}
349   * @return the difference
350   */
351  public static int diffSeqNum(CSN csn1, CSN csn2)
352  {
353    if (csn1 == null)
354    {
355      return 0;
356    }
357    if (csn2 == null)
358    {
359      return csn1.getSeqnum();
360    }
361    if (csn2.isNewerThanOrEqualTo(csn1))
362    {
363      return 0;
364    }
365
366    int seqnum1 = csn1.getSeqnum();
367    long time1 = csn1.getTime();
368    int seqnum2 = csn2.getSeqnum();
369    long time2 = csn2.getTime();
370
371    if (time2 <= time1)
372    {
373      if (seqnum2 <= seqnum1)
374      {
375        return seqnum1 - seqnum2;
376      }
377      return Integer.MAX_VALUE - (seqnum2 - seqnum1) + 1;
378    }
379    return 0;
380  }
381
382  /**
383   * Returns {@code true} if this CSN is older than the provided CSN.
384   *
385   * @param csn
386   *          The CSN to be compared.
387   * @return {@code true} if this CSN is older than the provided CSN.
388   */
389  public boolean isOlderThan(CSN csn)
390  {
391    return compare(this, csn) < 0;
392  }
393
394  /**
395   * Returns {@code true} if this CSN is older than or equal to the provided
396   * CSN.
397   *
398   * @param csn
399   *          The CSN to be compared.
400   * @return {@code true} if this CSN is older than or equal to the provided
401   *         CSN.
402   */
403  public boolean isOlderThanOrEqualTo(CSN csn)
404  {
405    return compare(this, csn) <= 0;
406  }
407
408  /**
409   * Returns {@code true} if this CSN is newer than or equal to the provided
410   * CSN.
411   *
412   * @param csn
413   *          The CSN to be compared.
414   * @return {@code true} if this CSN is newer than or equal to the provided
415   *         CSN.
416   */
417  public boolean isNewerThanOrEqualTo(CSN csn)
418  {
419    return compare(this, csn) >= 0;
420  }
421
422  /**
423   * Returns {@code true} if this CSN is newer than the provided CSN.
424   *
425   * @param csn
426   *          The CSN to be compared.
427   * @return {@code true} if this CSN is newer than the provided CSN.
428   */
429  public boolean isNewerThan(CSN csn)
430  {
431    return compare(this, csn) > 0;
432  }
433
434  /**
435   * Compares this CSN with the provided CSN for order and returns a negative
436   * number if this CSN is older than {@code csn}, zero if they have the same
437   * age, or a positive number if this CSN is newer than {@code csn}.
438   *
439   * @param csn
440   *          The CSN to be compared.
441   * @return A negative number if this CSN is older than {@code csn}, zero if
442   *         they have the same age, or a positive number if this CSN is newer
443   *         than {@code csn}.
444   */
445  @Override
446  public int compareTo(CSN csn)
447  {
448    return compare(this, csn);
449  }
450}