/*
* $Header: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/LabelMethod.java,v 1.21.2.1 2004/02/05 16:11:23 mholz Exp $
* $Revision: 1.21.2.1 $
* $Date: 2004/02/05 16:11:23 $
*
* ====================================================================
*
* 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 java.util.Iterator;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.NestedSlideException;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.common.SlideToken;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.util.XMLValue;
import org.apache.slide.webdav.WebdavException;
import org.apache.slide.webdav.WebdavServletConfig;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.LabeledRevisionNotFoundException;
import org.apache.slide.webdav.util.PreconditionViolationException;
import org.apache.slide.webdav.util.PropertyHelper;
import org.apache.slide.webdav.util.VersioningHelper;
import org.apache.slide.webdav.util.ViolatedPrecondition;
import org.apache.slide.webdav.util.WebdavUtils;
import org.apache.slide.webdav.util.resourcekind.AbstractResourceKind;
import org.apache.slide.webdav.util.resourcekind.CheckedInVersionControlled;
import org.apache.slide.webdav.util.resourcekind.ResourceKind;
import org.apache.slide.webdav.util.resourcekind.Version;
import org.apache.slide.webdav.util.resourcekind.VersionControlled;
import org.apache.util.WebdavStatus;
import org.jdom.Element;
import org.jdom.JDOMException;
/**
* LABEL method.
*
* @version $Revision: 1.21.2.1 $
*
* @author <a href="mailto:ralf.stuckert@softwareag.com">Ralf Stuckert</a>
*/
public class LabelMethod extends AbstractMultistatusResponseMethod implements DeltavConstants {
/**
** String constant for <code>Label missing</code>.
**/
public static final String LABEL_MISSING = "Label missing";
/**
* String constant for <code>Request content <label> element must
* contain either <add>, <set> or <remove></code>.
*/
public static final String LABEL_MUST_CONTAIN_EITHER_ADD_SET_OR_REMOVE =
"Request content <label> element must contain either <" +
E_ADD + ">, <" + E_SET + "> or <" + E_REMOVE + ">";
/**
* Resource to be written.
*/
private String resourcePath;
/**
* Indicates if the resource to delete is a collection.
*/
protected boolean isCollection = false;
/**
* The VersioningHelper used by this instance.
*/
protected VersioningHelper versioningHelper = null;
/**
* Indicates which label operation to perform:
* <code>add</code>, <code>set</code> or <code>remove</code>.
*/
protected String operation = null;
/**
* The label to add/set/remove.
*/
protected String label = null;
/**
* The value of the <code>Label</code> header
*/
protected String labelHeader = null;
// ----------------------------------------------------------- Constructors
/**
* Constructor.
*
* @param token the token for accessing the namespace
* @param config configuration of the WebDAV servlet
*/
public LabelMethod(NamespaceAccessToken token,
WebdavServletConfig config) {
super(token, config);
}
/**
* Parse WebDAV XML query.
*
* @throws WebdavException
*/
protected void parseRequest() throws WebdavException {
// readRequestContent();
versioningHelper = VersioningHelper.getVersioningHelper(slideToken,
token,
req,
resp,
config);
resourcePath = requestUri;
if (resourcePath == null) {
resourcePath = "/";
}
labelHeader = WebdavUtils.fixTomcatHeader(requestHeaders.getLabel(), "UTF-8");
try{
Element root = parseRequestContent(DeltavConstants.E_LABEL);
Element current = root.getChild(DeltavConstants.E_ADD, DNSP);
Element operationElement = null;
if (current != null) {
operationElement = current;
}
current = root.getChild(DeltavConstants.E_SET, DNSP);
if ( current != null) {
if (operationElement != null) {
throw new JDOMException(LABEL_MUST_CONTAIN_EITHER_ADD_SET_OR_REMOVE);
}
operationElement = current;
}
current = root.getChild(DeltavConstants.E_REMOVE, DNSP);
if ( current != null) {
if (operationElement != null) {
throw new JDOMException(LABEL_MUST_CONTAIN_EITHER_ADD_SET_OR_REMOVE);
}
operationElement = current;
}
if (operationElement == null) {
throw new JDOMException(LABEL_MUST_CONTAIN_EITHER_ADD_SET_OR_REMOVE);
}
operation = operationElement.getName();
Element labelName = operationElement.getChild(DeltavConstants.E_LABEL_NAME, DNSP);
if ( (labelName == null) ||
(labelName.getText() == null) ||
(labelName.getText().length() == 0) ) {
throw new JDOMException(LABEL_MISSING);
}
label = labelName.getText();
}
catch (IOException e){
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
catch (JDOMException e){
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
}
/**
* Execute the request.
*
* @throws WebdavException
* @throws IOException
*/
protected void executeRequest() throws WebdavException, IOException {
// Prevent dirty reads
slideToken.setForceStoreEnlistment(true);
// check lock-null resources
try {
if (isLockNull(resourcePath)) {
int statusCode = WebdavStatus.SC_NOT_FOUND;
sendError( statusCode, "lock-null resource", new Object[]{resourcePath} );
throw new WebdavException( statusCode );
}
}
catch (ServiceAccessException e) {
int statusCode = getErrorCode((Exception)e);
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
isCollection = isCollection(resourcePath);
try {
labelResource(resourcePath);
}
catch (NestedSlideException nestedSlideException) {
// If it's not a collection, we don't want to give a 207,
// because it's silly, and it confuses many clients (such as
// MS Web Folders).
if (generateMultiStatusResponse(isCollection, nestedSlideException, requestUri)) {
String errorMessage = generateErrorMessage(nestedSlideException);
// Write it on the servlet writer
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
try {
resp.setContentType(TEXT_XML_UTF_8);
resp.getWriter().write(errorMessage);
} catch(IOException ex) {
// Critical error ... Servlet container is dead or something
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError( statusCode, ex );
throw new WebdavException( statusCode );
}
} else {
// Returning 207 on non-collection requests is generally
// considered bad. So let's not do it, since this way
// makes clients generally behave better.
SlideException exception = (SlideException)nestedSlideException.enumerateExceptions().nextElement();
if (exception instanceof PreconditionViolationException) {
try {
sendPreconditionViolation((PreconditionViolationException)exception);
} catch(IOException ex) {
// Critical error ... Servlet container is dead or something
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError( statusCode, ex );
throw new WebdavException( statusCode );
}
}
else {
int statusCode = getErrorCode( exception );
sendError( statusCode, exception );
throw new WebdavException( statusCode );
}
}
//
// make sure the transaction is aborted
// throw any WebDAV exception to indicate the transaction wants to be aborted
//
throw new WebdavException(WebdavStatus.SC_ACCEPTED, false);
}
finally {
resp.setHeader(H_CACHE_CONTROL, NO_CACHE);
}
}
/**
* Labels the reource identified by the given <code>resourcePath</code>.
* If the resource is a collection and a <code>Depth</code> header is specified
* than the request is applied recursivly. If an attempt to label a resource
* fails, the corresponding Exception is contained in the thrown
* NestedSlideException.
*
* @param resourcePath the path of the resource to label.
*
* @throws NestedSlideException, if an attempt to label a resource fails.
*/
protected void labelResource(String resourcePath) throws NestedSlideException {
NestedSlideException nestedSlideException = new NestedSlideException(null);
try {
labelResource(resourcePath, getDepth(), nestedSlideException);
}
catch (WebdavException e) {
nestedSlideException.addException(e);
}
if ( ! nestedSlideException.isEmpty() ) {
throw nestedSlideException;
}
}
/**
* Labels the reource identified by the given <code>resourcePath</code>.
* If the resource is a collection and the <code>depth</code> > 0
* than the request is applied recursivly. If an attempt to label a resource
* fails, the corresponding Exception will be added to the given
* <code>nestedSlideException</code>.
*
* @param resourcePath the path of the resource to label.
* @param depth the depth to use if the resource is a collection.
* @param nestedSlideException the NestedSlideException to add all occurring
* Exceptions to.
*/
protected void labelResource(String resourcePath, int depth, NestedSlideException nestedSlideException) {
try {
if ( ! isCollection(resourcePath) ) {
ViolatedPrecondition violatedPrecondition = getPreconditionViolation(resourcePath);
if (violatedPrecondition != null) {
throw new PreconditionViolationException(violatedPrecondition, resourcePath);
}
performLabelOperation(resourcePath);
}
else if (depth > 0) {
// process children recursivly
ObjectNode currentNode = structure.retrieve(slideToken, resourcePath);
Enumeration childrenEnum = structure.getChildren(slideToken, currentNode);
if (childrenEnum != null) {
while (childrenEnum.hasMoreElements()) {
labelResource(((ObjectNode)childrenEnum.nextElement()).getUri(), depth-1, nestedSlideException);
}
}
}
}
catch (SlideException e) {
nestedSlideException.addException(e);
}
catch (JDOMException e) {
nestedSlideException.addException(new SlideException(e.getMessage()));
}
}
/**
* Perform the LABEL operation means it either adds, sets or removes the label.
*
* @param resourcePath the resource to add/set/remove the label.
*
* @throws JDOMException
* @throws SlideException
*/
private void performLabelOperation(String resourcePath) throws JDOMException, SlideException {
String labelHeader = WebdavUtils.fixTomcatHeader(requestHeaders.getLabel(), "UTF-8");
String labeledResourceUri = getResourceUri(resourcePath, labelHeader);
NodeRevisionDescriptors revisionDescriptors =
content.retrieve( slideToken, labeledResourceUri);
NodeRevisionDescriptor revisionDescriptor =
content.retrieve( slideToken, revisionDescriptors);
ResourceKind resourceKind = AbstractResourceKind.determineResourceKind(token, revisionDescriptors, revisionDescriptor);
if (resourceKind instanceof Version) {
if (DeltavConstants.E_REMOVE.equals(operation)) {
PropertyHelper.removeElementFromProperty(revisionDescriptor,
P_LABEL_NAME_SET,
E_LABEL_NAME,
label);
content.store(slideToken, revisionDescriptors.getUri(), revisionDescriptor, null);
}
if (DeltavConstants.E_SET.equals(operation)) {
try {
NodeRevisionDescriptor alreadyLabeledDescriptor =
versioningHelper.retrieveLabeledRevision(revisionDescriptors.getUri(),
label);
PropertyHelper.removeElementFromProperty(alreadyLabeledDescriptor,
P_LABEL_NAME_SET,
E_LABEL_NAME,
label);
content.store(slideToken, revisionDescriptors.getUri(), alreadyLabeledDescriptor, null);
}
catch (LabeledRevisionNotFoundException e) {
// there is no version with the given label so far,
// so we can perform the <set> operation without any preparation
}
}
if (DeltavConstants.E_ADD.equals(operation) ||
DeltavConstants.E_SET.equals(operation) ) {
PropertyHelper.addElementToProperty(revisionDescriptor,
P_LABEL_NAME_SET,
E_LABEL_NAME,
label);
content.store(slideToken, revisionDescriptors.getUri(), revisionDescriptor, null);
}
}
}
/**
* Checks the (DeltaV) preconditions
* <ul>
* <li><DAV:must-be-checked-in></li>
* <li><DAV:must-select-version-in-history></li>
* <li><DAV:must-be-new-label></li>
* <li><DAV:label-must-exist></li>
* </ul>
*
* @param resourcePath the URI of the resource.
*
* @return the precondition that has been violated (if any).
*
* @throws SlideException
*/
protected ViolatedPrecondition getPreconditionViolation(String resourcePath) throws SlideException {
// use a non-blocking slide token.
SlideToken stoken = readonlySlideToken();
ViolatedPrecondition violatedPrecondition = null;
NodeRevisionDescriptors revisionDescriptors =
content.retrieve( stoken, resourcePath);
NodeRevisionDescriptor revisionDescriptor =
content.retrieve( stoken, revisionDescriptors);
ResourceKind resourceKind = AbstractResourceKind.determineResourceKind(token, revisionDescriptors, revisionDescriptor);
// check <DAV:must-be-checked-in>
if ( (resourceKind instanceof VersionControlled) &&
!(resourceKind instanceof CheckedInVersionControlled) ) {
return new ViolatedPrecondition(DeltavConstants.C_MUST_BE_CHECKED_IN,
WebdavStatus.SC_CONFLICT);
}
// check <DAV:must-select-version-in-history>
if ( (resourceKind instanceof VersionControlled) &&
(labelHeader != null) ) {
try {
versioningHelper.getLabeledResourceUri(resourcePath, labelHeader);
}
catch (LabeledRevisionNotFoundException e) {
return new ViolatedPrecondition(DeltavConstants.C_MUST_SELECT_VERSION_IN_HISTORY,
WebdavStatus.SC_CONFLICT);
}
}
try {
String slideResourceUri = getResourceUri(resourcePath, labelHeader);
revisionDescriptors =
content.retrieve( stoken, slideResourceUri);
revisionDescriptor =
content.retrieve( stoken, revisionDescriptors);
resourceKind = AbstractResourceKind.determineResourceKind(token, revisionDescriptors, revisionDescriptor);
if (resourceKind instanceof Version) {
// check <DAV:label-must-exist>
if (DeltavConstants.E_REMOVE.equals(operation)) {
if ( ! hasLabel(revisionDescriptor, label) ) {
return new ViolatedPrecondition(DeltavConstants.C_LABEL_MUST_EXIST,
WebdavStatus.SC_CONFLICT);
}
}
try {
versioningHelper.retrieveLabeledRevision(revisionDescriptors.getUri(), label);
// check <DAV:must-be-new-label>
if (DeltavConstants.E_ADD.equals(operation)) {
return new ViolatedPrecondition(DeltavConstants.C_MUST_BE_NEW_LABEL,
WebdavStatus.SC_CONFLICT);
}
}
catch (LabeledRevisionNotFoundException e) {}
}
}
catch (LabeledRevisionNotFoundException e) {
// check <DAV:label-must-exist>
if (DeltavConstants.E_REMOVE.equals(operation)) {
return new ViolatedPrecondition(DeltavConstants.C_LABEL_MUST_EXIST,
WebdavStatus.SC_CONFLICT);
}
}
return violatedPrecondition;
}
/**
* Returns the value of the <code>Depth</code> header. If not specified,
* <code>0</code> is used as default.
*
* @return the value of the <code>Depth</code> header.
*/
private int getDepth() throws WebdavException {
return requestHeaders.getDepth(0);
}
/**
* Returns <code>true</code> if the given <code>revisionDescriptor</code>
* has a <code><label-name-set></code> property that contains a
* <code><label-name></code> element with the given <code>label</code>.
*
* @param revisionDescriptor the NodeRevisionDescriptor to check.
* @param label the label to look for.
*
* @return <code>true</code>, if the label was found.
*/
protected boolean hasLabel(NodeRevisionDescriptor revisionDescriptor, String label) {
boolean containsLabel = false;
NodeProperty labelNameSetProperty = revisionDescriptor.getProperty(DeltavConstants.P_LABEL_NAME_SET);
if ( (labelNameSetProperty != null) && (labelNameSetProperty.getValue() != null) ) {
try {
XMLValue xmlValue = new XMLValue(labelNameSetProperty.getValue().toString());
Iterator iterator = xmlValue.iterator();
while ( !containsLabel && iterator.hasNext()) {
containsLabel = label.equals(((Element)iterator.next()).getText());
}
}
catch (JDOMException e) {}
catch (IllegalArgumentException e) {}
}
return containsLabel;
}
/**
* Returns the Uri of the resource identified by the given <code>resourcePath</code>
* and the given <code>label</code>. If the <code>label</code> is <code>null</code>
* and the resource is a VCR, the associated VR is returned.
*
* @param resourcePath the path of the resource.
* @param label the label (may be <code>null</code>).
*
* @return the Uri of the resource identified by the given <code>resourcePath</code>
* and the given <code>label</code>.
*
* @throws SlideException
*/
protected String getResourceUri(String resourcePath, String label) throws SlideException {
String labeledResourceUri = versioningHelper.getLabeledResourceUri(resourcePath,
label);
NodeRevisionDescriptors revisionDescriptors =
content.retrieve( slideToken,labeledResourceUri);
NodeRevisionDescriptor revisionDescriptor =
content.retrieve( slideToken, revisionDescriptors);
ResourceKind resourceKind = AbstractResourceKind.determineResourceKind(token, revisionDescriptors, revisionDescriptor);
if (resourceKind instanceof VersionControlled) {
labeledResourceUri = versioningHelper.getUriOfAssociatedVR(resourcePath);
}
return labeledResourceUri;
}
}