/*
* $Header: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/UpdateMethod.java,v 1.32 2004/08/02 16:36:01 unico Exp $
* $Revision: 1.32 $
* $Date: 2004/08/02 16:36:01 $
*
* ====================================================================
*
* 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 java.util.List;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.NestedSlideException;
import org.apache.slide.common.PropertyParseException;
import org.apache.slide.common.RequestedProperties;
import org.apache.slide.common.RequestedPropertiesImpl;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
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.structure.ObjectNode;
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.LabeledRevisionNotFoundException;
import org.apache.slide.webdav.util.PreconditionViolationException;
import org.apache.slide.webdav.util.PropertyRetriever;
import org.apache.slide.webdav.util.PropertyRetrieverImpl;
import org.apache.slide.webdav.util.UriHandler;
import org.apache.slide.webdav.util.VersioningHelper;
import org.apache.slide.webdav.util.ViolatedPrecondition;
import org.apache.slide.webdav.util.WebdavStatus;
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.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.output.XMLOutputter;
/**
* UPDATE method.
*
*/
public class UpdateMethod extends AbstractMultistatusResponseMethod
implements DeltavConstants, WriteMethod {
/** The update target */
private String resourcePath;
/** The update source */
private String updateSourcePath;
/** The label of the update source */
private String updateLabelName;
/** Requested properties */
private RequestedProperties requestedProps;
/**
* The VersioningHelper used by this instance.
*/
protected VersioningHelper versioningHelper = null;
/**
* The URI of the server, e.g. <code>localhost:4000</code>.
*/
protected String serverUri = null;
/**
* The PropertyRetriever used to retrieve any requested properties.
*/
protected PropertyRetriever propertyRetriever = null;
// ----------------------------------------------------------- Constructors
/**
* Constructor.
*
* @param token the token for accessing the namespace
* @param config configuration of the WebDAV servlet
*/
public UpdateMethod(NamespaceAccessToken token,
WebdavServletConfig config) {
super(token, config);
}
/**
* Parse WebDAV XML query.
*
* @exception WebdavException
*/
protected void parseRequest() throws WebdavException {
versioningHelper = VersioningHelper.getVersioningHelper(
slideToken, token, req, resp, getConfig() );
// readRequestContent();
serverUri = req.getServerName() + ":" + req.getServerPort();
propertyRetriever = new PropertyRetrieverImpl(token, slideToken, getConfig());
resourcePath = requestUri;
if (resourcePath == null) {
resourcePath = "/";
}
if( req.getContentLength() == 0 ) {
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError( statusCode, getClass().getName()+".missingRequestBody" );
throw new WebdavException( statusCode );
}
try{
parseUpdateRequestContent();
}
catch (JDOMException e){
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
catch (PropertyParseException e){
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
catch( IOException e ){
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
}
/**
* Parses the expected request content specified for the Update method.
*
* @throws JDOMException if parsing the request failed for any reason.
* @throws IOException
* @throws PropertyParseException if parsing the property fails for any reason.
*/
private void parseUpdateRequestContent() throws IOException, JDOMException, PropertyParseException {
Element ve = null;
Iterator i = parseRequestContent(E_UPDATE).getChildren().iterator();
while( i.hasNext() ) {
Element e = (Element)i.next();
if( e.getName().equals(E_VERSION) ) {
if( updateSourcePath != null ) {
throw new JDOMException("At most one <"+E_VERSION+"> element allowed" );
}
if (updateLabelName != null) {
throw new JDOMException("Either a <"+E_VERSION+"> OR a <"+E_LABEL_NAME+"> element allowed");
}
// get the href element
ve = e;
try {
Element hre = (Element)ve.getChildren().get(0);
if( hre == null || !hre.getName().equals(E_HREF) )
throw new Exception();
updateSourcePath = getSlidePath( hre.getText() );
}
catch( Exception x ) {
throw new JDOMException("<"+E_VERSION+"> element must contain <"+E_HREF+"> element" );
}
}
if( e.getName().equals(E_PROP) ) {
if( requestedProps != null ) {
throw new JDOMException("At most one "+E_PROP+" element allowed" );
}
requestedProps = new RequestedPropertiesImpl( e );
}
if( e.getName().equals(E_LABEL_NAME) ) {
if (updateSourcePath != null) {
throw new JDOMException("Either a <"+E_VERSION+"> OR a <"+E_LABEL_NAME+"> element allowed");
}
if( updateLabelName != null ) {
throw new JDOMException("At most one <"+E_LABEL_NAME+"> element allowed" );
}
updateLabelName = e.getText();
}
}
}
/**
* Execute the request.
*
* @exception WebdavException
*/
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 );
}
Element multistatusElement = new Element(E_MULTISTATUS, DNSP);
try {
if ( WebdavEvent.UPDATE.isEnabled() ) EventDispatcher.getInstance().fireVetoableEvent(WebdavEvent.UPDATE, new WebdavEvent(this));
update(updateSourcePath, updateLabelName, resourcePath, getDepth(), multistatusElement);
} catch (NestedSlideException nestedSlideException) {
if (!requestHeaders.isDefined(H_DEPTH)) {
// do not send a 207 multistatus if the depth header is not set
SlideException exception = (SlideException)nestedSlideException.enumerateExceptions().nextElement();
resp.setStatus(getErrorCode(exception)); // special handling needed
if (exception instanceof PreconditionViolationException) {
try {
sendPreconditionViolation((PreconditionViolationException)exception);
} catch(IOException e) {
// Critical error ... Servlet container is dead or something
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
}
throw new WebdavException(getErrorCode(exception), false); // abort the TA
}
} catch (SlideException e) {
int statusCode = getErrorCode( e );
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
try {
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
resp.setContentType(TEXT_XML_UTF_8);
org.jdom.output.Format format = org.jdom.output.Format.getPrettyFormat();
format.setIndent(XML_RESPONSE_INDENT);
new XMLOutputter(format).
output(new Document(multistatusElement), resp.getWriter());
}
catch (Exception e) {
int statusCode = getErrorCode( e );
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
}
/**
* Updates the resource identified by <code>resourcePath</code>
* with the properties and the content either of the resource identified
* <code>updateSourcePath</code> or the version with the label
* <code>updateLabelName</code> (only one of these parameters is set).
* If <code>depth</code> is > 0, the operation is applied recursivly
* to all children of the destination resource.
*
* @param updateSourcePath the URI of update source.
* @param updateLabelName the label of the version used for the update.
* @param resourcePath the URI of update destination.
* @param depth the depth to use. If > 0, the update is
* applied recursivly.
* @param multistatusElement the <code><multistatus></code> element
* to append the <code><response></code>
* elements to.
*/
protected void update(String updateSourcePath, String updateLabelName, String resourcePath, int depth, Element multistatusElement) throws NestedSlideException {
NestedSlideException nestedSlideException = new NestedSlideException(null);
update(updateSourcePath, updateLabelName, resourcePath, depth, multistatusElement, nestedSlideException);
if ( ! nestedSlideException.isEmpty() ) {
throw nestedSlideException;
}
}
/**
* Updates the resource identified by <code>resourcePath</code>
* with the properties and the content either of the resource identified
* <code>updateSourcePath</code> or the version with the label
* <code>updateLabelName</code> (only one of these parameters is set).
* If <code>depth</code> is > 0, the operation is applied recursivly
* to all children of the destination resource.
*
* @param updateSourcePath the URI of update source.
* @param updateLabelName the label of the version used for the update.
* @param resourcePath the URI of update destination.
* @param depth the depth to use. If > 0, the update is
* applied recursivly.
* @param multistatusElement the <code><multistatus></code> element
* to append the <code><response></code>
* elements to.
*/
protected void update(String updateSourcePath, String updateLabelName, String resourcePath, int depth, Element multistatusElement, NestedSlideException nestedSlideException) {
Element responseElement = new Element(E_RESPONSE, DNSP);
multistatusElement.addContent(responseElement);
Element hrefElement = new Element(E_HREF, DNSP);
String absUri = WebdavUtils.getAbsolutePath (resourcePath, req, getConfig());
hrefElement.setText(absUri);
responseElement.addContent(hrefElement);
Element statusElement = new Element(E_STATUS, DNSP);
responseElement.addContent(statusElement);
Enumeration childrenEnum = null;
try {
if ( isCollection(resourcePath) && (depth > 0) ) {
ObjectNode currentNode = structure.retrieve(slideToken, resourcePath);
childrenEnum = structure.getChildren(slideToken, currentNode);
}
checkPreconditions(updateSourcePath, updateLabelName, resourcePath);
if (updateLabelName != null) {
updateSourcePath = versioningHelper.getLabeledResourceUri(resourcePath,
updateLabelName);
}
versioningHelper.update( resourcePath, updateSourcePath );
statusElement.setText(HTTP_VERSION + " " +
WebdavStatus.SC_OK + " " +
WebdavStatus.getStatusText(WebdavStatus.SC_OK));
appendRequestedProps(resourcePath, responseElement);
}
catch (SlideException e) {
handleException(e, statusElement, responseElement, nestedSlideException);
}
catch (JDOMException e) {
handleException(e, statusElement, responseElement, nestedSlideException);
}
// process children recursivly
if (childrenEnum != null) {
while (childrenEnum.hasMoreElements()) {
update(updateSourcePath,
updateLabelName,
((ObjectNode)childrenEnum.nextElement()).getUri(),
depth-1,
multistatusElement,
nestedSlideException);
}
}
}
/**
* Appends the <propstat> elements for the requested properties to
* the given <code>responseElement</code>.
*
* @param resourcePath the path of the resource for which to retrieve
* the properties.
* @param responseElement the <reponse> element to add the <propstat>
* elements to.
*
* @throws SlideException
* @throws JDOMException
*/
private void appendRequestedProps(String resourcePath, Element responseElement) throws SlideException, JDOMException {
if (requestedProps != null) {
// TOCHECK serverURI ???
// List propStatList = propertyRetriever.getPropertiesOfObject(requestedProps,
// resourcePath,
// req.getContextPath(),
// serverUri,
// true);
List propStatList = propertyRetriever.getPropertiesOfObject(requestedProps,
resourcePath,
getSlideContextPath(),
true);
Iterator iterator = propStatList.iterator();
while (iterator.hasNext()) {
responseElement.addContent((Element)iterator.next());
}
}
}
/**
* Checks the preconditions of the Update method.
*
* @param updateSourcePath the URI of update source.
* @param updateLabelName the label of the version used for the update.
* @param resourcePath the URI of update destination.
*
* @throws SlideException
* @throws PreconditionViolationException if any precondition has been violated.
*/
private void checkPreconditions(String updateSourcePath, String updateLabelName, String resourcePath) throws SlideException, PreconditionViolationException {
ViolatedPrecondition violatedPrecondition = getPreconditionViolation(updateSourcePath,
updateLabelName,
resourcePath);
if (violatedPrecondition != null) {
throw new PreconditionViolationException(violatedPrecondition, resourcePath);
}
}
/**
* Sets the appropriate status text and appends a <responsedescription>
* element if a precondition has been violated.
*
* @param exception the JDOMException that occurred.
* @param statusElement the <status> element.
* @param responseElement the <response> element.
* @param nestedSlideException the NestedSlideException to add the exception to.
*/
private void handleException(JDOMException exception, Element statusElement, Element responseElement, NestedSlideException nestedSlideException) {
handleException(new SlideException("Nested exception: " + exception),
statusElement,
responseElement,
nestedSlideException);
}
/**
* Sets the appropriate status text and appends a <responsedescription>
* element if a precondition has been violated.
*
* @param exception the SlideException that occurred.
* @param statusElement the <status> element.
* @param responseElement the <response> element.
* @param nestedSlideException the NestedSlideException to add the exception to.
*/
private void handleException(SlideException exception, Element statusElement, Element responseElement, NestedSlideException nestedSlideException) {
nestedSlideException.addException(exception);
int errorCode = getErrorCode(exception);
statusElement.setText(HTTP_VERSION + " " + errorCode + " " +
WebdavStatus.getStatusText(errorCode));
if (exception instanceof PreconditionViolationException) {
Element responseDescriptionElement = new Element(E_RESPONSEDESCRIPTION,
DNSP);
responseElement.addContent(responseDescriptionElement);
Element errorElement = MethodUtil.getPreconditionViolationError(((PreconditionViolationException)exception).getViolatedPrecondition());
responseDescriptionElement.addContent(errorElement);
}
}
/**
* Checks the (DeltaV) preconditions
* <ul>
* <li><DAV:must-be-checked-in-version-controlled-resource></li>
* <li><DAV:must-select-version-in-history></li>
* </ul>
*
* @param updateSourcePath the URI of update source.
* @param updateLabelName the label of the version used for the update.
* @param resourcePath the URI of update destination.
*
* @return the precondition that has been violated (if any).
*
* @throws SlideException
*/
protected ViolatedPrecondition getPreconditionViolation(String updateSourcePath, String updateSourceLabel, String resourcePath) throws SlideException {
ViolatedPrecondition violatedPrecondition = null;
NodeRevisionDescriptors revisionDescriptors =
content.retrieve(slideToken, resourcePath);
NodeRevisionDescriptor revisionDescriptor =
content.retrieve(slideToken, revisionDescriptors);
ResourceKind resourceKind = AbstractResourceKind.determineResourceKind(token,
revisionDescriptors,
revisionDescriptor);
if ( ! (resourceKind instanceof CheckedInVersionControlled) ) {
return new ViolatedPrecondition(DeltavConstants.C_MUST_BE_CHECKED_IN_VERSION_CONTROLLED_RESOURCE,
WebdavStatus.SC_CONFLICT);
}
if (updateSourceLabel != null) {
try {
updateSourcePath = versioningHelper.getLabeledResourceUri(resourcePath, updateLabelName);
}
catch (LabeledRevisionNotFoundException e) {
return new ViolatedPrecondition(DeltavConstants.C_MUST_SELECT_VERSION_IN_HISTORY,
WebdavStatus.SC_CONFLICT);
}
}
String associatedVrUri = versioningHelper.getUriOfAssociatedVR(resourcePath);
String vcrHistoryUri = UriHandler.getUriHandler(associatedVrUri).getAssociatedHistoryUri();
UriHandler vrUriHandler = UriHandler.getUriHandler(updateSourcePath);
boolean isVersionOfVcrHistory = false;
if (vrUriHandler.isVersionUri() &&
vcrHistoryUri.equals(vrUriHandler.getAssociatedHistoryUri()) ) {
NodeRevisionDescriptors vrDescriptors =
content.retrieve(slideToken, updateSourcePath);
try {
content.retrieve(slideToken, vrDescriptors);
isVersionOfVcrHistory = true;
}
catch (RevisionDescriptorNotFoundException e) {
}
}
if ( ! isVersionOfVcrHistory ) {
return new ViolatedPrecondition(DeltavConstants.C_MUST_SELECT_VERSION_IN_HISTORY,
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.
*/
protected int getDepth() throws WebdavException {
return requestHeaders.getDepth(0);
}
}