/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/lock/NodeLock.java,v 1.19 2004/11/08 09:32:15 luetzkendorf Exp $
 * $Revision: 1.19 $
 * $Date: 2004/11/08 09:32:15 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * Licensed 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.slide.lock;

import java.util.Date;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.slide.common.ObjectValidationFailedException;
import org.apache.slide.structure.ActionNode;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.util.Messages;

/**
 * NodeLock class.
 * 
 * @version $Revision: 1.19 $
 */
public final class NodeLock implements Cloneable, java.io.Serializable {
	public final static int SHARED = 0;

	public final static int EXCLUSIVE = 1;

	/*
	 * Indicates that this is a transaction related lock
	 */
	public final static int LOCAL = 2;

	/**
	 * Locked object.
	 */
	protected String objectUri;

	/**
	 * User who is the lock owner.
	 */
	protected String subjectUri;

	/**
	 * Lock type.
	 */
	protected String typeUri;

	/**
	 * Expiration date of the lock.
	 */
	protected Date expirationDate;

	/**
	 * Lock is inheritable.
	 */
	protected boolean inheritance;

	/**
	 * Lock scope.
	 */
	protected int scope;

	/**
	 * Lock Id.
	 */
	protected String lockId;

	/**
	 * Contacting information about the lock owner
	 */
	protected String ownerInfo;

	/**
	 * Constructor.
	 */
	public NodeLock() {
	}

	/**
	 * Constructor.
	 * 
	 * @param locked
	 *            Locked object
	 * @param user
	 *            Lock owner
	 * @param lockType
	 *            Type of the lock
	 * @param expirationDate
	 *            Date of expiration of the lock
	 * @param inheritance
	 *            True if lock is inheritable
	 */
	public NodeLock(ObjectNode locked, SubjectNode user, ActionNode lockType,
			Date expirationDate, boolean inheritance) {
		this(locked.getUri(), user.getUri(), lockType.getUri(), expirationDate,
				inheritance);
	}

	/**
	 * Constructor.
	 * 
	 * @param locked
	 *            Locked object
	 * @param user
	 *            Lock owner
	 * @param lockType
	 *            Type of the lock
	 * @param expirationDate
	 *            Date of expiration of the lock
	 * @param inheritance
	 *            True if lock is inheritable
	 */
	public NodeLock(ObjectNode locked, SubjectNode user, ActionNode lockType,
			Date expirationDate, boolean inheritance, boolean exclusive) {
		this(locked.getUri(), user.getUri(), lockType.getUri(), expirationDate,
				inheritance, exclusive);
	}

	/**
	 * Constructor.
	 * 
	 * @param locked
	 *            Locked object
	 * @param user
	 *            Lock owner
	 * @param lockType
	 *            Type of the lock
	 * @param expirationDate
	 *            Date of expiration of the lock
	 * @param inheritance
	 *            True if lock is inheritable
	 */
	public NodeLock(ObjectNode locked, SubjectNode user, ActionNode lockType,
			Date expirationDate, boolean inheritance, boolean exclusive,
			String ownerInfo) {
		this(locked.getUri(), user.getUri(), lockType.getUri(), expirationDate,
				inheritance, exclusive, ownerInfo);
	}

	/**
	 * Constructor.
	 * 
	 * @param objectUri
	 *            Locked object uri
	 * @param subjectUri
	 *            Lock owner uri
	 * @param typeUri
	 *            Lock type uri
	 * @param expirationDate
	 *            Date of expiration of the lock
	 * @param inheritance
	 *            True if lock is inheritable
	 */
	public NodeLock(String objectUri, String subjectUri, String typeUri,
			Date expirationDate, boolean inheritance) {
		this(objectUri, subjectUri, typeUri, expirationDate, inheritance, true);
	}

	/**
	 * Constructor.
	 * 
	 * @param objectUri
	 *            Locked object uri
	 * @param subjectUri
	 *            Lock owner uri
	 * @param typeUri
	 *            Lock type uri
	 * @param expirationDate
	 *            Date of expiration of the lock
	 * @param inheritance
	 *            True if lock is inheritable
	 */
	public NodeLock(String objectUri, String subjectUri, String typeUri,
			Date expirationDate, boolean inheritance, boolean exclusive) {
		this(objectUri, subjectUri, typeUri, expirationDate, inheritance,
				exclusive, null);
	}

	/**
	 * Constructor.
	 * 
	 * @param objectUri
	 *            Locked object uri
	 * @param subjectUri
	 *            Lock owner uri
	 * @param typeUri
	 *            Lock type uri
	 * @param expirationDate
	 *            Date of expiration of the lock
	 * @param inheritance
	 *            True if lock is inheritable
	 * @param exclusive
	 *            True if lock is exclusive
	 * @param ownerInfo
	 *            contacting info about the lock owner (phone number, email)
	 */
	public NodeLock(String objectUri, String subjectUri, String typeUri,
			Date expirationDate, boolean inheritance, boolean exclusive,
			String ownerInfo) {
		this(generateLockID(objectUri.hashCode(), subjectUri.hashCode(),
				typeUri.hashCode(), (expirationDate == null) ? 0
						: expirationDate.getTime()), objectUri, subjectUri,
				typeUri, expirationDate, inheritance, exclusive, ownerInfo);
	}

	/**
	 * Constructor. Creates a new lock linked to another given lock.
	 * 
	 * @param lock
	 *            Linked lock
	 */
	public NodeLock(NodeLock lock, String typeUri) {
		this(lock.getLockId(), lock.getObjectUri(), lock.getSubjectUri(),
				typeUri, lock.getExpirationDate(), lock.isInheritable(), lock
						.isExclusive(), lock.getOwnerInfo());
	}

	/**
	 * Constructor.
	 * 
	 * @param objectUri
	 *            Locked object uri
	 * @param subjectUri
	 *            Lock owner uri
	 * @param typeUri
	 *            Lock type uri
	 * @param expirationDate
	 *            Date of expiration of the lock
	 * @param inheritance
	 *            True if lock is inheritable
	 */
	public NodeLock(String lockId, String objectUri, String subjectUri,
			String typeUri, Date expirationDate, boolean inheritance,
			boolean exclusive) {
		this(lockId, objectUri, subjectUri, typeUri, expirationDate,
				inheritance, exclusive, null);
	}

	/**
	 * Constructor.
	 * 
	 * @param objectUri
	 *            Locked object uri
	 * @param subjectUri
	 *            Lock owner uri
	 * @param typeUri
	 *            Lock type uri
	 * @param expirationDate
	 *            Date of expiration of the lock
	 * @param inheritance
	 *            True if lock is inheritable
	 * @param exclusive
	 *            True if lock is exclusive
	 * @param ownerInfo
	 *            contacting info about the lock owner (phone number, email)
	 */
	public NodeLock(String lockId, String objectUri, String subjectUri,
			String typeUri, Date expirationDate, boolean inheritance,
			boolean exclusive, String ownerInfo) {
		this(lockId, objectUri, subjectUri, typeUri, expirationDate,inheritance, exclusive ? EXCLUSIVE : SHARED, ownerInfo);
	}

	public NodeLock(String objectUri, String subjectUri, String typeUri,
			Date expirationDate, boolean inheritance, int scope,
			String ownerInfo) {
		this(generateLockID(objectUri.hashCode(), subjectUri.hashCode(),
				typeUri.hashCode(), (expirationDate == null) ? 0
						: expirationDate.getTime()), objectUri, subjectUri,
				typeUri, expirationDate, inheritance, scope, ownerInfo);
	} 

	public NodeLock(String lockId, String objectUri, String subjectUri,
			String typeUri, Date expirationDate, boolean inheritance,
			int scope, String ownerInfo) {
		this.objectUri = objectUri;
		this.subjectUri = subjectUri;
		this.typeUri = typeUri;
		this.expirationDate = expirationDate;
		this.inheritance = inheritance;
		this.lockId = lockId;
		this.scope = scope;
		this.ownerInfo = ownerInfo;
	}

	/**
	 * Utility method for safely generating an lock id.
	 * 
	 * @param object
	 *            The hascode of the object-uri of the lock.
	 * @param subject
	 *            The hashcode of the subject-uri of the lock.
	 * @param type
	 *            The hashcode of the type-uri of the lock.
	 * @param expires
	 *            The time in milliseconds of the expires date.
	 * @return The generated lock-id.
	 */
	private static final String generateLockID(int object, int subject,
			int type, long expires) {
		long current = System.currentTimeMillis();
		byte[] input = new byte[4 + 4 + 4 + 8 + 8];
		encode(input, 0, object);
		encode(input, 4, subject);
		encode(input, 8, type);
		if (expires != 0)
			encode(input, 12, expires);
		encode(input, 20, current);
        return DigestUtils.md5Hex(input);
	}

	/**
	 * Encodes the given integer into the buffer at given position. It will
	 * always append 4 bytes.
	 */
	private static final void encode(byte[] buf, int offset, int number) {
		buf[offset + 0] = (byte) ((number) & 0xff);
		buf[offset + 1] = (byte) ((number >> 8) & 0xff);
		buf[offset + 2] = (byte) ((number >> 16) & 0xff);
		buf[offset + 3] = (byte) ((number >> 24) & 0xff);
	}

	/**
	 * Encodes the given long into the buffer at given position. It will always
	 * append 8 bytes.
	 */
	private static final void encode(byte[] buf, int offset, long number) {
		buf[offset + 0] = (byte) ((number) & 0xff);
		buf[offset + 1] = (byte) ((number >> 8) & 0xff);
		buf[offset + 2] = (byte) ((number >> 16) & 0xff);
		buf[offset + 3] = (byte) ((number >> 24) & 0xff);
		buf[offset + 4] = (byte) ((number >> 32) & 0xff);
		buf[offset + 5] = (byte) ((number >> 40) & 0xff);
		buf[offset + 6] = (byte) ((number >> 48) & 0xff);
		buf[offset + 7] = (byte) ((number >> 56) & 0xff);
	}

	/**
	 * Locked object accessor.
	 * 
	 * @return String Locked subject uri
	 */
	public String getObjectUri() {
		return this.objectUri;
	}

	/**
	 * Locked object mutator.
	 * 
	 * @param objectUri
	 *            Locked subject
	 */
	public void setObjectUri(String objectUri) {
		this.objectUri = objectUri;
	}

	/**
	 * Lock owner uri accessor.
	 * 
	 * @return String Lock owner
	 */
	public String getSubjectUri() {
		return this.subjectUri;
	}

	/**
	 * Lock owner uri mutator.
	 * 
	 * @param subjectUri
	 *            Lock owner
	 */
	void setSubjectUri(String subjectUri) {
		this.subjectUri = subjectUri;
	}

	/**
	 * Lock type uri accessor.
	 * 
	 * @return String Lock type uri
	 */
	public String getTypeUri() {
		return this.typeUri;
	}

	/**
	 * Lock type mutator.
	 * 
	 * @param typeUri
	 *            Lock type
	 */
	void setTypeUri(String typeUri) {
		this.typeUri = typeUri;
	}

	/**
	 * Expiration date accessor.
	 * 
	 * @return Date Exipiration date
	 */
	public Date getExpirationDate() {
		return expirationDate;
	}

	/**
	 * Expiration date mutator.
	 * 
	 * @param expirationDate
	 *            Expiration date
	 */
	void setExpirationDate(Date expirationDate) {
		this.expirationDate = expirationDate;
	}

	/**
	 * Expiration test.
	 * 
	 * @return boolean True if this lock has expired
	 */
	public boolean hasExpired() {
		//Date currentTime = new Date();
		return (expirationDate.before(new Date()));
	}

	/**
	 * Inheritance flag accessor.
	 * 
	 * @return boolean True if token is inheritable
	 */
	public boolean isInheritable() {
		return inheritance;
	}

	/**
	 * Inheritance flag mutator.
	 * 
	 * @param inheritance
	 *            Inheritance flag
	 */
	void setInheritable(boolean inheritance) {
		this.inheritance = inheritance;
	}

	/**
	 * Exclusive flag accessor.
	 * 
	 * @return boolean True if token is exclusive
	 */
	public boolean isExclusive() {
		return (scope == EXCLUSIVE);
	}

	/**
	 * Exclusive flag accessor.
	 * 
	 * @return boolean True if token is shared (ie, not exclusive)
	 */
	public boolean isShared() {
		return (scope == SHARED);
	}

	/**
	 * Exclusive flag accessor.
	 * 
	 * @return boolean True if token is exclusive
	 */
	public boolean isLocal() {
		return (scope == LOCAL);
	}

	/**
	 * Exclusive flag mutator.
	 * 
	 * @param exclusive
	 *            Exclusive flag
	 */
	void setExclusive(boolean exclusive) {
		if (exclusive) {
			scope = EXCLUSIVE;
		} else {
			scope = SHARED;
		}
	}

	/**
	 * Get lock identifier.
	 * 
	 * @return String lock id
	 */
	public String getLockId() {
		return lockId;
	}

	/**
	 * Set the contacting info for the lock owner (phone, email, etc.)
	 * 
	 * @param ownerInfo
	 *            a String
	 *  
	 */
	public void setOwnerInfo(String ownerInfo) {
		this.ownerInfo = ownerInfo;
	}

	/**
	 * Get the contacting info for the lock owner (phone, email, etc.)
	 * 
	 * @return a String
	 *  
	 */
	public String getOwnerInfo() {
		return ownerInfo;
	}

	// --------------------------------------------------------- Object Methods

	/**
	 * Clone.
	 * 
	 * @return Object clone
	 */
	public NodeLock cloneObject() {
		NodeLock result = null;
		try {
			result = (NodeLock) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return result;
	}

	/**
	 * Equals.
	 * 
	 * @param obj
	 *            Object to test
	 * @return boolean True if the two object are equal :
	 *         <li>obj is of type SlidePermission and is not null</li>
	 *         <li>All three Uris are equal</li>
	 */
	public boolean equals(Object obj) {
		boolean result = false;
		if ((obj != null) && (obj instanceof NodeLock)) {
			NodeLock lock = (NodeLock) obj;
			result = this.getLockId().equals(lock.getLockId());
		}
		return result;
	}

	/**
	 * Validate.
	 * 
	 * @param expectedUri
	 *            Uri
	 */
	public void validate(String expectedUri) {

		if (objectUri == null)
			throw new ObjectValidationFailedException(expectedUri, Messages
					.message(NodeLock.class.getName() + ".nullObjectUri"));

		if (!objectUri.equals(expectedUri))
			throw new ObjectValidationFailedException(expectedUri, Messages
					.message(NodeLock.class.getName() + ".incorrectObjectUri"));

		if (subjectUri == null)
			throw new ObjectValidationFailedException(expectedUri, Messages
					.message(NodeLock.class.getName() + ".nullSubjectUri"));

		if (typeUri == null)
			throw new ObjectValidationFailedException(expectedUri, Messages
					.message(NodeLock.class.getName() + ".nullTypeUri"));

		if (expirationDate == null)
			throw new ObjectValidationFailedException(expectedUri, Messages
					.message(NodeLock.class.getName() + ".nullExpirationDate"));

		if (lockId == null)
			throw new ObjectValidationFailedException(expectedUri, Messages
					.message(NodeLock.class.getName() + ".nullLockId"));

	}

}