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