/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/UnlockMethod.java,v 1.45 2005/02/25 17:03:53 luetzkendorf Exp $
 * $Revision: 1.45 $
 * $Date: 2005/02/25 17:03:53 $
 *
 * ====================================================================
 *
 * 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.webdav.method;

import java.io.IOException;
import java.util.Enumeration;

import javax.transaction.Transaction;

import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.event.EventDispatcher;
import org.apache.slide.lock.LockTokenNotFoundException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.structure.LinkedObjectNotFoundException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.transaction.ExternalTransactionContext;
import org.apache.slide.util.Configuration;
import org.apache.slide.webdav.WebdavException;
import org.apache.slide.webdav.WebdavServletConfig;
import org.apache.slide.webdav.event.WebdavEvent;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.PreconditionViolationException;
import org.apache.slide.webdav.util.ViolatedPrecondition;
import org.apache.slide.webdav.util.WebdavConstants;
import org.apache.slide.webdav.util.WebdavStatus;
import org.apache.slide.webdav.util.resourcekind.AbstractResourceKind;
import org.apache.slide.webdav.util.resourcekind.CheckedOutVersionControlled;
import org.apache.slide.webdav.util.resourcekind.ResourceKind;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;

/**
 * UNLOCK method.
 * 
 */
public class UnlockMethod extends AbstractWebdavMethod implements
		WebdavConstants, WriteMethod, FineGrainedLockingMethod {

	public static final String LOCK_TOKEN_HEADER_MISSING = "Lock-Token header missing";

	public static final String INVALID_LOCK_TOKEN = "Lock-Token is invalid";

	public static final String UNLOCK_NOT_ALLOWED = "Principal is neither the lock-owner nor has DAV:unlock privilege";

	public static final String IS_NOT_LOCK_ROOT = "Please try to unlock the root of the lock: ";

	public final static int NO_TRANSACTION = 0;

	public final static int ABORT_TRANSACTION = 1;

	public final static int COMMIT_TRANSACTION = 2;

	// ----------------------------------------------------- Instance Variables

	/**
	 * Resource to unlock.
	 */
	private String resourcePath;

	/**
	 * Id.
	 */
	private String lockId;

	private int command = NO_TRANSACTION;

	// ----------------------------------------------------------- Constructors

	/**
	 * Constructor.
	 * 
	 * @param token
	 *            the token for accessing the namespace
	 * @param config
	 *            configuration of the WebDAV servlet
	 */
	public UnlockMethod(NamespaceAccessToken token, WebdavServletConfig config) {
		super(token, config);
	}

  /**
   * @see org.apache.slide.webdav.method.FineGrainedLockingMethod#acquireFineGrainLocks()
   */
  public void acquireFineGrainLocks() {
      acquireStandardLocks(resourcePath);
      // changes this
      acquireLock(resourcePath, WRITE_LOCK);
  }

	// ------------------------------------------------------ Protected Methods

    /**
     * get the command for this unlock operation.
     * 
     * @return int command. One of NO_TRANSACTION, COMMIT_TRANSACTION, ABORT_TRANSACTION
     */
    protected int getCommand()
    {
        return command;
    }
    
	/**
	 * Parse the request.
	 * 
	 * @exception WebdavException
	 *                Bad request
	 */
	protected void parseRequest() throws WebdavException {

		// Loads the associated object from the store.
		resourcePath = requestUri;
		if (resourcePath == null) {
			resourcePath = "/";
		}

		lockId = requestHeaders.getLockToken().trim(); // XXX trim is needed for Netdrive which sends trailing spaces
		if (lockId == null) {
			sendError(WebdavStatus.SC_PRECONDITION_FAILED,
					LOCK_TOKEN_HEADER_MISSING);
			throw new WebdavException(WebdavStatus.SC_PRECONDITION_FAILED);
		}
		try {
			Document requestContent = parseRequestContent();
			if (requestContent != null) {
				Element transactioninfo = requestContent.getRootElement();
				if (E_TRANSACTIONINFO.equals(transactioninfo.getName())) {
                    slideToken.setExternalTx();
					Element transactionstatus = transactioninfo
							.getChild(E_TRANSACTIONSTATUS, Namespace.getNamespace(S_DAV));
					if (transactionstatus != null) {
						Element action = (Element) transactionstatus
								.getChildren().get(0);
						if (action != null) {
							if (E_COMMIT.equals(action.getName())) {
								command = COMMIT_TRANSACTION;
							} else if (E_ABORT.equals(action.getName())) {
								command = ABORT_TRANSACTION;
							} else {
                        sendError(WebdavStatus.SC_BAD_REQUEST, 
                                "transactionstatus must be one of commit or abort");
                        throw new WebdavException(WebdavStatus.SC_BAD_REQUEST); 
                     }
						} else {
						    sendError(WebdavStatus.SC_BAD_REQUEST, 
                          "transactionstatus must contain one of commit or abort");
                      throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
                  }
					}
				}
			}
		} 
      catch (WebdavException e) {
          throw e;
      }
      catch (Exception exception) {
		   sendError(WebdavStatus.SC_BAD_REQUEST, exception);
			throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
		}
	}

	/**
	 * Execute the request.
	 * 
	 * @exception WebdavException
	 */
	protected void executeRequest() throws WebdavException {

		// Prevent dirty reads
		slideToken.setForceStoreEnlistment(true);
        if (command == COMMIT_TRANSACTION || command == ABORT_TRANSACTION) {

            String fullTxId = "<" + S_LOCK_TOKEN + lockId + ">";

            ExternalTransactionContext externalTransaction = ExternalTransactionContext.lookupContext(fullTxId);
            if (externalTransaction != null) {
                Transaction tx = externalTransaction.getTransaction();
                try {
                    token.getTransactionManager().resume(tx);
                } catch (Exception exception) {
                    sendError(WebdavStatus.SC_METHOD_FAILURE, "No such transaction");
                }
            } else {
                sendError(WebdavStatus.SC_METHOD_FAILURE, "No such transaction");
            }

            if (command == COMMIT_TRANSACTION) {
                try {
                    token.commit();
                } catch (Exception exception) {
                    sendError(WebdavStatus.SC_METHOD_FAILURE, "Could not commit transaction");
                }
            } else if (command == ABORT_TRANSACTION) {
                try {
                    token.rollback();
                } catch (Exception exception) {
                    sendError(WebdavStatus.SC_METHOD_FAILURE, "Could not roll back transaction");
                }
            }
            ExternalTransactionContext.deregisterContext(lockId);
            
            resp.setStatus(WebdavStatus.SC_NO_CONTENT);
        } else {
			try {
				if (WebdavEvent.UNLOCK.isEnabled())
					EventDispatcher.getInstance().fireVetoableEvent(
							WebdavEvent.UNLOCK, new WebdavEvent(this));

				checkPreconditions();
				lock.unlock(slideToken, requestUri, lockId);

				NodeRevisionDescriptors revisionDescriptors = content.retrieve(
						slideToken, requestUri);
				NodeRevisionDescriptor revisionDescriptor = content.retrieve(
						slideToken, revisionDescriptors);

				// Check if resource must be checked in due to auto-versioning
				// semantics.
				ResourceKind resourceKind = AbstractResourceKind
						.determineResourceKind(token, requestUri,
								revisionDescriptor);
				if (Configuration.useVersionControl()
						&& (resourceKind instanceof CheckedOutVersionControlled)) {
					NodeProperty checkinLocktokenProperty = revisionDescriptor
							.getProperty(DeltavConstants.PN_CHECKIN_LOCKTOKEN);
					if (checkinLocktokenProperty == null) {
						// retry with default (DAV:) namespace which was the
						// former namespace of this property
						checkinLocktokenProperty = revisionDescriptor
								.getProperty(DeltavConstants.PN_CHECKIN_LOCKTOKEN_COMP);
					}
					if ((checkinLocktokenProperty != null)
							&& (checkinLocktokenProperty.getValue() != null)
							&& lockId.equals(checkinLocktokenProperty
									.getValue().toString())) {
						versioningHelper.checkin(revisionDescriptors,
								revisionDescriptor, false, false, true);
					}
				}

				// Checking if the resource at the URI isn't a lock-null
				// resource, in which case we must attempt to delete it
				ObjectNode node = structure.retrieve(slideToken, requestUri);

				if (isLockNull(revisionDescriptor)) {
					content.remove(slideToken, requestUri, revisionDescriptor);
					content.remove(slideToken, revisionDescriptors);
					structure.remove(slideToken, node);
				}

				resp.setStatus(WebdavStatus.SC_NO_CONTENT);

			} catch (PreconditionViolationException e) {
				try {
					sendPreconditionViolation(e);
					throw e;
				} catch (IOException x) {
				}
				throw new WebdavException(e.getStatusCode());
			} catch (Exception e) {
				int statusCode = getErrorCode(e);
				sendError(statusCode, e);
				throw new WebdavException(statusCode);
			}
		}
	}

	/**
	 * Get return status based on exception type.
	 */
	protected int getErrorCode(Exception ex) {
		try {
			throw ex;
		} catch (RevisionDescriptorNotFoundException e) {
			return WebdavStatus.SC_OK;
		} catch (LinkedObjectNotFoundException e) {
			return WebdavStatus.SC_NOT_FOUND;
		} catch (Exception e) {
			return super.getErrorCode(e);
		}
	}

	private void checkPreconditions() throws SlideException {
		Enumeration locksAll = lock.enumerateLocks(slideToken, resourcePath,
				true);
		if (!locksAll.hasMoreElements()) {
			return;
		}

		NodeLock nodeLock = findMatchingNodeLock(false);

		if (nodeLock != null) {
			if (!lock.checkLockOwner(slideToken, nodeLock)) {
				throw new PreconditionViolationException(
						new ViolatedPrecondition(
								"lock-owner-or-unlock-privilege",
								WebdavStatus.SC_FORBIDDEN, UNLOCK_NOT_ALLOWED),
						resourcePath);
			}
		} else {
			nodeLock = findMatchingNodeLock(true);
			if (nodeLock != null) {
				throw new PreconditionViolationException(
						new ViolatedPrecondition("lock-root",
								WebdavStatus.SC_CONFLICT, IS_NOT_LOCK_ROOT
										+ getFullPath(nodeLock.getObjectUri())),
						resourcePath);
			} else {
				throw new PreconditionViolationException(
						new ViolatedPrecondition("valid-lock-token",
								WebdavStatus.SC_CONFLICT, INVALID_LOCK_TOKEN),
						resourcePath);
			}
		}
	}

	private NodeLock findMatchingNodeLock(boolean inherited)
			throws ObjectNotFoundException, LockTokenNotFoundException,
			ServiceAccessException {
		NodeLock nodeLock = null;
		Enumeration locks = lock.enumerateLocks(slideToken, resourcePath,
				inherited);
		while (nodeLock == null && locks.hasMoreElements()) {
			NodeLock nl = (NodeLock) locks.nextElement();
			if (nl.getLockId().equals(lockId)) {
				nodeLock = nl;
			}
		}
		return nodeLock;
	}
}