/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/PropPatchMethod.java,v 1.93 2005/02/25 17:03:52 luetzkendorf Exp $
 * $Revision: 1.93 $
 * $Date: 2005/02/25 17:03:52 $
 *
 * ====================================================================
 *
 * 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.io.Writer;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.slide.common.Domain;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.PropertyParseException;
import org.apache.slide.common.RequestedPropertiesImpl;
import org.apache.slide.common.RequestedProperty;
import org.apache.slide.common.RequestedPropertyImpl;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.NodeProperty.NamespaceCache;
import org.apache.slide.event.EventDispatcher;
import org.apache.slide.structure.LinkedObjectNotFoundException;
import org.apache.slide.util.Configuration;
import org.apache.slide.util.XMLValue;
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.AclConstants;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.PreconditionViolationException;
import org.apache.slide.webdav.util.PropertyHelper;
import org.apache.slide.webdav.util.UriHandler;
import org.apache.slide.webdav.util.ViolatedPrecondition;
import org.apache.slide.webdav.util.WebdavStatus;
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.ResourceKindManager;
import org.jdom.CDATA;
import org.jdom.Comment;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.EntityRef;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.ProcessingInstruction;
import org.jdom.Text;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

/**
 * PROPPATCH method.
 *
 */
public class PropPatchMethod extends AbstractWebdavMethod implements DeltavConstants, AclConstants,
        WriteMethod, FineGrainedLockingMethod {
    
    
    // -------------------------------------------------------------- Constants
    
       
    // ----------------------------------------------------- Instance Variables
    
    
    protected PropertyHelper propertyHelper = null;
    
    /**
     * Properties to set.
     */
    private PropPatchProperties propertiesToSet;
    
    /**
     * Properties to remove.
     */
    private PropPatchProperties propertiesToRemove;
    
    /**
     * Resource which will have its properties updated.
     */
    private String resourcePath;
    
    /**
     * If true, the last modified date will be updated.
     */
    private boolean updateLastModified;
    
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Constructor.
     *
     * @param token     the token for accessing the namespace
     * @param config    configuration of the WebDAV servlet
     */
    public PropPatchMethod(NamespaceAccessToken token,
                           WebdavServletConfig config) {
        super(token, config);
    }
    
    /**
     * @see org.apache.slide.webdav.method.FineGrainedLockingMethod#acquireFineGrainLocks()
     */
    public void acquireFineGrainLocks() {
        acquireStandardLocks(resourcePath);
        // lock history folder in case we have auto versioning turned on
        acquireHistoryLocks(resourcePath);
        // changes this and parent
        acquireLock(resourcePath, WRITE_LOCK);
        acquireParentLock(resourcePath, WRITE_LOCK);
    }

    
    // ------------------------------------------------------ Protected Methods
    
    
    /**
     * Parse the request.
     *
     * @exception WebdavException Bad request
     */
    protected void parseRequest()
        throws WebdavException {
        
        propertyHelper =
            PropertyHelper.getPropertyHelper( slideToken, token, getConfig() );
        
//        readRequestContent();
        
        updateLastModified = getBooleanInitParameter( "updateLastModified" );
        
        resourcePath = requestUri;
        if (resourcePath == null) {
            resourcePath = "/";
        }
        propertiesToSet = new PropPatchProperties();
        propertiesToRemove = new PropPatchProperties();
        
        if (req.getContentLength() != 0) {
            
            try {
                Iterator childrenIterator = parseRequestContent(E_PROPERTYUPDATE).getChildren().iterator();
                Element child = null;
                while (childrenIterator.hasNext()) {
                    child = (Element)childrenIterator.next();
                    if (E_SET.equals(child.getName())) {
                        propertiesToSet.add(getPropElement(child));
                    }
                    else if (E_REMOVE.equals(child.getName())) {
                        propertiesToRemove.add(getPropElement(child));
                    }
                    else {
                        throw new JDOMException("Expected <"+E_SET+"> or <"+E_REMOVE+"> element");
                    }
                }
                
                
            }
            catch( Exception e ) {
                int statusCode = getErrorCode( e );
                sendError( statusCode, e );
                throw new WebdavException( statusCode );
            }
        }
        else {
            int statusCode = WebdavStatus.SC_BAD_REQUEST;
            sendError( statusCode, getClass().getName()+".missingRequestBody" );
            throw new WebdavException( statusCode );
        }
        
    }
    
    /**
     * Checks if the given Element has exactly one child named
     * <code>&lt;prop&gt;</code>. If the check succeeds the
     * <code>&lt;prop&gt;</code> element is returned, otherwise a
     * JDOMException is thrown.
     *
     * @param      parent  the parent Element of the <code>&lt;prop&gt;</code>.
     *
     * @return     the <code>&lt;prop&gt;</code> element.
     *
     * @throws     JDOMException  if the check fails.
     */
    private Element getPropElement(Element parent) throws JDOMException {
        List childrenList = parent.getChildren();
        if ( (childrenList.size() != 1) ||
                ( ! E_PROP.equals(((Element)childrenList.get(0)).getName()) ) ) {
            throw new JDOMException("Expected &lt;"+E_PROP+"&gt; element");
        }
        return (Element)childrenList.get(0);
    }
    
    
    /**
     * Execute the request.
     *
     * @exception WebdavException
     */
    protected void executeRequest()
        throws WebdavException, IOException {
        
        boolean allOperationsExcecuted = true;
        
        // 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 );
        }

        try {
            if ( WebdavEvent.PROPPATCH.isEnabled() ) EventDispatcher.getInstance().fireVetoableEvent(WebdavEvent.PROPPATCH, new WebdavEvent(this));
            
            NodeRevisionDescriptors revisionDescriptors =
                content.retrieve(slideToken, resourcePath);
            
            NodeRevisionNumber revisionNumber =
                revisionDescriptors.getLatestRevision();
            NodeRevisionDescriptor revisionDescriptor = null;
            if (revisionNumber != null) {
                try {
                    revisionDescriptor = content.retrieve
                        (slideToken, revisionDescriptors);
                } catch (RevisionDescriptorNotFoundException e) {
                }
            }
            
            if (revisionDescriptor == null) {
                revisionDescriptor = new NodeRevisionDescriptor(0);
            }
            
            ResourceKind resourceKind = AbstractResourceKind.determineResourceKind(token, resourcePath, revisionDescriptor);
            
            // check preconditions
            ViolatedPrecondition violatedPrecondition = getPreconditionViolation(revisionDescriptors, revisionDescriptor, resourceKind);
            if (violatedPrecondition != null) {
                throw new PreconditionViolationException(violatedPrecondition, resourcePath);
            }
            
            // Changed for DeltaV --start--
            boolean mustCheckIn = false;
            if(Configuration.useVersionControl() &&
                (resourceKind instanceof CheckedInVersionControlled) &&
                !notVersionForPropPatch(token.getUri(slideToken, resourcePath)) &&
                versioningHelper.mustCheckoutAutoVersionedVCR(revisionDescriptors, revisionDescriptor) ) {
                
                versioningHelper.checkout(revisionDescriptors,
                        revisionDescriptor, false, false, true);
                mustCheckIn = versioningHelper.mustCheckinAutoVersionedVCR(
                        slideToken, revisionDescriptors, revisionDescriptor);
            }
            
            // Modifying the properties
            
            Iterator propertyIterator = null;
            
            propertyIterator = propertiesToSet.iterator();
            PropPatchProperty currentProperty = null;
            while (propertyIterator.hasNext()) {
                
                currentProperty = (PropPatchProperty)propertyIterator.next();
                if (checkPropertyModification(currentProperty, revisionDescriptor, resourceKind)) {
                    // Convert absolute URIs to relative ones, because Slide
                    // converts them to absolute ones in the result of queries.
                    String finalValue = currentProperty.getValue();
                    Element property = new Element(currentProperty.getName(), currentProperty.getNamespace());
                    String propertyValue = currentProperty.getValue();
                    if ( (propertyValue != null) && (propertyValue.toString().length() > 0)) {
                        if( propertyValue.toString().indexOf('<') >= 0 ) {
                            try {
                                XMLValue xmlValue = new XMLValue(propertyValue.toString(), Namespace.getNamespace(currentProperty.getNamespace()));
                                if (resourceKind.isSupportedLiveProperty(currentProperty.getPropertyName())) {
                                    convertHrefValueToRelativeURL(xmlValue, getSlideContextPath(), config);
                                }
                                Iterator iterator = xmlValue.iterator();
                                while (iterator.hasNext()) {
                                    Object o = iterator.next();
                                    if( o instanceof Element )
                                        property.addContent((Element)o);
                                    else if( o instanceof Text )
                                        property.addContent((Text)o);
                                    else if( o instanceof Comment )
                                        property.addContent((Comment)o);
                                    else if( o instanceof ProcessingInstruction )
                                        property.addContent((ProcessingInstruction)o);
                                    else if( o instanceof CDATA )
                                        property.addContent((CDATA)o);
                                    else if( o instanceof EntityRef )
                                        property.addContent((EntityRef)o);
                                }
                                finalValue = new XMLOutputter(Format.getRawFormat()).outputString(property.getContent());
                            }
                            catch (JDOMException e) {
                                // Fallback to original value
                            }
                        }
                    }
                    NodeProperty newProperty = new NodeProperty(
                            currentProperty.getPropertyName(), finalValue);
                    revisionDescriptor.setProperty(newProperty);
                    
                }
                else {
                    allOperationsExcecuted = false;
                }
            }
            
            propertyIterator = propertiesToRemove.iterator();
            while (propertyIterator.hasNext()) {
                
                currentProperty = (PropPatchProperty)propertyIterator.next();
                if (checkPropertyModification(currentProperty, revisionDescriptor, resourceKind)) {
                    revisionDescriptor.removeProperty(currentProperty.getPropertyName());
                }
                else {
                    allOperationsExcecuted = false;
                }
                
            }
            
            if (updateLastModified) {
                revisionDescriptor.setLastModified(new Date());
            }
            
            if (allOperationsExcecuted) {
                content.store(slideToken, resourcePath, revisionDescriptor, null);
            }
            
            // Changed for DeltaV --start--
            if( Configuration.useVersionControl() && mustCheckIn) {
                versioningHelper.checkin(revisionDescriptors, revisionDescriptor, 
                        false, false, true ); //forkOk=false, keepCheckedOut=false
            }
            // Changed for DeltaV --end--
            
            resp.setContentType(TEXT_XML_UTF_8);
            
            resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
            
        }
        catch (PreconditionViolationException e) {
            sendPreconditionViolation(e);
            throw e;
        }
        catch (Exception e) {
            int statusCode = getErrorCode( e );
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        }
        
        
        // No serious errors. Printing the XML report.
        writeReport();
        
        if (!allOperationsExcecuted) {
            throw new WebdavException( WebdavStatus.SC_ACCEPTED ); // abort the TA
        }
        
    }
    
    
    
    
    /**
     * Get return status based on exception type.
     */
    protected int getErrorCode(Exception ex) {
        try {
            throw ex;
        } catch (LinkedObjectNotFoundException e) {
            return WebdavStatus.SC_NOT_FOUND;
        } catch (PropertyParseException e) {
            return WebdavStatus.SC_BAD_REQUEST;
        } catch (JDOMException e) {
            return WebdavStatus.SC_BAD_REQUEST;
        } catch (Exception e) {
            return super.getErrorCode(e);
        }
    }
    
    
    
    // -------------------------------------------------------- Private Methods
    
    
    
    
    
    /**
     * Check if the property is a live property which should have its value
     * enforced by the server.
     *
     * @param property The property object
     * @param rd the revision descriptor containing all properties
     * @param resourceKind  the kind of the resource to patch.
     * @return <code>true</code> if the property may be modified
     */
    private boolean checkPropertyModification(PropPatchProperty property, 
            NodeRevisionDescriptor rd, ResourceKind resourceKind) {
        
        ViolatedPrecondition violatedPrecondition = 
            getPropertySpecificPreconditionViolation(resourceKind, property);
        if (violatedPrecondition != null) {
            property.setViolatedPrecondition(violatedPrecondition);
            return false;
        } else {
            if (!isSupportedPropertyValue(property, resourceKind)) {
                property.setErrorMessage("Value " + property.getValue() + 
                        " is not supported by property " + property.getQualifiedNameAsElementString());
                
                property.setStatusCode(WebdavStatus.SC_CONFLICT);
                return false;
            }
            
            return true;
        }
    }

    /**
     * Return <code>true</code> if the given <code>property</code> value is supported
     * for that property of the <code>resourceKind</code>
     *
     * @param      property      the property to check.
     * @param      resourceKind  the ResourceKind.
     *
     * @return     <code>true</code> if the value is supported.
     */
    private boolean isSupportedPropertyValue(PropPatchProperty property, ResourceKind resourceKind) {
        
        if (property.getValue() != null) {
            return resourceKind.isSupportedPropertyValue(
                    property.getPropertyName(), property.getValue());
        }
        return true;
    }
    
    
    
    /**
     * Write the report.
     */
    private void writeReport()
        throws WebdavException {
        
        Element multistatus = new Element(E_MULTISTATUS, DNSP);
        Element response = new Element(E_RESPONSE, DNSP);
        multistatus.addContent(response);
        Element href = new Element(E_HREF, DNSP);
        href.setText(getFullPath(requestUri));
        response.addContent(href);
        
        // Parse the two properties list, and printout their status
        addPropstatElements(propertiesToSet, response);
        addPropstatElements(propertiesToRemove, response);
        
        try {
            resp.setContentType(TEXT_XML_UTF_8);
            Writer writer = resp.getWriter();
            org.jdom.output.Format format = org.jdom.output.Format.getPrettyFormat();
            format.setIndent(XML_RESPONSE_INDENT);
            new XMLOutputter(format).
                output(new Document(multistatus), writer);
            writer.flush();
        } catch (Exception e) {
            int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        }
        
    }
    
    /**
     * Adds the <code>&lt;propstat&gt;</code> elements for the given
     * <code>properties</code> to the given <code>&lt;response&gt;</code>
     * element.
     *
     * @param      properties  the PropPatchProperties for which to add the
     *                         <code>&lt;propstat&gt;</code> elements.
     * @param      response    the <code>&lt;response&gt;</code> element to add
     *                         the <code>&lt;propstat&gt;</code> elements to.
     */
    private void addPropstatElements(PropPatchProperties properties, Element response) {
        
        Iterator propertyIterator = properties.iterator();
        while(propertyIterator.hasNext()) {
            PropPatchProperty property = (PropPatchProperty) propertyIterator.next();
            Element propstat = createPropstatElement(property);
            response.addContent(propstat);
            if (property.getViolatedPrecondition() != null) {
                Element responseDescription = new Element(E_RESPONSEDESCRIPTION, DNSP);
                responseDescription.addContent(MethodUtil.getPreconditionViolationError(property.getViolatedPrecondition()));
                propstat.addContent(responseDescription);
            }
            else if (property.getErrorMessage() != null) {
                Element responseDescription = new Element(E_RESPONSEDESCRIPTION, DNSP);
                responseDescription.addContent(property.getErrorMessage());
                propstat.addContent(responseDescription);
            }
        }
    }
    
    /**
     * Creates a <code>&lt;propstat&gt;</code> element for the given
     * <code>property</code>.
     *
     * @param      property  the PropPatchProperty for which to create a
     *                       <code>&lt;propstat&gt;</code> element.
     *
     * @return     the created <code>&lt;propstat&gt;</code> element.
     */
    private Element createPropstatElement(PropPatchProperty property) {
        
        Element propstat = new Element(E_PROPSTAT, DNSP);
        Element prop = new Element(E_PROP, DNSP);
        propstat.addContent(prop);
        
        Namespace namespace = Namespace.NO_NAMESPACE;
        if (property.getNamespace() != null) {
            namespace = NamespaceCache.getNamespace(property.getNamespace());
        }
        Element propertyElement = new Element(property.getName(), namespace);
        prop.addContent(propertyElement);
        
        Element status = new Element(E_STATUS, DNSP);
        status.setText("HTTP/1.1 " + property.statusCode + " "
                           + WebdavStatus.getStatusText(property.statusCode));
        propstat.addContent(status);
        return propstat;
    }
    
    /**
     * Checks the (DeltaV) preconditions
     * <ul>
     * <li>&lt;DAV:cannot-modify-version-controlled-property&gt;</li>
     * <li>&lt;DAV:cannot-modify-version&gt;</li>
     * <li>&lt;DAV:cannot-modify-protected-property&gt;</li>
     * <li>&lt;DAV:supported-live-property&gt;</li>
     * </ul>
     *
     * @param      revisionDescriptors  the NodeRevisionDescriptors of the resource
     *                                  to perform the <code>PUT</code> on.
     * @param      revisionDescriptor   the NodeRevisionDescriptor of the resource
     *                                  to perform the <code>PUT</code> on.
     * @param      resourceKind         the ResourceKind of the resource.
     *
     * @return     the precondition that has been violated (if any).
     */
    private ViolatedPrecondition getPreconditionViolation(NodeRevisionDescriptors revisionDescriptors, NodeRevisionDescriptor revisionDescriptor, ResourceKind resourceKind)
        throws ServiceAccessException {
        
        if( Configuration.useVersionControl() ) {
            
            if (resourceKind instanceof CheckedInVersionControlled) {
                
                // check precondition DAV:cannot-modify-version-controlled-property
                String autoVersion = versioningHelper.getAutoVersionElementName(revisionDescriptor);
                if (autoVersion == null) {
                    autoVersion = "";
                }
                
                if ( !E_CHECKOUT_CHECKIN.equals(autoVersion) &&
                    !E_CHECKOUT_UNLOCKED_CHECKIN.equals(autoVersion) &&
                    !E_CHECKOUT.equals(autoVersion) &&
                    !E_CHECKOUT_IGNORE_UNLOCK.equals(autoVersion) &&
                    !E_LOCKED_CHECKOUT.equals(autoVersion) &&
                    !notVersionForPropPatch(token.getUri(slideToken, resourcePath))) {
                    return new ViolatedPrecondition(C_CANNOT_MODIFY_VERSION_CONTROLLED_PROPERTY,
                                                    WebdavStatus.SC_FORBIDDEN);
                }
                if (E_LOCKED_CHECKOUT.equals(autoVersion)) {
                    if ( !versioningHelper.isWriteLocked(slideToken, revisionDescriptors) ) {
                        return new ViolatedPrecondition(C_CANNOT_MODIFY_VERSION_CONTROLLED_PROPERTY,
                                                        WebdavStatus.SC_FORBIDDEN);
                    }
                }
            }
            
            // check precondition DAV:cannot-modify-version
            UriHandler uriHandler = UriHandler.getUriHandler(resourcePath);
            if (uriHandler.isVersionUri()) {
                return new ViolatedPrecondition(C_CANNOT_MODIFY_VERSION,
                                                WebdavStatus.SC_FORBIDDEN);
            }
        }
        
        return null;
    }
    
    /**
     * Checks the property specific preconditions
     * <ul>
     * <li>&lt;DAV:cannot-modify-protected-property&gt;</li>
     * <li>&lt;DAV:supported-live-property&gt;</li>
     * </ul>
     *
     * @param      property  the PropPatchProperty to check.
     *
     * @return     the precondition that has been violated (if any,
     *             otherwise <code>null</code>).
     */
    private ViolatedPrecondition getPropertySpecificPreconditionViolation(
            ResourceKind resourceKind, PropPatchProperty property) {
        
        // check precondition DAV:cannot-modify-protected-property
        if (ResourceKindManager.isProtectedProperty(property.getPropertyName())) {
            return new ViolatedPrecondition(C_CANNOT_MODIFY_PROTECTED_PROPERTY,
                                            WebdavStatus.SC_CONFLICT);
        }
        
        // check precondition DAV:supported-live-property
        // ...is there any property to check here yet ?
        
        return null;
    }
    
    
    
    /**
     * If the given <code>xmlValue</code> contains <code>&lt;href&gt;</code>
     * elements (at any depth), absolute URIs are converted to relative ones.
     * 
     * This method modifies <code>xmlValue</code> (and its children).
     *
     * @param xmlValue The XMLValue that might contain
     *                 <code>&lt;href&gt;</code> values to convert.
     * @param servletContextPath The prefix which when added to a relative URI
     *                           makes it an absolute one.
     * @param config Configuration of the WebDAV servlet.
     */
    private static void convertHrefValueToRelativeURL (XMLValue xmlValue,
                                                       String servletContextPath,
                                                       WebdavServletConfig config) {
        if (xmlValue != null) {
            Iterator iterator = xmlValue.iterator();
            Element element = null;
            while (iterator.hasNext()) {
                Object o = iterator.next();
                if( o instanceof Element ) {
                    element = (Element)o;
                    convertHrefValueToRelativeURL(element, servletContextPath, config);
                }
            }
        }
    }
    
    
    
    /**
     * If the given <code>Element</code> contains <code>&lt;href&gt;</code>
     * elements (at any depth), the absolute URI is converted to a relative
     * one.
     * 
     * This method modifies the <code>Element</code> (and its children).
     *
     * @param element The <code>Element</code> that might contain
     *                 <code>&lt;href&gt;</code> values to convert.
     * @param servletContextPath The prefix which when added to a relative URI
     *                           makes it an absolute one.
     * @param config Configuration of the WebDAV servlet.
     */
    private static void convertHrefValueToRelativeURL (Element element,
                                                       String servletContextPath,
                                                       WebdavServletConfig config) {
        if (element.getChildren().size() > 0) {
            Iterator i = element.getChildren().iterator();
            while (i.hasNext()) {
                Element child = (Element) i.next();
                convertHrefValueToRelativeURL(child, servletContextPath, config);
            }
        }
        if ( E_HREF.equals(element.getName()) && (element.getText() != null) ) {
            if ( PropertyHelper.isAbsoluteURL(servletContextPath, element.getText()) ) {
                element.setText(element.getText().substring(servletContextPath.length()));
            }
        }
    }
    
    /**
     * <code>true</code> if unversioned PROPPATCH is allowed 
     */
    private static boolean notVersionForPropPatch(Uri resource) {
        return Boolean.valueOf(Domain.getParameter("no-versioning-for-proppatch", 
                "false", resource.getStore())).booleanValue();
    }
    
    /**
     * An extension of the RequestedProperties class manages PropPatchProperty
     * instead of RequestedProperty.
     */
    public class PropPatchProperties extends RequestedPropertiesImpl {
        
        /**
         * Default constructor.
         */
        public PropPatchProperties() {
            super();
        }
        
        /**
         * Constructs a List of PropPatchProperty from a &lt;DAV:prop&gt; element.
         *
         * @param      propElement  the &lt;DAV:prop&gt; from which to create the
         *                          List of PropPatchProperty.
         *
         * @throws      PropertyParseException  if parsing the property fails for any reason.
         */
        public PropPatchProperties (Element propElement) throws PropertyParseException {
            super(propElement);
        }
        
        /**
         * Creates a RequestedProperty from the given parameters. This method
         * may be overwritten by subclasses in order to create appropriate
         * implementations of RequestedProperty.
         *
         * @param      name       the name of the propery.
         * @param      namespacePrefix  the namespace prefix of the propery.
         * @param      namespaceUri     the namespace URI of the propery.
         * @param      text       the text of the propery element.
         * @param      children   the children of the propery element.
         *
         * @return     the created RequestedProperty.
         */
        protected RequestedProperty createRequestedProperty(String name, String namespacePrefix, String namespaceUri, List content) {
            
            String value = "";
            if (content.size() == 1 && content.get(0) instanceof Text) {
                value = ((Text)content.get(0)).getText();
            }
            else if (content.size() > 0) {
                XMLValue xmlVal = new XMLValue(content);
                if (P_GROUP_MEMBER_SET.equals(name) && S_DAV.equals(namespaceUri)) {
                    xmlVal.stripServletContext(getSlideContextPath());
                }
                value = xmlVal.toString();
            }
            // register prefix/URI at NamespaceCache
            NamespaceCache.getNamespace(namespacePrefix, namespaceUri);
            
            return new PropPatchProperty(name, namespaceUri, value);
        }
        
    }
    
    /**
     * An extension of the RequestedProperty class which supports a property value.
     */
    public static class PropPatchProperty extends RequestedPropertyImpl {
        
        /**
         * The value of the property.
         */
        protected String value = null;
        
        /**
         * The status of the PropPatch operation on this property.
         */
        protected int statusCode = WebdavStatus.SC_OK;
        
        /**
         * The precondition that has been violated when trying to patch this
         * property.
         */
        protected ViolatedPrecondition violatedPrecondition = null;
        
        /**
         * If the code returned by {@link #getStatusCode getStatusCode} is not
         * <code>200 (OK)</code> this String provides more details about the error.
         */
        protected String errorMessage = null;
        
        /**
         * Provides the qualified name of the property as an Element String,
         * i.e. if the name of the property is <code>munster</code> with
         * the namespace <code>DAV:</code> this String is
         * <code>&lt;munster xmlns="DAV:" /&gt;</code>.
         */
        protected String qualifiedNameAsElement = null;
        
        
        /**
         * Constructs a PropPatchProperty using the default namespace as defined
         * in NodeProperty.
         *
         * @param      propertyName  the name of the property.
         */
        public PropPatchProperty (String propertyName) {
            super(propertyName);
        }
        
        /**
         * Constructs a PropPatchProperty.
         *
         * @param      propertyName  the name of the property.
         * @param      namespace     the namespace of the property.
         */
        public PropPatchProperty (String propertyName, String namespace) {
            super(propertyName, namespace);
        }
        
        /**
         * Constructs a PropPatchProperty.
         *
         * @param      propertyName  the name of the property.
         * @param      namespace     the namespace of the property.
         * @param      value         the value of the property.
         */
        public PropPatchProperty (String propertyName, String namespace, String value) {
            super(propertyName, namespace);
            this.value = value;
        }
        
        /**
         * Returns the value of the property.
         *
         * @return     the value of the property.
         */
        public String getValue() {
            return value;
        }
        
        /**
         * Sets the status of the PropPatch operation on this property.
         *
         * @param      statusCode  the status code of the PropPatch operation
         *                         on this property.
         */
        public void setStatusCode(int statusCode) {
            this.statusCode = statusCode;
        }
        
        /**
         * Returns the status of the PropPatch operation on this property.
         *
         * @return     the status of the PropPatch operation on this property.
         */
        public int getStatusCode() {
            return statusCode;
        }
        
        /**
         * Sets the error message of this property.
         *
         * @param      errorMessage  the error message.
         *
         * @see #getErrorMessage
         */
        public void setErrorMessage(String errorMessage) {
            this.errorMessage = errorMessage;
        }
        
        /**
         * If the code returned by {@link #getStatusCode getStatusCode} is not
         * <code>200 (OK)</code> this String provides more details about the error.
         *
         * @return     the error message.
         */
        public String getErrorMessage() {
            return errorMessage;
        }
        
        /**
         * Sets the precondition that has been violated when trying to patch this
         * property. If the <code>violatedPrecondition</code> is not <code>null</code>
         * the status will be set to those of the ViolatedPrecondition.
         *
         * @param      violatedPrecondition  the precondition that has been violated
         *                                   when trying to patch this property.
         */
        public void setViolatedPrecondition(ViolatedPrecondition violatedPrecondition) {
            this.violatedPrecondition = violatedPrecondition;
            if (violatedPrecondition != null) {
                setStatusCode(violatedPrecondition.getStatusCode());
            }
        }
        
        /**
         * Returns the precondition that has been violated when trying to patch
         * this property.
         *
         * @return     the precondition that has been violated when trying to
         *             patch this property.
         */
        public ViolatedPrecondition getViolatedPrecondition() {
            return violatedPrecondition;
        }
        
        /**
         * Provides the qualified name of the property as an Element String,
         * i.e. if the name of the property is <code>munster</code> with
         * the namespace <code>DAV:</code> this method returns
         * <code>&lt;munster xmlns="DAV:" /&gt;</code>.
         *
         * @return     the qualified name of the property as an Element String
         */
        public String getQualifiedNameAsElementString() {
            if (qualifiedNameAsElement == null) {
                StringBuffer buffer = new StringBuffer();
                buffer.append("<");
                buffer.append(getName());
                if ( (getNamespace() != null) && (getNamespace().length() > 0) ) {
                    buffer.append(" xmlns=\"");
                    buffer.append(getNamespace());
                    buffer.append("\"");
                    buffer.append(" />");
                }
                qualifiedNameAsElement = buffer.toString();
            }
            return qualifiedNameAsElement;
        }
        
        /**
         * Returns a String representation of this instance.
         *
         * @return     a String representation of this instance.
         */
        public String toString() {
            return getNamespace()+":"+getName()+"["+getValue()+"]";
        }
    }
    
}

