All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.plugins.document.Revision Maven / Gradle / Ivy

There is a newer version: 1.62.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.oak.plugins.document;

import org.apache.jackrabbit.oak.cache.CacheValue;
import org.apache.jackrabbit.oak.plugins.document.util.Utils;
import org.apache.jackrabbit.oak.stats.Clock;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * A revision.
 */
public final class Revision implements CacheValue {

    //Extra 2 for those cases where counter or clusterId is 2 digit
    final static int REV_STRING_APPROX_SIZE = Revision.newRevision(0).toString().length() + 2;

    static final int SHALLOW_MEMORY_USAGE = 32;

    private static volatile long lastTimestamp;

    private static volatile long lastRevisionTimestamp;
    private static volatile int lastRevisionCount;

    /**
     * The timestamp in milliseconds since 1970 (unlike in seconds as in
     * MongoDB). The timestamp is local to the machine that generated the
     * revision, such that timestamps of revisions can only be compared if the
     * machine id is the same.
     */
    private final long timestamp;

    /**
     * An incrementing counter, for commits that occur within the same
     * millisecond.
     */
    private final int counter;

    /**
     * The cluster id (the MongoDB machine id).
     */
    private final int clusterId;

    /**
     * Whether this is a branch revision.
     */
    private final boolean branch;

    /** Only set for testing */
    private static Clock clock;

    /**
     * 
     * Only to be used for testing.
     * Do Not Use Otherwise
     * 
     * 
     * @param c - the clock
     */
    static void setClock(Clock c) {
        checkNotNull(c);
        clock = c;
    }

    static void resetClockToDefault(){
        clock = Clock.SIMPLE;
        lastTimestamp = clock.getTime();
        lastRevisionTimestamp = clock.getTime();

    }
    public Revision(long timestamp, int counter, int clusterId) {
        this(timestamp, counter, clusterId, false);
    }

    public Revision(long timestamp, int counter, int clusterId, boolean branch) {
        this.timestamp = timestamp;
        this.counter = counter;
        this.clusterId = clusterId;
        this.branch = branch;
    }

    /**
     * Compare the time part of two revisions. If they contain the same time,
     * the counter is compared.
     * 

* This method requires that both revisions are from the same cluster node. * * @param other the other revision * @return -1 if this revision occurred earlier, 1 if later, 0 if equal * @throws IllegalArgumentException if the cluster ids don't match */ int compareRevisionTime(Revision other) { if (clusterId != other.clusterId) { throw new IllegalArgumentException( "Trying to compare revisions of different cluster ids: " + this + " and " + other); } int comp = timestamp < other.timestamp ? -1 : timestamp > other.timestamp ? 1 : 0; if (comp == 0) { comp = counter < other.counter ? -1 : counter > other.counter ? 1 : 0; } return comp; } /** * Compare the time part of two revisions. If they contain the same time, * the counter is compared. If the counter is the same, the cluster ids are * compared. * * @param other the other revision * @return -1 if this revision occurred earlier, 1 if later, 0 if equal */ int compareRevisionTimeThenClusterId(Revision other) { int comp = timestamp < other.timestamp ? -1 : timestamp > other.timestamp ? 1 : 0; if (comp == 0) { comp = counter < other.counter ? -1 : counter > other.counter ? 1 : 0; } if (comp == 0) { comp = compareClusterId(other); } return comp; } /** * Compare all components of two revisions. * * @param other the other revision * @return -1, 0, or 1 */ int compareTo(Revision other) { int comp = compareRevisionTimeThenClusterId(other); if (comp == 0) { if (branch != other.branch) { return branch ? -1 : 1; } } return comp; } /** * Compare the cluster node ids of both revisions. * * @param other the other revision * @return -1 if this revision occurred earlier, 1 if later, 0 if equal */ int compareClusterId(Revision other) { return clusterId < other.clusterId ? -1 : clusterId > other.clusterId ? 1 : 0; } /** * Create a simple revision id. The format is similar to MongoDB ObjectId. * * @param clusterId the unique machineId + processId * @return the unique revision id */ static Revision newRevision(int clusterId) { long timestamp = getCurrentTimestamp(); int c; synchronized (Revision.class) { // need to check again, because threads // could arrive inside the synchronized block // out of order if (timestamp < lastRevisionTimestamp) { timestamp = lastRevisionTimestamp; } if (timestamp == lastRevisionTimestamp) { c = ++lastRevisionCount; } else { lastRevisionTimestamp = timestamp; lastRevisionCount = c = 0; } } return new Revision(timestamp, c, clusterId); } /** * Get the timestamp value of the current date and time. Within the same * process, the returned value is never smaller than a previously returned * value, even if the system time was changed. * * @return the timestamp */ public static long getCurrentTimestamp() { long timestamp = System.currentTimeMillis(); if (clock != null) { timestamp = clock.getTime(); } if (timestamp < lastTimestamp) { // protect against decreases in the system time, // time machines, and other fluctuations in the time continuum timestamp = lastTimestamp; } else if (timestamp > lastTimestamp) { lastTimestamp = timestamp; } return timestamp; } /** * Get the timestamp difference between two revisions (r1 - r2) in * milliseconds. * * @param r1 the first revision * @param r2 the second revision * @return the difference in milliseconds */ public static long getTimestampDifference(Revision r1, Revision r2) { return r1.getTimestamp() - r2.getTimestamp(); } public static Revision fromString(String rev) { boolean isBranch = rev.charAt(0) == 'b'; int idx = isBranch ? 2 : 1; if (rev.charAt(idx - 1) != 'r') { throw new IllegalArgumentException(rev); } int len = rev.length(); // Parse timestamp long timestamp = 0; for (; idx < len; idx++) { char c = rev.charAt(idx); if (c == '-') { break; } int digit = c >= 'a'? c - 'a' + 10 : c - '0'; timestamp = (timestamp << 4) + digit; } // Parse counter int counter = 0; for (idx++; idx < len; idx++) { char c = rev.charAt(idx); if (c == '-') { break; } int digit = c >= 'a' ? c - 'a' + 10 : c - '0'; counter = (counter << 4) + digit; } // Parse clusterId int clusterId = 0; for (idx++; idx < len; idx++) { char c = rev.charAt(idx); int digit = c >= 'a' ? c - 'a' + 10 : c - '0'; clusterId = (clusterId << 4) + digit; } return new Revision(timestamp, counter, clusterId, isBranch); } @Override public String toString() { return toStringBuilder(new StringBuilder(REV_STRING_APPROX_SIZE)).toString(); } /** * Appends the string representation of this revision to the given * StringBuilder. * * @param sb a StringBuilder. * @return the StringBuilder instance passed to this method. */ public StringBuilder toStringBuilder(StringBuilder sb) { if (branch) { sb.append('b'); } sb.append('r'); toHexString(sb, timestamp); sb.append('-'); toHexString(sb, counter); sb.append('-'); toHexString(sb, clusterId); return sb; } public String toReadableString() { StringBuilder buff = new StringBuilder(); buff.append("revision: \"").append(toString()).append("\""); buff.append(", clusterId: ").append(clusterId); buff.append(", time: \""). append(Utils.timestampToString(timestamp)). append("\""); if (counter > 0) { buff.append(", counter: ").append(counter); } if (branch) { buff.append(", branch: true"); } return buff.toString(); } /** * Get the timestamp in milliseconds since 1970. * * @return the timestamp */ public long getTimestamp() { return timestamp; } public int getCounter() { return counter; } /** * @return true if this is a branch revision, otherwise * false. */ public boolean isBranch() { return branch; } /** * Returns a revision with the same timestamp, counter and clusterId as this * revision and the branch flag set to true. * * @return branch revision with this timestamp, counter and clusterId. */ public Revision asBranchRevision() { if (isBranch()) { return this; } else { return new Revision(timestamp, counter, clusterId, true); } } /** * Returns a revision with the same timestamp, counter and clusterId as this * revision and the branch flag set to false. * * @return trunk revision with this timestamp, counter and clusterId. */ public Revision asTrunkRevision() { if (!isBranch()) { return this; } else { return new Revision(timestamp, counter, clusterId); } } @Override public int hashCode() { return (int) (timestamp >>> 32) ^ (int) timestamp ^ counter ^ clusterId; } @Override public boolean equals(Object other) { if (this == other) { return true; } else if (other == null) { return false; } else if (other.getClass() != this.getClass()) { return false; } Revision r = (Revision) other; return r.timestamp == this.timestamp && r.counter == this.counter && r.clusterId == this.clusterId && r.branch == this.branch; } public int getClusterId() { return clusterId; } @Override public int getMemory() { return SHALLOW_MEMORY_USAGE; } private static void toHexString(StringBuilder sb, long x) { int bitCount = (64 - Long.numberOfLeadingZeros(x)); bitCount = Math.max(0, ((bitCount + 3) / 4 * 4) - 4); for (int i = bitCount; i >= 0; i -= 4) { int t = (int) (x >> i) & 15; if (t > 9) { t = t - 10 + 'a'; } else { t += '0'; } sb.append((char) t); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy