/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions Copyright 2013-2015 ForgeRock AS. */ package org.opends.server.replication.common; import java.io.Serializable; import java.util.Date; import org.forgerock.opendj.ldap.ByteSequence; import org.forgerock.opendj.ldap.ByteSequenceReader; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.ByteStringBuilder; /** * Class used to represent Change Sequence Numbers. * * @see <a href="http://tools.ietf.org/html/draft-ietf-ldup-infomod-08" * >Inspiration for this class comes from LDAPChangeSequenceNumber</a> */ public class CSN implements Serializable, Comparable<CSN> { /** * The number of bytes used by the byte string representation of a change * number. * * @see #valueOf(ByteSequence) * @see #toByteString() * @see #toByteString(ByteStringBuilder) */ public static final int BYTE_ENCODING_LENGTH = 14; /** * The number of characters used by the string representation of a change * number. * * @see #valueOf(String) * @see #toString() */ public static final int STRING_ENCODING_LENGTH = 28; /** The maximum possible value for a CSN. */ public static final CSN MAX_CSN_VALUE = new CSN(Long.MAX_VALUE, Integer.MAX_VALUE, Short.MAX_VALUE); private static final long serialVersionUID = -8802722277749190740L; private final long timeStamp; /** * The sequence number is set to zero at the start of each millisecond, and * incremented by one for each update operation that occurs within that * millisecond. It allows to distinguish changes that have been done in the * same millisecond. */ private final int seqnum; private final int serverId; /** * Parses the provided {@link #toString()} representation of a CSN. * * @param s * The string to be parsed. * @return The parsed CSN. * @see #toString() */ public static CSN valueOf(String s) { return new CSN(s); } /** * Decodes the provided {@link #toByteString()} representation of a change * number. * * @param bs * The byte sequence to be parsed. * @return The decoded CSN. * @see #toByteString() */ public static CSN valueOf(ByteSequence bs) { ByteSequenceReader reader = bs.asReader(); long timeStamp = reader.readLong(); int serverId = reader.readShort() & 0xffff; int seqnum = reader.readInt(); return new CSN(timeStamp, seqnum, serverId); } /** * Create a new {@link CSN} from a String. * * @param str * the string from which to create a {@link CSN} */ public CSN(String str) { String temp = str.substring(0, 16); timeStamp = Long.parseLong(temp, 16); temp = str.substring(16, 20); serverId = Integer.parseInt(temp, 16); temp = str.substring(20, 28); seqnum = Integer.parseInt(temp, 16); } /** * Create a new {@link CSN}. * * @param timeStamp * timeStamp for the {@link CSN} * @param seqNum * sequence number * @param serverId * identity of server */ public CSN(long timeStamp, int seqNum, int serverId) { this.serverId = serverId; this.timeStamp = timeStamp; this.seqnum = seqNum; } /** * Getter for the time. * * @return the time */ public long getTime() { return timeStamp; } /** * Get the timestamp associated to this {@link CSN} in seconds. * * @return timestamp associated to this {@link CSN} in seconds */ public long getTimeSec() { return timeStamp / 1000; } /** * Getter for the sequence number. * * @return the sequence number */ public int getSeqnum() { return seqnum; } /** * Getter for the server ID. * * @return the server ID */ public int getServerId() { return serverId; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof CSN) { final CSN csn = (CSN) obj; return this.seqnum == csn.seqnum && this.serverId == csn.serverId && this.timeStamp == csn.timeStamp; } else { return false; } } @Override public int hashCode() { return this.seqnum + this.serverId + Long.valueOf(timeStamp).hashCode(); } /** * Encodes this CSN as a byte string. * <p> * NOTE: this representation must not be modified otherwise interop with * earlier protocol versions will be broken. * * @return The encoded representation of this CSN. * @see #valueOf(ByteSequence) */ public ByteString toByteString() { final ByteStringBuilder builder = new ByteStringBuilder(BYTE_ENCODING_LENGTH); toByteString(builder); return builder.toByteString(); } /** * Encodes this CSN into the provided byte string builder. * <p> * NOTE: this representation must not be modified otherwise interop with * earlier protocol versions will be broken. * * @param builder * The byte string builder. * @see #valueOf(ByteSequence) */ public void toByteString(ByteStringBuilder builder) { builder.appendLong(timeStamp).appendShort(serverId & 0xffff).appendInt(seqnum); } /** * Convert the {@link CSN} to a printable String. * <p> * NOTE: this representation must not be modified otherwise interop with * earlier protocol versions will be broken. * * @return the string */ @Override public String toString() { final StringBuilder buffer = new StringBuilder(); toString(buffer); return buffer.toString(); } /** * Appends the text representation of this {@link CSN} into the provided StringBuilder. * <p> * NOTE: this representation must not be modified otherwise interop with * earlier protocol versions will be broken. * * @param buffer the StringBuilder where to output the CSN text representation */ void toString(final StringBuilder buffer) { leftPadWithZeros(buffer, 16, Long.toHexString(timeStamp)); leftPadWithZeros(buffer, 4, Integer.toHexString(serverId)); leftPadWithZeros(buffer, 8, Integer.toHexString(seqnum)); } private void leftPadWithZeros(StringBuilder buffer, int nbChars, String toAppend) { final int padding = nbChars - toAppend.length(); for (int i = 0; i < padding; i++) { buffer.append('0'); } buffer.append(toAppend); } /** * Convert the {@link CSN} to a printable String with a user friendly format. * * @return the string */ public String toStringUI() { final StringBuilder buffer = new StringBuilder(); toStringUI(buffer); return buffer.toString(); } private void toStringUI(final StringBuilder buffer) { toString(buffer); buffer.append(" (sid=").append(serverId) .append(",tsd=").append(new Date(timeStamp)) .append(",ts=").append(timeStamp) .append(",seqnum=").append(seqnum) .append(")"); } /** * Compares this CSN with the provided CSN for order and returns a negative * number if {@code csn1} is older than {@code csn2}, zero if they have the * same age, or a positive number if {@code csn1} is newer than {@code csn2}. * * @param csn1 * The first CSN to be compared, which may be {@code null}. * @param csn2 * The second CSN to be compared, which may be {@code null}. * @return A negative number if {@code csn1} is older than {@code csn2}, zero * if they have the same age, or a positive number if {@code csn1} is * newer than {@code csn2}. */ public static int compare(CSN csn1, CSN csn2) { if (csn1 == null) { return csn2 == null ? 0 : -1; } else if (csn2 == null) { return 1; } else if (csn1.timeStamp != csn2.timeStamp) { return csn1.timeStamp < csn2.timeStamp ? -1 : 1; } else if (csn1.seqnum != csn2.seqnum) { return csn1.seqnum - csn2.seqnum; } else { return csn1.serverId - csn2.serverId; } } /** * Computes the difference in number of changes between 2 CSNs. First one is * expected to be newer than second one. If this is not the case, 0 will be * returned. * * @param csn1 * the first {@link CSN} * @param csn2 * the second {@link CSN} * @return the difference */ public static int diffSeqNum(CSN csn1, CSN csn2) { if (csn1 == null) { return 0; } if (csn2 == null) { return csn1.getSeqnum(); } if (csn2.isNewerThanOrEqualTo(csn1)) { return 0; } int seqnum1 = csn1.getSeqnum(); long time1 = csn1.getTime(); int seqnum2 = csn2.getSeqnum(); long time2 = csn2.getTime(); if (time2 <= time1) { if (seqnum2 <= seqnum1) { return seqnum1 - seqnum2; } return Integer.MAX_VALUE - (seqnum2 - seqnum1) + 1; } return 0; } /** * Returns {@code true} if this CSN is older than the provided CSN. * * @param csn * The CSN to be compared. * @return {@code true} if this CSN is older than the provided CSN. */ public boolean isOlderThan(CSN csn) { return compare(this, csn) < 0; } /** * Returns {@code true} if this CSN is older than or equal to the provided * CSN. * * @param csn * The CSN to be compared. * @return {@code true} if this CSN is older than or equal to the provided * CSN. */ public boolean isOlderThanOrEqualTo(CSN csn) { return compare(this, csn) <= 0; } /** * Returns {@code true} if this CSN is newer than or equal to the provided * CSN. * * @param csn * The CSN to be compared. * @return {@code true} if this CSN is newer than or equal to the provided * CSN. */ public boolean isNewerThanOrEqualTo(CSN csn) { return compare(this, csn) >= 0; } /** * Returns {@code true} if this CSN is newer than the provided CSN. * * @param csn * The CSN to be compared. * @return {@code true} if this CSN is newer than the provided CSN. */ public boolean isNewerThan(CSN csn) { return compare(this, csn) > 0; } /** * Compares this CSN with the provided CSN for order and returns a negative * number if this CSN is older than {@code csn}, zero if they have the same * age, or a positive number if this CSN is newer than {@code csn}. * * @param csn * The CSN to be compared. * @return A negative number if this CSN is older than {@code csn}, zero if * they have the same age, or a positive number if this CSN is newer * than {@code csn}. */ @Override public int compareTo(CSN csn) { return compare(this, csn); } }