/* * 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.core.lock; import org.apache.jackrabbit.core.SessionImpl; import org.apache.jackrabbit.core.id.NodeId; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.lock.LockException; /** * Internal lock information. */ public abstract class LockInfo { /** * The biggest possible timeout hint value (in seconds), used to avoid * overflows when calculating the timeout. 100 years should be plenty * enough for anyone... */ private static final long MAXIMUM_TIMEOUT = 100L * 365L * 24L * 60L * 60L; /** * Lock holder node id. Used also as the lock token. */ private final NodeId id; /** * Flag indicating whether lock is session scoped */ private final boolean sessionScoped; /** * Flag indicating whether lock is deep */ private final boolean deep; /** * Lock owner, determined on creation time */ private final String lockOwner; /** * Lock timeout hint (in seconds) given when the lock was created. * Guaranteed to be between 0 and {@link #MAXIMUM_TIMEOUT}. If the value * is 0, then this lock will not timeout. */ private final long timeoutHint; /** * Time (in milliseconds since the epoch) when this lock will timeout. Set to * {@link Long#MAX_VALUE} if this lock will not timeout. */ private long timeoutTime; /** * Flag indicating whether this lock is live. See also {@link #timeoutTime}. */ private boolean live; /** * Session currently holding lock */ private SessionImpl lockHolder; /** * Create a new instance of this class. * * @param id lock holder node id * @param sessionScoped whether lock token is session scoped * @param deep whether lock is deep * @param lockOwner owner of lock * @param timeoutHint lock timeout hint in seconds */ protected LockInfo( NodeId id, boolean sessionScoped, boolean deep, String lockOwner, long timeoutHint) { this.id = id; this.sessionScoped = sessionScoped; this.deep = deep; this.lockOwner = lockOwner; this.timeoutHint = timeoutHint; updateTimeoutTime(); } protected LockInfo(LockInfo that) { this.id = that.id; this.sessionScoped = that.sessionScoped; this.deep = that.deep; this.lockOwner = that.lockOwner; this.timeoutHint = that.timeoutHint; this.timeoutTime = that.timeoutTime; } /** * Return the lock token associated with this lock. * * @return lock token */ public String getLockToken() { String uuid = id.toString(); return uuid + "-" + getLockTokenCheckDigit(uuid); } /** * Return the ID of the lock holding node * @return the id */ public NodeId getId() { return id; } /** * Return the lock owner. * * @return lock owner */ public String getLockOwner() { return lockOwner; } /** * Return a flag indicating whether the lock is deep. * * @return <code>true</code> if the lock is deep; * <code>false</code> otherwise */ public boolean isDeep() { return deep; } /** * Return a flag indicating whether the session given is lock holder. * * @param session session to compare with */ public boolean isLockHolder(Session session) { return lockHolder == session; } /** * Return the session currently holding the lock * * @return session currently holding the lock */ public SessionImpl getLockHolder() { return lockHolder; } /** * Set the session currently holding the lock * * @param lockHolder session currently holding the lock */ public void setLockHolder(SessionImpl lockHolder) { this.lockHolder = lockHolder; } /** * Return a flag indicating whether the lock is live * * @return <code>true</code> if the lock is live; otherwise <code>false</code> */ public boolean isLive() { return live; } /** * Set the live flag * * @param live live flag */ public void setLive(boolean live) { this.live = live; } /** * Return a flag indicating whether the lock information may still change. * * @return <code>true</code> if the lock is still alive. */ public boolean mayChange() { return live; } /** * Return a flag indicating whether the lock is session-scoped * * @return <code>true</code> if the lock is session-scoped; * otherwise <code>false</code> */ public boolean isSessionScoped() { return sessionScoped; } /** * Returns the timeout hint given when the lock was created. * * @return timeout hint (in seconds) */ public long getTimeoutHint() { return timeoutHint; } /** * Returns the time when this lock will expire. * * @return timeout time in milliseconds since the epoch */ public long getTimeoutTime() { return timeoutTime; } public boolean isExpired() { return timeoutTime != Long.MAX_VALUE && System.currentTimeMillis() > timeoutTime; } /** * Updates the timeout time of this lock based on current time and * the timeout hint specified for this lock. The timeout time is always * rounded up. */ public void updateTimeoutTime() { if (timeoutHint > 0 && timeoutHint <= MAXIMUM_TIMEOUT) { long now = System.currentTimeMillis(); this.timeoutTime = now + timeoutHint * 1000; } else { this.timeoutTime = Long.MAX_VALUE; } } /** * Utility method that throws a {@link LockException} with the * "failure node path" set to the path of the node that holds this lock. * The given session is used to resolve the path of the lock holder node. * * @param message exception message * @param session session that the user was using for the failing operation * @throws LockException always thrown, unless another error occurs * @throws RepositoryException if the path of this lock can not be resolved */ public void throwLockException(String message, SessionImpl session) throws LockException, RepositoryException { String path; try { path = session.getJCRPath( session.getHierarchyManager().getPath(id)); } catch (RepositoryException ignored) { path = null; } if (path != null) { throw new LockException( message + " (lock held by node " + path + ")", null, path); } else { throw new LockException(message); } } /** * {@inheritDoc} */ public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append('('); if (deep) { buffer.append("deep "); } if (sessionScoped) { buffer.append("session "); } buffer.append("holder:"); if (lockHolder != null) { buffer.append(lockHolder.getUserID()).append(' '); } else { buffer.append("none "); } buffer.append("owner:").append(lockOwner); buffer.append(')'); return buffer.toString(); } /** * Parse a lock token string representation and return the lock * holder node id. * * @param token string representation of lock token * @return lock holder node id * @throws IllegalArgumentException if some field is illegal */ public static NodeId parseLockToken(String token) throws IllegalArgumentException { int sep = token.lastIndexOf('-'); if (sep == -1 || sep == token.length() - 1) { throw new IllegalArgumentException("Separator not found. Token [" + token + "]"); } String uuid = token.substring(0, sep); if (getLockTokenCheckDigit(uuid) != token.charAt(token.length() - 1)) { throw new IllegalArgumentException("Bad check digit. Token [" + token + "]"); } return NodeId.valueOf(uuid); } /** * Return the check digit for a lock token, given by its UUID * @param uuid uuid * @return check digit */ private static char getLockTokenCheckDigit(String uuid) { int result = 0; int multiplier = 36; for (int i = 0; i < uuid.length(); i++) { char c = uuid.charAt(i); if (c >= '0' && c <= '9') { int num = c - '0'; result += multiplier * num; multiplier--; } else if (c >= 'A' && c <= 'F') { int num = c - 'A' + 10; result += multiplier * num; multiplier--; } else if (c >= 'a' && c <= 'f') { int num = c - 'a' + 10; result += multiplier * num; multiplier--; } } int rem = result % 37; if (rem != 0) { rem = 37 - rem; } if (rem >= 0 && rem <= 9) { return (char) ('0' + rem); } else if (rem >= 10 && rem <= 35) { return (char) ('A' + rem - 10); } else { return '+'; } } }