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