/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/VersioningHelper.java,v 1.118 2005/02/25 16:57:55 luetzkendorf Exp $
 * $Revision: 1.118 $
 * $Date: 2005/02/25 16:57:55 $
 *
 * ====================================================================
 *
 * 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.util;
import java.io.IOException;
import java.io.StringReader;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.slide.common.Domain;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.common.SlideToken;
import org.apache.slide.common.SlideTokenWrapper;
import org.apache.slide.content.Content;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionContent;
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.event.VetoException;
import org.apache.slide.lock.Lock;
import org.apache.slide.lock.LockTokenNotFoundException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.lock.ObjectLockedException;
import org.apache.slide.macro.ConflictException;
import org.apache.slide.macro.Macro;
import org.apache.slide.search.BadQueryException;
import org.apache.slide.search.Search;
import org.apache.slide.search.SearchQuery;
import org.apache.slide.search.SearchQueryResult;
import org.apache.slide.search.SlideUri;
import org.apache.slide.search.basic.Literals;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.ActionNode;
import org.apache.slide.structure.LinkedObjectNotFoundException;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.Structure;
import org.apache.slide.structure.SubjectNode;
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.DetailedWebdavEvent;
import org.apache.slide.webdav.method.AbstractWebdavMethod;
import org.apache.slide.webdav.method.MethodNotAllowedException;
import org.apache.slide.webdav.util.resourcekind.AbstractResourceKind;
import org.apache.slide.webdav.util.resourcekind.CheckedInVersionControlled;
import org.apache.slide.webdav.util.resourcekind.CheckedOut;
import org.apache.slide.webdav.util.resourcekind.CheckedOutVersionControlled;
import org.apache.slide.webdav.util.resourcekind.DeltavCompliantUnmappedUrl;
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.slide.webdav.util.resourcekind.VersionControlledImpl;
import org.apache.slide.webdav.util.resourcekind.VersionHistoryImpl;
import org.apache.slide.webdav.util.resourcekind.VersionImpl;
import org.apache.slide.webdav.util.resourcekind.Working;
import org.apache.slide.webdav.util.resourcekind.WorkingImpl;
import org.apache.slide.webdav.util.resourcekind.WorkspaceImpl;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;


/**
 * Helper class for versioning operations. Allows to execute the operation from
 * within multiple methods.
 *
 */

public class VersioningHelper extends AbstractWebdavHelper {
    
    /**
     * Factory method.
     */
    public static VersioningHelper getVersioningHelper(SlideToken sToken,
            NamespaceAccessToken nsaToken, HttpServletRequest req,
            HttpServletResponse resp, WebdavServletConfig sConf) {

        return new VersioningHelper(sToken, nsaToken, req, resp, sConf, null);
    }
    public static VersioningHelper getVersioningHelper(SlideToken sToken,
            NamespaceAccessToken nsaToken, HttpServletRequest req,
            HttpServletResponse resp, WebdavServletConfig sConf, 
            AbstractWebdavMethod method) {

        return new VersioningHelper(sToken, nsaToken, req, resp, sConf, method);
    }
    
    /**
     * * The SAXBuilder used to create JDOM Documents.
     */
    protected static SAXBuilder saxBuilder = null;
    
    private Content content = null;
    private Structure structure = null;
    private Macro macro = null;
    private Lock lock = null;
    private HttpServletRequest req = null;
    private HttpServletResponse resp = null;
    private WebdavServletConfig sConf = null;
    private PropertyHelper pHelp = null;
    private String slideContextPath = null;
    private AbstractWebdavMethod method = null;
    
    /**
     * The URI of the <code>modifyRevisionMetadataAction</code>.
     */
    protected final String modifyMetadataUri;
    
    /**
     * The URI of the <code>modifyRevisionContentAction</code>.
     */
    protected final String modifyContentUri;
    
    
    /**
     * Protected contructor
     */
    protected VersioningHelper(SlideToken sToken, NamespaceAccessToken nsaToken,
                               HttpServletRequest req, HttpServletResponse resp, 
                               WebdavServletConfig sConf, AbstractWebdavMethod method) {
        super( sToken, nsaToken );
        this.req = req;
        this.resp = resp;
        this.sConf = sConf;
        this.content = nsaToken.getContentHelper();
        this.structure = nsaToken.getStructureHelper();
        this.macro = nsaToken.getMacroHelper();
        this.lock = nsaToken.getLockHelper();
        this.pHelp = PropertyHelper.getPropertyHelper( sToken, nsaToken, sConf );
        this.slideContextPath = req.getContextPath();
        this.method = method;
        if (!sConf.isDefaultServlet()) {
            this.slideContextPath += req.getServletPath();
        }
        ActionNode actionNode = nsaToken.getNamespaceConfig().getModifyRevisionMetadataAction();
        if (actionNode != null) {
            modifyMetadataUri = actionNode.getUri();
        }
        else {
            modifyMetadataUri = "";
        }
        actionNode = nsaToken.getNamespaceConfig().getModifyRevisionContentAction();
        if (actionNode != null) {
            modifyContentUri = actionNode.getUri();
        }
        else {
            modifyContentUri = "";
        }
        //serverURL = "http://" + req.getServerName()+ ":" + req.getServerPort();
    }
    
    /**
     * Returns slide Uri determinating the NodeRevisionDescriptors and
     * NodeRevisionDescriptor associated with the given <code>resourcePath</code>.
     * If the given <code>label</code> is not <code>null</code>, and the
     * <code>resourcePath</code> identifies a VCR, the revision with that label
     * of the associated history is returned.
     *
     * @param      resourcePath  the path of the resource for which to retrieve
     *                           the SlideResource.
     * @param      label         the label of the revision to return. May be
     *                           <code>null</code>.
     *
     * @return     slide Uri determinating the NodeRevisionDescriptors and
     *             NodeRevisionDescriptor associated with the given
     *             <code>resourcePath</code>.
     *
     * @throws     SlideException
     * @throws     LabeledRevisionNotFoundException if no revision with the specified
     *                                              label was found.
     */
    public String getLabeledResourceUri(String resourcePath, String label) throws SlideException, LabeledRevisionNotFoundException {
        if (label == null) {
            return resourcePath;
        }
        else {
            SlideToken lightSToken = sToken;
            if (sToken.isForceStoreEnlistment() || sToken.isForceLock()) {
                lightSToken = new SlideTokenWrapper(sToken);
                lightSToken.setForceLock(false);
            }
            return getLabeledResourceUri(nsaToken, lightSToken, content, resourcePath, label);
        }
    }
    
    /**
     * If the <code>resourcePath</code> identifies a VHR, the associated revision
     * with the given <code>label</code> is returned. If the <code>resourcePath</code>
     * does not identify a VHR , <code>null</code> is returned.
     *
     * @param      resourcePath  the path of the resource for which to retrieve
     *                           the NRD.
     * @param      label         the label of the revision to return.
     *
     * @return     the associated revision with the given <code>label</code>.
     *
     * @throws     SlideException
     * @throws     LabeledRevisionNotFoundException if no revision with the specified
     *                                              label was found.
     */
    public NodeRevisionDescriptor retrieveLabeledRevision(String resourcePath, String label) throws SlideException, LabeledRevisionNotFoundException {
        return retrieveLabeledRevision(nsaToken, sToken, content, resourcePath, label);
    }
    
    /**
     * Set the specified resource under version control
     *
     * @param resourcePath the URI of the resource to version-control
     * @throws SlideException
     */
    public void versionControl( String resourcePath ) throws SlideException {
        UriHandler rUh = UriHandler.getUriHandler( resourcePath );
        Iterator i;
        Enumeration j;
        
        NodeRevisionDescriptors rNrds = content.retrieve( sToken, resourcePath );
        NodeRevisionDescriptor rNrd = content.retrieve( sToken, rNrds );
        ResourceKind rRk = AbstractResourceKind.determineResourceKind( nsaToken, resourcePath, rNrd );
        if( !rRk.isSupportedMethod(req.getMethod()) ) {
            throw new MethodNotAllowedException( rRk );
        }
        
        // Check for rRk = K_VERSION_CONTROLLED*
        if( rRk instanceof VersionControlled ) {
            // nothing to do
            return;
        }
        
        if (DetailedWebdavEvent.VERSION_CONTROL_BEFORE.isEnabled()) {
            EventDispatcher.getInstance().fireVetoableEvent(
                    DetailedWebdavEvent.VERSION_CONTROL_BEFORE,
                    new DetailedWebdavEvent(this.method, resourcePath));
        }
        
        // Create new history URI
        UriHandler vhrUh = UriHandler.createNextHistoryUri( sToken, nsaToken, rUh );
        String vhrUri = String.valueOf(vhrUh);
        
        // Set initial VR properties
        NodeRevisionDescriptor vrNrd =
            new NodeRevisionDescriptor(req.getContentLength());
        i = pHelp.createInitialProperties(VersionImpl.getInstance(), vhrUri).iterator();
        while( i.hasNext() )
            vrNrd.setProperty( (NodeProperty)i.next() );
        
        // Copy dead properties VCR -> VR
        j = rNrd.enumerateProperties();
        while( j.hasMoreElements() ) {
            NodeProperty p = (NodeProperty)j.nextElement();
            //if( p.isLiveProperty() )
            if (rRk.isSupportedLiveProperty(p.getPropertyName()))
                continue;
            if( !vrNrd.exists(p.getPropertyName()) )
                vrNrd.setProperty( p );
        }
        
        // Copy properties VCR->VR
        NodeRevisionContent rNrc = content.retrieve( sToken, rNrds, rNrd );
        vrNrd.setContentType(rNrd.getContentType()); // P_GETCONTENTTYPE
        vrNrd.setContentLength(rNrd.getContentLength()); // P_GETCONTENTLENGTH
        vrNrd.setContentLanguage(rNrd.getContentLanguage()); // P_GETCONTENTLANGUAGE
        String comment = "INITIAL VERSION. ";
        if( rNrd.exists(PN_COMMENT) )
            comment += (String)rNrd.getProperty(PN_COMMENT).getValue();
        vrNrd.setProperty(
            new NodeProperty(PN_COMMENT, comment) );
        
        // Set initial VHR properties
        NodeRevisionDescriptor vhrNrd =
            new NodeRevisionDescriptor( NodeRevisionNumber.HIDDEN_0_0, 
                    NodeRevisionDescriptors.MAIN_BRANCH );
        i = pHelp.createInitialProperties(VersionHistoryImpl.getInstance(), vhrUri).iterator();
        while( i.hasNext() )
            vhrNrd.setProperty( (NodeProperty)i.next() );
        
        // Set initial VCR properties (do not overwrite existing!!)
        i = pHelp.createInitialProperties(VersionControlledImpl.getInstance(), resourcePath).iterator();
        while( i.hasNext() ) {
            NodeProperty p = (NodeProperty)i.next();
            if( !rNrd.exists(p.getPropertyName()) )
                rNrd.setProperty( p );
        }
        
        // Create VHR/VR
        SubjectNode vhrNode = new SubjectNode();
        structure.create( sToken, vhrNode, String.valueOf(vhrUh) );
        content.create( sToken, vhrUri, true ); //isVersioned=true
        content.create( sToken, vhrUri, vrNrd, rNrc );
        //NodeRevisionDescriptors vhrNrds = content.retrieve( sToken, vhrUri );
        content.create(
            sToken, vhrUri, null, vhrNrd, null ); //branch=null, revisionContent=null
        
        // Create VR node
        NodeRevisionNumber vrVersion = vrNrd.getRevisionNumber();
        SubjectNode vrNode = new SubjectNode();
        UriHandler vrUh =
            UriHandler.createVersionUri( vhrUh, String.valueOf(vrVersion) );
        String vrUri = String.valueOf( vrUh );
        structure.create( sToken, vrNode, String.valueOf(vrUh) );
        
        // Set specific properties
        vrNrd.setName(rUh.getName()); // P_DISPLAYNAME
        rNrd.setProperty(PN_CHECKED_IN, pHelp.createHrefValue(vrUri));
        vhrNrd.setCreationDate( new Date() ); // P_CREATIONDATE
        setCreationUser(vhrNrd);
        vhrNrd.setLastModified( new Date() ); // P_GETLASTMODIFIED
        vhrNrd.setContentLength( 0 ); // P_GETCONTENTLENGTH
        vhrNrd.setETag( PropertyHelper.computeEtag(vhrUri, vhrNrd) ); // P_GETETAG
//        vhrNrd.setName( vhrUh.getHistoryName() ); // P_DISPLAYNAME
        vhrNrd.setName( rNrd.getName() ); // P_DISPLAYNAME
        vhrNrd.setProperty(PN_VERSION_SET, pHelp.createHrefValue(vrUri));
        
        vrNrd.setCreationDate( new Date() ); // P_CREATIONDATE
        setCreationUser(vrNrd);
        vrNrd.setLastModified( new Date() ); // P_GETLASTMODIFIED
        vrNrd.setETag( PropertyHelper.computeEtag(vrUri, vrNrd)); // P_GETETAG
        vrNrd.setProperty(
            new NodeProperty(PN_VERSION_NAME, vrUh.getVersionName()) );
        
        // Store changes
        content.store( sToken, resourcePath, rNrd, null ); //revisionContent=null
        content.store( sToken, vhrUri, vhrNrd, null ); //revisionContent=null
        content.store( sToken, vhrUri, vrNrd, null ); //revisionContent=null
        
        if (DetailedWebdavEvent.VERSION_CONTROL_AFTER.isEnabled()) {
            EventDispatcher.getInstance().fireVetoableEvent(
                    DetailedWebdavEvent.VERSION_CONTROL_AFTER,
                    new DetailedWebdavEvent(this.method, resourcePath));
        }
    }
    
    /**
     * Create new VCR for an existing version history.
     * @pre   existingVersionPath != null
     *
     * @param resourcePath the URI of the resource to version-control
     * @param existingVersionPath the URI of the VR on which the new VCR will be based
     * @throws SlideException
     */
    public void versionControl( String resourcePath, String existingVersionPath ) throws SlideException {
        Iterator i;
        
        UriHandler rUh = UriHandler.getUriHandler( resourcePath );
        UriHandler evUh = UriHandler.getUriHandler( existingVersionPath );
        if ( ! evUh.isVersionUri() ) {
            throw new PreconditionViolationException(
                new ViolatedPrecondition(C_MUST_BE_VERSION, WebdavStatus.SC_CONFLICT), resourcePath);
        }
        //NodeRevisionNumber evNrn = new NodeRevisionNumber( evUh.getVersionName() );
        NodeRevisionDescriptors rNrds = null;
        NodeRevisionDescriptor rNrd = null;
        //NodeRevisionDescriptors vcrNrds = null;
        NodeRevisionDescriptor vcrNrd = null;
        NodeRevisionDescriptors evNrds = null;
        NodeRevisionDescriptor evNrd = null;
        
        try {
            rNrds = content.retrieve( sToken, resourcePath );
            rNrd = content.retrieve( sToken, rNrds );
        }
        catch( ObjectNotFoundException e ) {}; // can be ignored here!
        try {
            evNrds = content.retrieve( sToken, existingVersionPath );
            evNrd = content.retrieve( sToken, evNrds /*, evNrn*/ ); //existingVersionPath
            //redirector should do the job
            //s.t. evNrn is not required
        }
        catch( ObjectNotFoundException e ) {}; // can be ignored here!
        
        ViolatedPrecondition violatedPrecondition =
            getVersionControlPreconditionViolation(resourcePath,
                                                   rNrd,
                                                   rUh,
                                                   existingVersionPath,
                                                   evNrd,
                                                   evUh);
        if (violatedPrecondition != null) {
            throw new PreconditionViolationException(violatedPrecondition,
                                                     resourcePath);
        }
        
        if (DetailedWebdavEvent.VERSION_CONTROL_BEFORE.isEnabled()) {
            EventDispatcher.getInstance().fireVetoableEvent(
                    DetailedWebdavEvent.VERSION_CONTROL_BEFORE,
                    new DetailedWebdavEvent(this.method, resourcePath, existingVersionPath));
        }
        
        // create the VCR
        String vcrUri = String.valueOf(rUh);
        String evUri = String.valueOf(evUh);
        UriHandler vcrUh = UriHandler.getUriHandler( vcrUri );
        vcrNrd = new NodeRevisionDescriptor(0);
        i = pHelp.createInitialProperties(VersionControlledImpl.getInstance(), resourcePath).iterator();
        while( i.hasNext() )
            vcrNrd.setProperty( (NodeProperty)i.next() );
        
        // Set specific properties
        vcrNrd.setLastModified( new Date() ); //P_GETLASTMODIFIED
        vcrNrd.setContentLength( evNrd.getContentLength() ); // P_GETCONTENTLENGTH
        vcrNrd.setETag( PropertyHelper.computeEtag(vcrUri, vcrNrd));  // P_GETETAG
        vcrNrd.setContentType( evNrd.getContentType() ); // P_GETCONTENTTYPE
        vcrNrd.setContentLanguage(evNrd.getContentLanguage()); // P_GETCONTENTLANGUAGE
        
        String[] utok = vcrUh.getUriTokens();
        
        if (!Configuration.useBinding(nsaToken.getUri(sToken, vcrUri).getStore())) {
            vcrNrd.setName( utok[utok.length - 1] ); // P_DISPLAYNAME
        }
        vcrNrd.setCreationDate( new Date() ); // P_CREATIONDATE
        setCreationUser(vcrNrd);
        vcrNrd.setProperty( new NodeProperty(PN_CHECKED_IN,
                                             pHelp.createHrefValue(evUri)) );
        // set workspace
        setWorkspaceProperty( vcrUri, vcrNrd );
        
        // store
        SubjectNode vcrNode = new SubjectNode();
        structure.create( sToken, vcrNode, vcrUri );
        NodeRevisionContent evContent =
            content.retrieve( sToken, evNrds, evNrd );
        content.create( sToken, vcrUri, vcrNrd, evContent );
        
        if (DetailedWebdavEvent.VERSION_CONTROL_AFTER.isEnabled()) {
            EventDispatcher.getInstance().fireVetoableEvent(
                    DetailedWebdavEvent.VERSION_CONTROL_AFTER,
                    new DetailedWebdavEvent(this.method, resourcePath, existingVersionPath));
        }
        
        // Set status created
        resp.setStatus( WebdavStatus.SC_CREATED );
    }
    
    /**
     * Returns the precondition that might have been violated on an attempt to create
     * a new VCR in a workspace for an existing version history.
     * The following precondtions are checked:
     * <ul>
     * <li>&lt;DAV:cannot-add-to-existing-history&gt;</li>
     * <li>&lt;DAV:must-be-version&gt;</li>
     * <li>&lt;DAV:one-version-controlled-resource-per-history-per-workspace&gt;</li>
     * </ul>
     *
     * @param      resourcePath               the path of the resource.
     * @param      resourceNrd                the NodeRevisionDescriptor of the resource.
     * @param      resourceUriHandler         the UriHandler of the resource.
     * @param      existingVersionPath        the path of the existing version.
     * @param      existingVersionNrd         the NodeRevisionDescriptor of the existing version.
     * @param      existingVersionUriHandler  the UriHandler of the existing version.
     *
     * @return     the precondition that has been violated (if any).
     *
     * @throws     SlideException
     */
    public ViolatedPrecondition getVersionControlPreconditionViolation(String resourcePath,
                                                                       NodeRevisionDescriptor resourceNrd,
                                                                       UriHandler resourceUrihandler,
                                                                       String existingVersionPath,
                                                                       NodeRevisionDescriptor existingVersionNrd,
                                                                       UriHandler existingVersionUrihandler) throws SlideException {
        
        ResourceKind rRk = AbstractResourceKind.determineResourceKind( nsaToken, resourcePath, resourceNrd );
        ResourceKind evRk = AbstractResourceKind.determineResourceKind( nsaToken, existingVersionPath, existingVersionNrd );
        
        if( !(rRk instanceof DeltavCompliantUnmappedUrl) ) {
            return new ViolatedPrecondition(C_CANNOT_ADD_TO_EXISTING_HISTORY, WebdavStatus.SC_CONFLICT);
        }
        if( !(evRk instanceof Version) || existingVersionNrd == null) {
            return new ViolatedPrecondition(C_MUST_BE_VERSION, WebdavStatus.SC_CONFLICT);
        }
        
        String scope = resourceUrihandler.getAssociatedWorkspaceUri();
        if( scope == null )
            scope = UriHandler.bestMatchingScope(nsaToken.getName(), resourceUrihandler).toString();
        String historyPath = existingVersionUrihandler.getAssociatedHistoryUri();
        SearchQueryResult queryResult = searchResourcesWithGivenHistory(historyPath, scope, Integer.MAX_VALUE);
        Iterator queryResultIterator = queryResult.iterator();
        if (queryResultIterator.hasNext()) {
            return new ViolatedPrecondition(C_ONE_VERSION_CONTROLLED_RESOURCE_PER_HISTORY_PER_WORKSPACE,
                                            WebdavStatus.SC_CONFLICT);
        }
        
        return null;
    }
    
    /**
     * Searches all resources in the given <code>scope</code> that have either a
     * <code>&lt;checked-in&gt;</code> or <code>&lt;checked-in&gt;</code> property
     * with a <code>&lt;href&gt;</code> element containing the given
     * <code>historyPath</code>.
     *
     * @param      historyPath  the path of the history.
     * @param      scope        the scope of the search.
     *
     * @return     all matching resources.
     *
     * @throws     ServiceAccessException
     * @throws     BadQueryException
     */
    protected SearchQueryResult searchResourcesWithGivenHistory(String historyPath, String scope, int maxDepth) throws ServiceAccessException, BadQueryException, VetoException {
        
        SlideUri slideUri = SlideUri.createWithRequestUri(
                this.slideContextPath, req.getRequestURI());
        String absPath = slideUri.getContextPath (scope);
        
        Element basicSearch = getResourcesWithVersionHistoryQueryElement(absPath,
                                                                         historyPath);
        String grammarNamespace = basicSearch.getNamespaceURI();
        Search searchHelper = nsaToken.getSearchHelper();
        SearchQuery searchQuery = searchHelper.createSearchQuery(grammarNamespace,
                                                                 basicSearch,
                                                                 sToken,
                                                                 maxDepth,
                                                                 req.getRequestURI());
        
        SearchQueryResult queryResult = searchHelper.search(sToken, searchQuery);
        return queryResult;
    }
    
    
    /**
     * Returns the query document used to search all resources in the given
     * <code>scope</code> that have either a &lt;checked-in&gt; or &lt;checked-out&gt;
     * property with a &lt;href&gt; value containing the URI that identifies a
     * version of the given history.
     *
     * @param      scope        the scope of the search.
     * @param      historyPath  the Uri of the history.
     *
     * @return     the query document.
     */
    protected Element getResourcesWithVersionHistoryQueryElement(String scope, String historyPath) {
        
        Element resourcesWithVersionHistoryQueryElement = new Element(DaslConstants.E_BASICSEARCH, NamespaceCache.DEFAULT_NAMESPACE);
        
        Element select = new Element(DaslConstants.E_SELECT, NamespaceCache.DEFAULT_NAMESPACE);
        resourcesWithVersionHistoryQueryElement.addContent(select);
        Element prop = new Element(E_PROP, NamespaceCache.DEFAULT_NAMESPACE);
        select.addContent(prop);
        Element checkedIn = new Element(P_CHECKED_IN, NamespaceCache.DEFAULT_NAMESPACE);
        prop.addContent(checkedIn);
        Element checkedOut = new Element(P_CHECKED_OUT, NamespaceCache.DEFAULT_NAMESPACE);
        prop.addContent(checkedOut);
        
        Element from = new Element(DaslConstants.E_FROM, NamespaceCache.DEFAULT_NAMESPACE);
        resourcesWithVersionHistoryQueryElement.addContent(from);
        Element scopeElement = new Element(DaslConstants.E_SCOPE, NamespaceCache.DEFAULT_NAMESPACE);
        from.addContent(scopeElement);
        Element href = new Element(E_HREF, NamespaceCache.DEFAULT_NAMESPACE);
        scopeElement.addContent(href);
        href.setText(scope);
        
        Element where = new Element(DaslConstants.E_WHERE, NamespaceCache.DEFAULT_NAMESPACE);
        resourcesWithVersionHistoryQueryElement.addContent(where);
        Element or = new Element(Literals.OR, NamespaceCache.DEFAULT_NAMESPACE);
        where.addContent(or);
        
        Element propcontains = new Element(DaslConstants.E_PROPCONTAINS, NamespaceCache.SLIDE_NAMESPACE);
        or.addContent(propcontains);
        prop = new Element(E_PROP, NamespaceCache.DEFAULT_NAMESPACE);
        propcontains.addContent(prop);
        prop.addContent((Element)checkedIn.clone());
        Element literal = new Element(DaslConstants.E_LITERAL, NamespaceCache.DEFAULT_NAMESPACE);
        propcontains.addContent(literal);
        literal.setText(historyPath);
        
        propcontains = new Element(DaslConstants.E_PROPCONTAINS, NamespaceCache.SLIDE_NAMESPACE);
        or.addContent(propcontains);
        prop = new Element(E_PROP, NamespaceCache.DEFAULT_NAMESPACE);
        propcontains.addContent(prop);
        prop.addContent((Element)checkedOut.clone());
        literal = new Element(DaslConstants.E_LITERAL, NamespaceCache.DEFAULT_NAMESPACE);
        propcontains.addContent(literal);
        literal.setText(historyPath);
        
        return resourcesWithVersionHistoryQueryElement;
    }
    
    /**
     * Set the workspace property if needed.
     *
     * @param rUri the URI of the resource to set the workspace property
     * @param rNrd the NodeRevisionDescriptor to set the workspace property
     */
    public void setWorkspaceProperty( String rUri, NodeRevisionDescriptor rNrd ) {
        UriHandler rUh = UriHandler.getUriHandler( rUri );
        String wsUri = rUh.getAssociatedWorkspaceUri();
        if( wsUri != null ) {
            rNrd.setProperty(
                new NodeProperty(PN_WORKSPACE, pHelp.createHrefValue(wsUri)) );
        }
        else {
            rNrd.removeProperty(PN_WORKSPACE);
        }
    }
    
    /**
     * Create the specified workspace.
     *
     * @param resourcePath the URI of the workspace to create
     * @throws SlideException
     */
    public void mkworkspace( String resourcePath ) throws SlideException {
        Iterator i;
        
        UriHandler rUh = UriHandler.getUriHandler( resourcePath );
        NodeRevisionDescriptor rNrd = null;
        try {
            NodeRevisionDescriptors rNrds = content.retrieve( sToken, resourcePath );
            rNrd = content.retrieve( sToken, rNrds );
        }
        catch( ObjectNotFoundException e ) {}; // can be ignored here!
        
        ResourceKind rRk = AbstractResourceKind.determineResourceKind( nsaToken, resourcePath, rNrd );
        
        if( !(rRk instanceof DeltavCompliantUnmappedUrl) ) {
            throw new PreconditionViolationException(
                new ViolatedPrecondition(C_RESOURCE_MUST_BE_NULL, WebdavStatus.SC_CONFLICT), resourcePath);
        }
        if( !rUh.isWorkspaceUri() ) {
            throw new PreconditionViolationException(
                new ViolatedPrecondition(C_WORKSPACE_LOCATION_OK, WebdavStatus.SC_FORBIDDEN), resourcePath);
        }
        if( !rRk.isSupportedMethod(req.getMethod()) ) {
            throw new MethodNotAllowedException( rRk );
        }
        
        // Set initial ws properties
        String wsUri = String.valueOf(rUh);
        NodeRevisionDescriptor wsNrd =
            new NodeRevisionDescriptor(0);
        i = pHelp.createInitialProperties(WorkspaceImpl.getInstance(), resourcePath).iterator();
        while( i.hasNext() )
            wsNrd.setProperty( (NodeProperty)i.next() );
        
        // Set specific properties
        wsNrd.setProperty(
            new NodeProperty(PN_WORKSPACE, pHelp.createHrefValue(wsUri)) );
        wsNrd.setLastModified( new Date() ); //P_GETLASTMODIFIED
        wsNrd.setContentLength( 0 ); // P_GETCONTENTLENGTH
        wsNrd.setETag( PropertyHelper.computeEtag(wsUri, wsNrd) ); // P_GETETAG
        if (!Configuration.useBinding(nsaToken.getUri(sToken, wsUri).getStore())) {
            wsNrd.setName( rUh.getWorkspaceName() ); // P_DISPLAYNAME
        }
        wsNrd.setCreationDate( new Date() ); // P_CREATIONDATE
        setCreationUser(wsNrd);
        
        // Create the ws resource
        SubjectNode wsNode = new SubjectNode();
        structure.create( sToken, wsNode, wsUri );
        content.create( sToken, wsUri, wsNrd, null ); // revisionContent = null
        
        // Set status created
        resp.setStatus( WebdavStatus.SC_CREATED );
    }
    
    /**
     * Checkout a resource (for both: checkout-in-place and working-resource features).
     * @param resourcePath the request URI
     * @param forkOk true, if the request body contained a DAV:fork-ok element
     * @param applyToVersion true, if the request body contained a DAV:apply-to-version element
     * @return the URI of the created working resource, null if no resource was created
     * @throws SlideException
     * @throws JDOMException
     * @throws IOException
     * @throws PreconditionViolatedException
     */
    public String checkout( String resourcePath, boolean forkOk, boolean applyToVersion )
        throws SlideException, JDOMException, IOException, PreconditionViolationException  {
        return checkout(resourcePath, forkOk, applyToVersion, false);
    }
    
    /**
     * Checkout a resource (for both: checkout-in-place and working-resource features).
     * @param resourcePath the request URI
     * @param forkOk true, if the request body contained a DAV:fork-ok element
     * @param applyToVersion true, if the request body contained a DAV:apply-to-version element
     * @param isAutoVersionCheckout true, if this is an implicit request due to auto-versioning
     * @return the URI of the created working resource, null if no resource was created
     * @throws SlideException
     * @throws JDOMException
     * @throws IOException
     * @throws PreconditionViolatedException
     */
    public String checkout( String resourcePath, boolean forkOk, boolean applyToVersion, boolean isAutoVersionCheckout )
        throws SlideException, JDOMException, IOException, PreconditionViolationException  {
        
        UriHandler rUh = UriHandler.getUriHandler( resourcePath );
        NodeRevisionDescriptors rNrds = content.retrieve( sToken, resourcePath );
        NodeRevisionDescriptor rNrd = content.retrieve( sToken, rNrds );
        
        if( rUh.isVersionUri() ) {
            NodeRevisionContent rNrc = content.retrieve( sToken, rNrds, rNrd );
            return checkout( rNrds, rNrd, rNrc, forkOk, null ); // autoUpdateUri=null
        }
        else {
            return checkout( rNrds, rNrd, forkOk, applyToVersion, isAutoVersionCheckout);
        }
    }
    
    /**
     * Checkout a resource (for both: checkout-in-place and working-resource features).
     * @param rNrds the revision descriptors instance
     * @param rNrd the revision descriptor instance
     * @param forkOk true, if the request body contained a DAV:fork-ok element
     * @param applyToVersion true, if the request body contained a DAV:apply-to-version element
     * @return the URI of the created working resource, null if no resource was created
     * @throws SlideException
     * @throws JDOMException
     * @throws IOException
     * @throws PreconditionViolatedException
     */
    public String checkout( NodeRevisionDescriptors rNrds,
                           NodeRevisionDescriptor rNrd, boolean forkOk, boolean applyToVersion )
        throws SlideException, JDOMException, IOException, PreconditionViolationException  {
        return checkout(rNrds, rNrd, forkOk, applyToVersion, false);
    }
    
    /**
     * Checkout a resource (for both: checkout-in-place and working-resource features).
     * Checkout a resource (for both: checkout-in-place and working-resource features).
     * @param rNrds the revision descriptors instance
     * @param rNrd the revision descriptor instance
     * @param forkOk true, if the request body contained a DAV:fork-ok element
     * @param applyToVersion true, if the request body contained a DAV:apply-to-version element
     * @param isAutoVersionCheckout true, if this is an implicit request due to auto-versioning
     * @return the URI of the created working resource, null if no resource was created
     * @throws SlideException
     * @throws JDOMException
     * @throws IOException
     * @throws PreconditionViolatedException
     */
    public String checkout( NodeRevisionDescriptors rNrds,
                           NodeRevisionDescriptor rNrd, boolean forkOk, boolean applyToVersion, boolean isAutoVersionCheckout  )
        throws SlideException, JDOMException, IOException, PreconditionViolationException  {
        
        //Iterator i;
        String rUri = getUri( rNrds, rNrd );
        ResourceKind rRk = AbstractResourceKind.determineResourceKind( nsaToken, rNrds, rNrd );
        
        if( !rRk.isSupportedMethod(req.getMethod()) ) {
            // check precondition C_MUST_BE_CHECKED_IN
            if( rRk instanceof CheckedOut ) {
                throw new PreconditionViolationException(new ViolatedPrecondition(C_MUST_BE_CHECKED_IN, WebdavStatus.SC_CONFLICT), rNrds.getUri());
            }
            throw new MethodNotAllowedException( rRk );
        }
        
        if( rRk instanceof CheckedInVersionControlled ) {
            // get checked-in VR
            NodeProperty cinProp = rNrd.getProperty(PN_CHECKED_IN);
            String cinHref = getElementValue((String)cinProp.getValue());
            UriHandler cinUriHandler = UriHandler.getUriHandler(cinHref);
            String cinhUri = cinUriHandler.getAssociatedHistoryUri();
            NodeRevisionNumber cinNrn = new NodeRevisionNumber(cinUriHandler.getVersionName());
            NodeRevisionDescriptors cinNrds = content.retrieve(sToken, cinhUri);
            NodeRevisionDescriptor cinNrd = content.retrieve(sToken, cinNrds, cinNrn);
            
            // working resource feature
            if( applyToVersion ) {
                NodeRevisionContent cinNrc = content.retrieve( sToken, cinNrds, cinNrd );
                return checkout( cinNrds, cinNrd, cinNrc, forkOk, rUri ); // autoUpdateUri=rUri
            }
            
            ViolatedPrecondition violatedPrecondition = getCheckoutPreconditionViolation(cinNrds, cinNrd, forkOk);
            if (violatedPrecondition != null) {
                throw new PreconditionViolationException(violatedPrecondition, rNrds.getUri());
            }
            
            //NodeRevisionDescriptors vhrNrds = content.retrieve(sToken, cinhUri);
            
            // do the checkout
            backupSpecificLiveProperties(rNrds, rNrd);
            rNrd.removeProperty( cinProp );
            rNrd.setProperty(
                new NodeProperty(PN_CHECKED_OUT, cinProp.getValue()) );
            rNrd.setProperty(
                new NodeProperty(PN_PREDECESSOR_SET, cinProp.getValue()) );
            NodeProperty property = cinNrd.getProperty(PN_CHECKOUT_FORK);
            if (property != null) {
                rNrd.setProperty(property);
            }
            property = cinNrd.getProperty(PN_CHECKIN_FORK);
            if (property != null) {
                rNrd.setProperty(property);
            }
            
            if (isAutoVersionCheckout && 
            		!(E_CHECKOUT_IGNORE_UNLOCK.equals(getAutoVersionElementName(rNrd)))) {
                NodeLock writeLock = getWriteLock(sToken, rNrds);
                if (writeLock != null) {
                    NodeProperty p =
                        new NodeProperty(I_CHECKIN_LOCKTOKEN,
                                         writeLock.getLockId(),
                                         NamespaceCache.SLIDE_URI,
                                         true); // protected
                    rNrd.setProperty( p );
                }
            }
            
            // update checked-in VR's DAV:checkout-set property
            PropertyHelper.addHrefToProperty(cinNrd, P_CHECKOUT_SET, rUri);
            
            // Store changes
            content.store( sToken, rNrds.getUri(), rNrd, null ); //revisionContent=null
            content.store( sToken, cinNrds.getUri(), cinNrd, null );
            return null;
        }
        else {
            Domain.warn(
                "Do not know how to checkout a '"+rRk+"' resource" );
            resp.setStatus(WebdavStatus.SC_CONFLICT);
            throw new WebdavException( WebdavStatus.SC_CONFLICT );
        }
    }
    
    /**
     * Checkout a resource (working-resource features only).
     * @param rNrds the revision descriptors instance associated to the version being checked-out
     * @param rNrd the revision descriptor instance associated to the version being checked-out
     * @param rNrc the revision content instance associated to the version being checked-out
     * @param forkOk true, if the request body contained a DAV:fork-ok element
     * @param autoUpdateUri the URI of the VCR that will be updated when the WR will be checked-in
     * @return the URI of the created working resource
     * @throws SlideException
     * @throws JDOMException
     * @throws IOException
     * @throws PreconditionViolatedException
     */
    public String checkout( NodeRevisionDescriptors rNrds, NodeRevisionDescriptor rNrd, NodeRevisionContent rNrc,
                           boolean forkOk, String autoUpdateUri )
        throws SlideException, JDOMException, IOException, PreconditionViolationException  {
        
        Iterator i;
        Enumeration j;
        String rUri = getUri( rNrds, rNrd );
        ResourceKind rRk = AbstractResourceKind.determineResourceKind( nsaToken, rNrds, rNrd );
        
        if( !rRk.isSupportedMethod(req.getMethod()) ) {
            // check precondition C_MUST_BE_CHECKED_IN
            if( rRk instanceof CheckedOut ) {
                throw new PreconditionViolationException(new ViolatedPrecondition(C_MUST_BE_CHECKED_IN, WebdavStatus.SC_CONFLICT), rNrds.getUri());
            }
            throw new MethodNotAllowedException( rRk );
        }
        
        if( rRk instanceof Version ) {
            
            UriHandler rUh = UriHandler.getUriHandler( rUri );
            //String vhUri = rUh.getAssociatedHistoryUri();
            
            ViolatedPrecondition violatedPrecondition = getCheckoutPreconditionViolation(rNrds, rNrd, forkOk);
            if (violatedPrecondition != null) {
                throw new PreconditionViolationException(violatedPrecondition, rNrds.getUri());
                
                
            }
            
            //NodeRevisionDescriptors vhrNrds = content.retrieve(sToken, vhUri);
            
            // create the workingresource
            UriHandler wrUh = UriHandler.createNextWorkingresourceUri( sToken, nsaToken, rUh );
            String wrUri = String.valueOf( wrUh );
            SubjectNode wrNode = new SubjectNode();
            structure.create( sToken, wrNode, String.valueOf(wrUh) );
            
            // set WR props
            NodeRevisionDescriptor wrNrd = new NodeRevisionDescriptor();
            i = pHelp.createInitialProperties(WorkingImpl.getInstance(), wrUri).iterator();
            while( i.hasNext() )
                wrNrd.setProperty( (NodeProperty)i.next() );
            //            content.create( sToken, wrUri, wrNrd, rNrc );
            
            // set specific live props
            wrNrd.setProperty(
                new NodeProperty(PN_CHECKED_OUT, pHelp.createHrefValue(rUri)) );
            wrNrd.setProperty(
                new NodeProperty(PN_PREDECESSOR_SET, pHelp.createHrefValue(rUri)) );
            NodeProperty coutfProp = rNrd.getProperty(PN_CHECKOUT_FORK);
            if( coutfProp != null )
                wrNrd.setProperty( coutfProp );
            NodeProperty cinfProp = rNrd.getProperty(PN_CHECKIN_FORK);
            if( cinfProp != null )
                wrNrd.setProperty( cinfProp );
            wrNrd.setContentType(rNrd.getContentType()); // P_GETCONTENTTYPE
            wrNrd.setContentLength( rNrd.getContentLength() ); // P_GETCONTENTLENGTH
            wrNrd.setContentLanguage(rNrd.getContentLanguage()); // P_GETCONTENTLANGUAGE
            wrNrd.setLastModified( new Date() ); //P_GETLASTMODIFIED
            wrNrd.setCreationDate( new Date() ); // P_CREATIONDATE
            setCreationUser(wrNrd);
            wrNrd.setETag( PropertyHelper.computeEtag(wrUri, wrNrd)  ); // P_GETETAG
            
            // set auto-update
            if( autoUpdateUri != null && autoUpdateUri.length() > 0 ) {
                UriHandler autoUpdateUh = UriHandler.getUriHandler( autoUpdateUri );
                wrNrd.setProperty(
                    new NodeProperty(PN_AUTO_UPDATE, pHelp.createHrefValue(autoUpdateUri)) );
                wrNrd.setName( autoUpdateUh.getName() );
            }
            else {
                wrNrd.removeProperty(PN_AUTO_UPDATE);
                wrNrd.setName( rNrd.getName() );
            }
            
            // Copy dead properties VR -> WR
            j = rNrd.enumerateProperties();
            while( j.hasMoreElements() ) {
                NodeProperty p = (NodeProperty)j.nextElement();
                //if( p.isLiveProperty() )
                if (rRk.isSupportedLiveProperty(p.getPropertyName()))
                    continue;
                wrNrd.setProperty( p );
            }
            
            // update version's DAV:checkout-set property
            PropertyHelper.addHrefToProperty(rNrd, P_CHECKOUT_SET, wrUri);
            content.store( sToken, rNrds.getUri(), rNrd, null);
            
            // store changes
            content.create( sToken, wrUri, wrNrd, rNrc );
            content.store( sToken, wrUri, wrNrd, null );
            
            // Set status created
            resp.setStatus( WebdavStatus.SC_CREATED );
            return wrUri;
        }
        else {
            Domain.warn(
                "Do not know how to checkout a '"+rRk+"' resource" );
            resp.setStatus(WebdavStatus.SC_CONFLICT);
            throw new WebdavException( WebdavStatus.SC_CONFLICT );
        }
    }
    
    /**
     * Returns the ViolatedPrecondition if one of the precondition defined for
     * the <code>CHECKOUT</code> methods has been violated, otherwise
     * <code>null</code>.
     *
     * @param      cinNrds  the NodeRevisionDescriptors of the VR to checkout.
     * @param      cinNrd   the NodeRevisionDescriptor of the VR to checkout.
     * @param      isForkOk      indicates if <code>&lt;fork-ok&gt;</code> is set in
     *                           the request content.
     *
     * @return     the ViolatedPrecondition (if any).
     */
    protected ViolatedPrecondition getCheckoutPreconditionViolation(NodeRevisionDescriptors cinNrds, NodeRevisionDescriptor cinNrd, boolean isForkOk) throws IllegalArgumentException, IOException, JDOMException, SlideException {
        SlideToken stok = sToken;
        
        ViolatedPrecondition violatedPrecondition = null;
        
        NodeProperty checkoutForkProperty =cinNrd.getProperty(PN_CHECKOUT_FORK);
        if (checkoutForkProperty != null
             && !checkoutForkProperty.getValue().toString().equals("")) 
        {
            Element checkoutForkElement = pHelp.parsePropertyValue(checkoutForkProperty.getValue().toString());
            if (checkoutForkElement != null) {
                
                // check if the version has successors
                Enumeration successors = cinNrds.getSuccessors(cinNrd.getRevisionNumber());
                if ( successors.hasMoreElements()) {
                    
                    // check precondition C_CHECKOUT_OF_VERSION_WITH_DESCENDANT_IS_FORBIDDEN
                    if (E_FORBIDDEN.equals(checkoutForkElement.getName()))  {
                        return new ViolatedPrecondition(C_CHECKOUT_OF_VERSION_WITH_DESCENDANT_IS_FORBIDDEN, WebdavStatus.SC_FORBIDDEN);
                    }
                        
                        // check precondition C_CHECKOUT_OF_VERSION_WITH_DESCENDANT_IS_DISCOURAGED
                    else if (E_DISCOURAGED.equals(checkoutForkElement.getName()) && !isForkOk)  {
                        return new ViolatedPrecondition(C_CHECKOUT_OF_VERSION_WITH_DESCENDANT_IS_DISCOURAGED, WebdavStatus.SC_CONFLICT);
                    }
                }
                
                // check if the version is already checked out
                PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(stok, nsaToken, sConf);
                
                NodeProperty checkoutSetProp = propertyHelper.getProperty(PN_CHECKOUT_SET, cinNrds, cinNrd, slideContextPath);
                if( checkoutSetProp != null && checkoutSetProp.getValue() != null ) {
                    XMLValue checkoutSetValue = new XMLValue( checkoutSetProp.getValue().toString() );
                    if (checkoutSetValue.iterator().hasNext()) {
                        
                        // check precondition C_CHECKOUT_OF_CHECKED_OUT_VERSION_IS_FORBIDDEN
                        if (E_FORBIDDEN.equals(checkoutForkElement.getName()))  {
                            return new ViolatedPrecondition(C_CHECKOUT_OF_CHECKED_OUT_VERSION_IS_FORBIDDEN, WebdavStatus.SC_FORBIDDEN);
                        }
                            
                            // check precondition C_CHECKOUT_OF_CHECKED_OUT_VERSION_IS_DISCOURAGED
                        else if (E_DISCOURAGED.equals(checkoutForkElement.getName()) && !isForkOk)  {
                            return new ViolatedPrecondition(C_CHECKOUT_OF_CHECKED_OUT_VERSION_IS_DISCOURAGED, WebdavStatus.SC_CONFLICT);
                        }
                    }
                }
            }
        }
        
        return violatedPrecondition;
    }
    
    /**
     * Uncheckout the specified resource.
     *
     * @param     resourcePath  the path of the resource to uncheckout.
     */
    public void uncheckout(String resourcePath) throws SlideException, JDOMException, IOException, PreconditionViolationException  {
        
        NodeRevisionDescriptors rNrds = content.retrieve( sToken, resourcePath );
        NodeRevisionDescriptor rNrd = content.retrieve( sToken, rNrds );
        uncheckout( rNrds, rNrd);
    }
    
    /**
     * Uncheckout the specified resource.
     *
     * @param     rNrds  the NodeRevisionDescriptors of the resource to uncheckout.
     * @param     rNrd   the NodeRevisionDescriptor of the resource to uncheckout.
     */
    public void uncheckout( NodeRevisionDescriptors rNrds, NodeRevisionDescriptor rNrd)
        throws SlideException, JDOMException, IOException, PreconditionViolationException  {
        
        //Iterator i;
        String rUri = getUri( rNrds, rNrd );
        ResourceKind rRk = AbstractResourceKind.determineResourceKind( nsaToken, rNrds, rNrd );
        
        // check precondition C_MUST_BE_CHECKED_OUT_VERSION_CONTROLLED_RESOURCE
        if ( ! (rRk instanceof CheckedOutVersionControlled) ) {
            throw new PreconditionViolationException(new ViolatedPrecondition(C_MUST_BE_CHECKED_OUT_VERSION_CONTROLLED_RESOURCE,
                                                                              WebdavStatus.SC_CONFLICT),
                                                     rNrds.getUri());
        }
        
        if( !rRk.isSupportedMethod(req.getMethod()) ) {
            throw new MethodNotAllowedException( rRk );
        }
        
        // get checked-out VR
        NodeProperty coutProp = rNrd.getProperty(PN_CHECKED_OUT);
        String coutHref = getElementValue((String)coutProp.getValue());
        UriHandler coutUriHandler = UriHandler.getUriHandler(coutHref);
        String coutUri = coutUriHandler.getAssociatedHistoryUri();
        NodeRevisionNumber coutNrn = new NodeRevisionNumber(coutUriHandler.getVersionName());
        NodeRevisionDescriptors coutNrds = content.retrieve(sToken, coutUri);
        NodeRevisionDescriptor coutNrd = content.retrieve(sToken, coutNrds, coutNrn);
        //NodeRevisionContent coutNrc = content.retrieve( sToken, coutNrds, coutNrd );
        
        // update its DAV:checkout-set property
        if (!PropertyHelper.removeHrefFromProperty(coutNrd, P_CHECKOUT_SET, rUri)) {
            StringBuffer b = new StringBuffer("Invalid path");
            PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(sToken, nsaToken, sConf);
            NodeProperty checkoutSetProp = propertyHelper.getProperty(PN_CHECKOUT_SET, coutNrds, coutNrd, slideContextPath);
            if( checkoutSetProp != null && checkoutSetProp.getValue() != null ) {
                XMLValue checkoutSetValue = new XMLValue( checkoutSetProp.getValue().toString() );
                if (checkoutSetValue.iterator().hasNext()) {
                    b.append(" - please use "+checkoutSetValue.getTextValue()+" instead");
                }
            }
            throw new ConflictException(
                rUri, new SlideException(b.toString()));
        }
        content.store(sToken, coutNrds.getUri(), coutNrd, null);
        
        // update VCR to previous VR
        rNrd.removeProperty(PN_CHECKED_OUT);
        update( rNrds, rNrd, coutNrds, coutNrd );
        
        // restore some live properties
        restoreSpecificLiveProperties( rNrds, rNrd );
        
        // Store changes
        content.store( sToken, rNrds.getUri(), rNrd, null ); // revisionContent=null
        // remove the hidden 0.0 revision
        content.remove( sToken, rNrds.getUri(), NodeRevisionNumber.HIDDEN_0_0 );
    }
    
    /**
     * Checkin the specified resource (for both: checkout-in-place and working-resource features).
     * @param resourcePath the request URI
     * @param forkOk true, if the request body contained a DAV:fork-ok element
     * @param keepCheckedOut true, if the request body contained a DAV:keep-checked-out element
     * @param autoVersion true, if the checkin is due to auto-versioning
     * @return the URI of the created version resource
     * @throws SlideException
     * @throws JDOMException
     * @throws IOException
     * @throws PreconditionViolatedException
     */
    public String checkin( String resourcePath, boolean forkOk, boolean keepCheckedOut, boolean autoVersion )
        throws SlideException, JDOMException, IOException, PreconditionViolationException  {
        
        NodeRevisionDescriptors rNrds = content.retrieve( sToken, resourcePath );
        NodeRevisionDescriptor rNrd = content.retrieve( sToken, rNrds );
        return checkin( rNrds, rNrd, forkOk, keepCheckedOut, autoVersion );
    }
    
    /**
     * Checkin the specified resource (for both: checkout-in-place and working-resource features).
     * @param rNrds the revision descriptors instance
     * @param rNrd the revision descriptor instance
     * @param forkOk true, if the request body contained a DAV:fork-ok element
     * @param keepCheckedOut true, if the request body contained a DAV:keep-checked-out element
     * @param autoVersion true, if the checkin is due to auto-versioning
     * @return the URI of the created version resource
     * @throws SlideException
     * @throws JDOMException
     * @throws IOException
     * @throws PreconditionViolatedException
     */
    public String checkin( NodeRevisionDescriptors rNrds, NodeRevisionDescriptor rNrd,
                          boolean forkOk, boolean keepCheckedOut, boolean autoVersion )
        throws SlideException, JDOMException, IOException, PreconditionViolationException {
        
        Iterator i;
        Enumeration j;
        String rUri = getUri( rNrds, rNrd );
        ResourceKind rRk = AbstractResourceKind.determineResourceKind( nsaToken, rNrds, rNrd );
        
        if( !rRk.isSupportedMethod(req.getMethod()) ) {
            // check precondition C_MUST_BE_CHECKED_OUT
            if( (rRk instanceof CheckedInVersionControlled) ) {
                throw new PreconditionViolationException(
                    new ViolatedPrecondition(C_MUST_BE_CHECKED_OUT, WebdavStatus.SC_CONFLICT), rUri);
            }
            throw new MethodNotAllowedException( rRk );
        }
        
        if( rRk instanceof CheckedOutVersionControlled || rRk instanceof Working ) {
            
            boolean isWorkingResource = (rRk instanceof Working);
            
            NodeProperty coutProp = rNrd.getProperty(PN_CHECKED_OUT);
            NodeProperty predSetProp = rNrd.getProperty(PN_PREDECESSOR_SET);
            NodeProperty autoUpdProp = rNrd.getProperty(PN_AUTO_UPDATE);
            
            // prepare auto-update
            NodeRevisionDescriptors autoUpdNrds = null;
            NodeRevisionDescriptor autoUpdNrd = null;
            if( autoUpdProp != null ) {
                Element autoUpdElm = pHelp.parsePropertyValue( (String)autoUpdProp.getValue() );
                String autoUpdUri = autoUpdElm.getTextTrim();
                autoUpdNrds = content.retrieve( sToken, autoUpdUri );
                autoUpdNrd = content.retrieve( sToken, autoUpdNrds );
            }
            
            // Retrieve VHR
            Element coutElm = pHelp.parsePropertyValue( (String)coutProp.getValue() );
            String vrUriOld = coutElm.getTextTrim();
            UriHandler vrUhOld = UriHandler.getUriHandler( vrUriOld );
            NodeRevisionNumber vrNrnOld = new NodeRevisionNumber( vrUhOld.getVersionName() );
            String vhrUri = vrUhOld.getAssociatedHistoryUri();
            NodeRevisionDescriptors vhrNrds = content.retrieve( sToken, vhrUri );
            NodeRevisionDescriptor vhrNrd = content.retrieve( sToken, vhrNrds ); //vhrUri
            NodeProperty vSetProp = vhrNrd.getProperty(PN_VERSION_SET);
            
            // Retrieve old VR
            NodeRevisionDescriptor vrNrdOld =
                content.retrieve( sToken, vhrNrds, vrNrnOld ); // vrUriOld
            
            // update the old VR's DAV:checkout-set property
            if (!PropertyHelper.removeHrefFromProperty(vrNrdOld, P_CHECKOUT_SET, rUri)) {
                StringBuffer b = new StringBuffer("Invalid path");
                PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(sToken, nsaToken, sConf);
                NodeProperty checkoutSetProp = propertyHelper.getProperty(PN_CHECKOUT_SET, vhrNrds, vrNrdOld, slideContextPath);
                if( checkoutSetProp != null && checkoutSetProp.getValue() != null ) {
                    XMLValue checkoutSetValue = new XMLValue( checkoutSetProp.getValue().toString() );
                    if (checkoutSetValue.iterator().hasNext()) {
                        b.append(" - please use "+checkoutSetValue.getTextValue()+" instead");
                    }
                }
                throw new ConflictException(
                    rUri, new SlideException(b.toString()));
            }
            content.store(sToken, vhrNrds.getUri(), vrNrdOld, null);
            
            // check preconditions
            ViolatedPrecondition violatedPrecondition =
                getCheckinPreconditionViolation( predSetProp, vhrNrds, forkOk, autoUpdNrd );
            if (violatedPrecondition != null) {
                throw new PreconditionViolationException(violatedPrecondition, rUri);
            }
            
            // check forking
            String forkBranch = getForkBranch(predSetProp, vhrNrds, forkOk);
            NodeRevisionDescriptor vrNrdNew = null;
            if (forkBranch != null) {
                // Create a new branch
                NodeRevisionNumber branchedRevisionNumber =
                    content.fork(sToken, vhrNrds.getUri(), forkBranch, vrNrdOld);
                vhrNrds = content.retrieve( sToken, vhrUri );
                vrNrdNew = content.retrieve(sToken, vhrNrds, branchedRevisionNumber);
                vrNrdNew.setContentLength(rNrd.getContentLength());
            }
            else {
                // Create new VR in the MAIN branch
                vrNrdNew = new NodeRevisionDescriptor( rNrd.getContentLength() );
            }
            
            i = pHelp.createInitialProperties(VersionImpl.getInstance(), vhrUri).iterator();
            while( i.hasNext() )
                vrNrdNew.setProperty( (NodeProperty)i.next() );
            
            // Copy dead properties VCR --> VR-new
            j = rNrd.enumerateProperties();
            while( j.hasMoreElements() ) {
                NodeProperty p = (NodeProperty)j.nextElement();
                //if( p.isLiveProperty() )
                if (rRk.isSupportedLiveProperty(p.getPropertyName()))
                    continue;
                if( !vrNrdNew.exists(p.getPropertyName()))
                    vrNrdNew.setProperty( p );
            }
            // Copy specific live properties VCR/WR -> VR
            vrNrdNew.setContentType(rNrd.getContentType()); // P_GETCONTENTTYPE
            vrNrdNew.setContentLength(rNrd.getContentLength()); // P_GETCONTENTLENGTH
            vrNrdNew.setContentLanguage( rNrd.getContentLanguage() ); // P_GETCONTENTLANGUAGE
            String comment = (autoVersion ? "CREATED BY AUTO-VERSIONING. " : "");
            if( rNrd.exists(PN_COMMENT) )
                comment = (String)rNrd.getProperty(PN_COMMENT).getValue();
            vrNrdNew.setProperty(
                new NodeProperty(PN_COMMENT, comment) );
            
            vrNrdNew.setProperty( rNrd.getProperty(PN_CHECKOUT_FORK) );
            vrNrdNew.setProperty( rNrd.getProperty(PN_CHECKIN_FORK) );
            
            NodeRevisionContent rNrc = content.retrieve( sToken, rNrds, rNrd );
            
            if (forkBranch != null) {
                content.store(sToken, vhrUri, vrNrdNew, rNrc);
            }
            else {
                String branch = vrNrdOld.getBranchName();
                content.create( sToken, vhrUri, branch, vrNrdNew, rNrc );
            }
            
            // create new VR node
            String vrUriNew = vhrUri+"/"+vrNrdNew.getRevisionNumber().toString();
            UriHandler vrUhNew = UriHandler.getUriHandler( vrUriNew );
            SubjectNode vrNodeNew = new SubjectNode();
            structure.create( sToken, vrNodeNew, vrUriNew );
            
            // set specific properties
            if( keepCheckedOut ) {
                rNrd.setProperty(
                    new NodeProperty(PN_CHECKED_OUT, pHelp.createHrefValue(vrUriNew)) );
                rNrd.setProperty(
                    new NodeProperty(PN_PREDECESSOR_SET, "") );
                PropertyHelper.addHrefToProperty(rNrd, P_PREDECESSOR_SET, vrUriNew);
                PropertyHelper.addHrefToProperty(vrNrdNew, P_CHECKOUT_SET, rUri);
            }
            else {
                if( !isWorkingResource ) {
                    rNrd.removeProperty( coutProp );
                    rNrd.setProperty(
                        new NodeProperty(PN_CHECKED_IN, pHelp.createHrefValue(vrUriNew)) );
                    rNrd.removeProperty(PN_CHECKIN_LOCKTOKEN);
                    // retry with default (DAV:) namespace which was the
                    // former namespace of this property
                    rNrd.removeProperty(PN_CHECKIN_LOCKTOKEN_COMP);
                    rNrd.removeProperty(PN_PREDECESSOR_SET);
                    rNrd.removeProperty(PN_CHECKOUT_FORK);
                    rNrd.removeProperty(PN_CHECKIN_FORK);
                }
            }
            
            vhrNrd.setLastModified( new Date() ); // P_GETLASTMODIFIED
            vhrNrd.setProperty( new NodeProperty(
                                   PN_VERSION_SET, ((String)vSetProp.getValue())+pHelp.createHrefValue(vrUriNew)) );
            
            vrNrdNew.setName( rNrd.getName() ); // P_DISPLAYNAME
            vrNrdNew.setCreationDate( new Date() ); // P_CREATIONDATE
            vrNrdNew.setLastModified( new Date() ); // P_GETLASTMODIFIED
            vrNrdNew.setETag( PropertyHelper.computeEtag(vrUriNew, vrNrdNew)  ); // P_GETETAG
            vrNrdNew.setProperty(
                new NodeProperty(PN_VERSION_NAME, vrUhNew.getVersionName()) );
            vrNrdNew.setProperty(
                new NodeProperty(PN_PREDECESSOR_SET, predSetProp.getValue()) );

            // Store changes
            if( keepCheckedOut || !isWorkingResource ) {
                content.store( sToken, rUri, rNrd, null ); //revisionContent=null
                try {
                    // remove the hidden 0.0 revision and create new one if keepCheckedOut
                    content.remove( sToken, rUri, NodeRevisionNumber.HIDDEN_0_0 );
                }
                catch( RevisionDescriptorNotFoundException x ) {
                    // the implicit CHECKOUT from COPY (auto-versioning) does not create a
                    // backup descriptor.
                    Domain.info( "Checkin: no backup descriptor found at "+rUri );
                }
                if( keepCheckedOut )
                    backupSpecificLiveProperties( rNrds, rNrd );
            }
            else {
                // remove the WR
                macro.delete( sToken, rUri );
            }
            content.store( sToken, vhrUri, vhrNrd, null ); //revisionContent=null
            content.store( sToken, vhrUri, vrNrdNew, null ); //revisionContent=null
            
            // auto-update
            if( autoUpdNrd != null ) {
                update( autoUpdNrds, autoUpdNrd, vhrNrds, vrNrdNew );
            }
            
            // Set status created
            resp.setStatus( WebdavStatus.SC_CREATED );
            return vrUriNew;
        }
        else {
            Domain.warn(
                "Do not know how to checkout a '"+rRk+"' resource" );
            resp.setStatus(WebdavStatus.SC_CONFLICT);
            return null;
        }
    }
    
    private void setCreationUser(NodeRevisionDescriptor nrd) throws ServiceAccessException, ObjectNotFoundException {
        // Set the creation user
        String creationUser = ((SubjectNode)nsaToken.getSecurityHelper().getPrincipal(sToken)).getPath().lastSegment();
        nrd.setCreationUser(creationUser);
        nrd.setOwner(creationUser);
    }
    
    /**
     * Returns the ViolatedPrecondition if one of the precondition defined for
     * the <code>CHECKIN</code> methods has been violated, otherwise
     * <code>null</code>.
     *
     * @param      predSetProp   the <code>predecessor-set</code> NodeProperty
     *                           of the VCR to checkin.
     * @param      vhrNrds       the NodeRevisionDescriptors of the associated VHR.
     * @param      isForkOk      indicates if <code>&lt;fork-ok&gt;</code> is set in
     *                           the request content.
     * @param      autoUpdNrd         the NodeRevisionDescriptor of the VCR referenced via auto-update
     *                                if not null, indicates that a working resource is being checked-in
     *                                for which the auto-update property was set
     *
     * @return     the ViolatedPrecondition (if any).
     */
    protected ViolatedPrecondition getCheckinPreconditionViolation(NodeProperty predSetProp, NodeRevisionDescriptors vhrNrds, boolean isForkOk, NodeRevisionDescriptor autoUpdNrd ) throws LinkedObjectNotFoundException, ServiceAccessException, ObjectLockedException, RevisionDescriptorNotFoundException, JDOMException, IllegalArgumentException, ObjectNotFoundException, AccessDeniedException, IOException, VetoException {
        SlideToken stok = sToken;
        
        ViolatedPrecondition violatedPrecondition = null;
        
        if ( (predSetProp != null) && (predSetProp.getValue() != null) ) {
            XMLValue predecessors = new XMLValue( (String)predSetProp.getValue() );
            
            Iterator iterator = predecessors.iterator();
            while (iterator.hasNext()) {
                String href = ((Element)iterator.next()).getTextTrim();
                if (href != null) {
                    
                    UriHandler predecessorUriHandler = UriHandler.getUriHandler( href);
                    
                    // check precondition C_VERSION_HISTORY_IS_TREE
                    if ( !predecessorUriHandler.isVersionUri() ||
                        !vhrNrds.getUri().equals(predecessorUriHandler.getAssociatedHistoryUri()) ) {
                        return new ViolatedPrecondition(C_VERSION_HISTORY_IS_TREE, WebdavStatus.SC_FORBIDDEN);
                    }
                    
                    // check precondition C_CHECKIN_FORK_FORBIDDEN
                    NodeRevisionNumber predecessorNrn = new NodeRevisionNumber(predecessorUriHandler.getVersionName());
                    NodeRevisionDescriptor predecessorNrd = content.retrieve(stok,
                                                                             vhrNrds,
                                                                             predecessorNrn);
                    NodeProperty predecessorCheckinForkProperty = predecessorNrd.getProperty(PN_CHECKIN_FORK);
                    if (predecessorCheckinForkProperty != null
                            && !predecessorCheckinForkProperty.getValue().toString().equals("")) 
                    {
                        Enumeration predecessorSuccessors = vhrNrds.getSuccessors(predecessorNrn);
                        if ( (predecessorSuccessors.hasMoreElements()) &&
                             (predecessorCheckinForkProperty.getValue() != null) ) {
                            
                            String checkinFork = getElementName(predecessorCheckinForkProperty.getValue().toString());
                            
                            if (E_FORBIDDEN.equals(checkinFork)) {
                                return new ViolatedPrecondition(C_CHECKIN_FORK_FORBIDDEN, WebdavStatus.SC_FORBIDDEN);
                            }
                                
                                // check precondition C_CHECKIN_FORK_DISCOURAGED
                            else if (E_DISCOURAGED.equals(checkinFork) && !isForkOk ) {
                                return new ViolatedPrecondition(C_CHECKIN_FORK_DISCOURAGED, WebdavStatus.SC_CONFLICT);
                            }
                        }
                    }
                    
                    // check precondition C_NO_OVERWRITE_BY_AUTO_UPDATE
                    if( autoUpdNrd != null ) {
                        NodeProperty cinProp = autoUpdNrd.getProperty(PN_CHECKED_IN);
                        if( cinProp != null ) {
                            Element cinHrefElm = pHelp.parsePropertyValue( (String)cinProp.getValue() );
                            UriHandler cinUh = UriHandler.getUriHandler( cinHrefElm.getTextTrim() );
                            NodeRevisionNumber cinNrn = new NodeRevisionNumber( cinUh.getVersionName() );
                            if( !vhrNrds.getUri().equals(cinUh.getAssociatedHistoryUri()) ) {
                                // violation
                                return new ViolatedPrecondition(C_NO_OVERWRITE_BY_AUTO_UPDATE, WebdavStatus.SC_CONFLICT);
                            }
                            if( !vhrNrds.isAncestorDescendant(cinNrn, predecessorNrn) ) {
                                // violation
                                return new ViolatedPrecondition(C_NO_OVERWRITE_BY_AUTO_UPDATE, WebdavStatus.SC_CONFLICT);
                            }
                        }
                    }
                }
            }
        }
        return violatedPrecondition;
    }
    
    /**
     * Returns the ViolatedPrecondition if one of the precondition defined for
     * the <code>CHECKIN</code> methods has been violated, otherwise
     * <code>null</code>.
     *
     * @param      predSetProp   the <code>predecessor-set</code> NodeProperty
     *                           of the VCR to checkin.
     * @param      vhrNrds       the NodeRevisionDescriptors of the associated VHR.
     * @param      isForkOk      indicates if <code>&lt;fork-ok&gt;</code> is set in
     *                           the request content.
     *
     * @return     the violated precondition.
     */
    protected String getForkBranch(NodeProperty predSetProp, NodeRevisionDescriptors vhrNrds, boolean isForkOk) throws LinkedObjectNotFoundException, ServiceAccessException, ObjectLockedException, RevisionDescriptorNotFoundException, JDOMException, IllegalArgumentException, ObjectNotFoundException, AccessDeniedException, IOException, VetoException {
        
        String forkBranch = null;
        
        if ( (predSetProp != null) && (predSetProp.getValue() != null) ) {
            XMLValue predecessors = new XMLValue( (String)predSetProp.getValue() );
            
            Iterator iterator = predecessors.iterator();
            if (iterator.hasNext()) {
                String href = ((Element)iterator.next()).getTextTrim();
                if (href != null) {
                    
                    UriHandler predecessorUriHandler = UriHandler.getUriHandler( href);
                    
                    NodeRevisionNumber predecessorNrn = new NodeRevisionNumber(predecessorUriHandler.getVersionName());
                    NodeRevisionDescriptor predecessorNrd = content.retrieve(sToken,
                                                                             vhrNrds,
                                                                             predecessorNrn);
                    NodeProperty predecessorCheckinForkProperty = predecessorNrd.getProperty(PN_CHECKIN_FORK);
                    if (predecessorCheckinForkProperty != null) {
                        
                        Enumeration predecessorSuccessors = vhrNrds.getSuccessors(predecessorNrn);
                        if ( predecessorSuccessors.hasMoreElements() ) {
                            forkBranch = "branch_" + predecessorNrn.toString();
                        }
                    }
                    
                }
            }
        }
        return forkBranch;
    }
    
    /**
     * If the resource described by the given <code>resourceUri</code> is a
     * VCR this method returns the URI of the associated VR,
     * otherwise <code>null</code>
     *
     * @param      resourceUri  the URI of the resource.
     *
     * @return     the URI of the associated VR.
     *
     * @throws     SlideException
     */
    public String getUriOfAssociatedVR(String resourceUri) throws SlideException {
        return getUriOfAssociatedVR(nsaToken, sToken, content, resourceUri);
    }
    
    /**
     * Returns the URI of the resource defined by the given NodeRevisionDescriptor(s).
     *
     * @param      revisionDescriptors  the NodeRevisionDescriptors of the resource.
     * @param      revisionDescriptor   the NodeRevisionDescriptor of the resource.
     *
     * @return     the URI of the resource.
     *
     * @throws     SlideException
     */
    public String getUri(NodeRevisionDescriptors revisionDescriptors,
                         NodeRevisionDescriptor revisionDescriptor) throws SlideException {
        return getUri(nsaToken, sToken, content, revisionDescriptors, revisionDescriptor);
    }
    
    /**
     * Updates the VCR specified by <code>vcrPath</code> with the properties
     * and the content of the VR specified by the given <code>vrPath</code>.
     *
     * @param      vcrUri  the URI of the VCR to update.
     * @param      vrUri   the URI of the VR from which to update.
     *
     * @throws     SlideException
     */
    public void update(String vcrUri, String vrUri) throws SlideException {
        
        NodeRevisionDescriptors vcrRevisionDescriptors = content.retrieve( sToken,vcrUri );
        NodeRevisionDescriptor vcrRevisionDescriptor = content.retrieve( sToken, vcrRevisionDescriptors);
        
        NodeRevisionDescriptors vrRevisionDescriptors = content.retrieve( sToken, vrUri );
        NodeRevisionDescriptor vrRevisionDescriptor = content.retrieve( sToken, vrRevisionDescriptors); // vrUri
        
        update(vcrRevisionDescriptors, vcrRevisionDescriptor, vrRevisionDescriptors, vrRevisionDescriptor);
    }
    
    /**
     * Updates the VCR specified by <code>vcrRevisionDescriptors</code> and
     * <code>vcrRevisionDescriptors</code> with the properties and the content
     * of the VR specified by the given <code>vrRevisionDescriptors</code>
     * and <code>vrRevisionDescriptor</code>.
     *
     * @pre        (AbstractResourceKind.determineResourceKind(vrRevisionDescriptor) instanceof Version)
     *
     * @param      vcrRevisionDescriptors  the NodeRevisionDescriptors of the VCR to update.
     * @param      vcrRevisionDescriptor   the NodeRevisionDescriptor of the VCR to update.
     * @param      vrRevisionDescriptors   the NodeRevisionDescriptors of the VR from
     *                                     which to update.
     * @param      vrRevisionDescriptor    the NodeRevisionDescriptor of the VR from
     *                                     which to update.
     *
     * @throws     SlideException
     */
    public void update(NodeRevisionDescriptors vcrRevisionDescriptors, NodeRevisionDescriptor vcrRevisionDescriptor, NodeRevisionDescriptors vrRevisionDescriptors, NodeRevisionDescriptor vrRevisionDescriptor) throws SlideException {
        
        // ***************************************
        // TODO:
        // 1) Preconditions; Problem: not specified formally.
        // ***************************************
        
        //ResourceKind vrResourceKind = VersionImpl.getInstance();
        //ResourceKind cinvcrResourceKind = CheckedInVersionControlledImpl.getInstance();
        String vcrUri = getUri(vcrRevisionDescriptors, vcrRevisionDescriptor);
        Enumeration propertyEnum;
        ResourceKind resourceKind = AbstractResourceKind.determineResourceKind( 
                nsaToken, vcrRevisionDescriptors, vcrRevisionDescriptor);
        
        // Remove all VCR dead properties first
        propertyEnum = vcrRevisionDescriptor.enumerateProperties();
        while (propertyEnum.hasMoreElements()) {
            NodeProperty p = (NodeProperty)propertyEnum.nextElement();
            //if( p.isLiveProperty() )
            if (resourceKind.isSupportedLiveProperty(p.getPropertyName()))
                continue;
            vcrRevisionDescriptor.removeProperty(p);
        }
        
        // Copy all dead properties of VR to VCR
        propertyEnum = vrRevisionDescriptor.enumerateProperties();
        while (propertyEnum.hasMoreElements()) {
            NodeProperty p = (NodeProperty)propertyEnum.nextElement();
            //if( !p.isLiveProperty() ) {
            if (resourceKind.isSupportedLiveProperty(p.getPropertyName())) {
                vcrRevisionDescriptor.setProperty(p);
            }
        }
        
        // update specific live properties
        String vrUri = getUri(vrRevisionDescriptors, vrRevisionDescriptor);
        vcrRevisionDescriptor.setProperty(new NodeProperty(PN_CHECKED_IN,
                                                           pHelp.createHrefValue(vrUri)) );
        vcrRevisionDescriptor.setLastModified(new Date());
        vcrRevisionDescriptor.setContentLength(vrRevisionDescriptor.getContentLength());
        vcrRevisionDescriptor.setContentType(vrRevisionDescriptor.getContentType());
        vcrRevisionDescriptor.setContentLanguage(vrRevisionDescriptor.getContentLanguage());
        vcrRevisionDescriptor.setETag(PropertyHelper.computeEtag(vcrRevisionDescriptors.getUri(), vcrRevisionDescriptor) );
        
        // set workspace
        setWorkspaceProperty( vcrUri, vcrRevisionDescriptor );
        
        // get the VR content
        NodeRevisionContent vrContent = content.retrieve(sToken, vrRevisionDescriptors, vrRevisionDescriptor);
        
        // store the content
        content.store( sToken, vcrUri, vcrRevisionDescriptor, vrContent );
    }
    
    /**
     * Backups specific live properties of the given <code>revisionDescriptor</code>
     * at the 0.0 revision of the NodeRevisionDescriptors of the VCR. A good idea seems to
     * be to backup the non-protected live properties.
     *
     * @param      rNrds         the NodeRevisionDescriptors of the
     *                           VCR to backup.
     * @param      rNrd          the NodeRevisionDescriptor of the
     *                           VCR to backup.
     *
     * @throws     SlideException
     */
    protected void backupSpecificLiveProperties(NodeRevisionDescriptors rNrds, NodeRevisionDescriptor rNrd) throws SlideException {
        
        NodeRevisionDescriptor backupNrd =
            new NodeRevisionDescriptor( NodeRevisionNumber.HIDDEN_0_0, "backup");
        
        NodeProperty p;
        
        p = rNrd.getProperty( PN_AUTO_VERSION );
        if( p != null )
            backupNrd.setProperty( p );
        
        p = rNrd.getProperty( PN_COMMENT );
        if( p != null )
            backupNrd.setProperty( p );
        
        p = rNrd.getProperty( PN_DISPLAYNAME );
        if( p != null )
            backupNrd.setProperty( p );
        
        p = rNrd.getProperty( PN_CREATOR_DISPLAYNAME );
        if( p != null )
            backupNrd.setProperty( p );
        
        try {
            content.retrieve( sToken, rNrds, NodeRevisionNumber.HIDDEN_0_0 );
            content.store( sToken, rNrds.getUri(), backupNrd, null );
        }
        catch (RevisionDescriptorNotFoundException e) {
            content.create( sToken, rNrds.getUri(), null,  backupNrd, null ); // branch=null, revisionContent=null
        }
    }
    
    /**
     * Restores specific live properties of the given <code>revisionDescriptor</code>
     * from the hidden 0.0 revision of the NodeRevisionDescriptors of the VCR.
     *
     * @param      rNrds         the NodeRevisionDescriptors of the
     *                           VCR to restore.
     * @param      rNrd          the NodeRevisionDescriptor of the
     *                           VCR to restore.
     *
     * @throws     SlideException
     */
    protected void restoreSpecificLiveProperties(NodeRevisionDescriptors rNrds, NodeRevisionDescriptor rNrd) throws SlideException {
        
        NodeRevisionDescriptor backupNrd =
            content.retrieve(sToken, rNrds, NodeRevisionNumber.HIDDEN_0_0);
        
        NodeProperty p;
        
        p = backupNrd.getProperty( PN_AUTO_VERSION );
        if( p != null )
            rNrd.setProperty( p );
        
        p = backupNrd.getProperty( PN_COMMENT );
        if( p != null )
            rNrd.setProperty( p );
        
        p = backupNrd.getProperty( PN_DISPLAYNAME );
        if( p != null )
            rNrd.setProperty( p );
        
        p = backupNrd.getProperty( PN_CREATOR_DISPLAYNAME );
        if( p != null )
            rNrd.setProperty( p );
        
        content.store(sToken, rNrds.getUri(), rNrd, null);
    }
    
    /**
     * Returns the name of the element that represents the value of the
     * <code>&lt;auto-version&gt;</code> property of the given
     * <code>revisionDescriptor</code> (e.g. <code>checkout-checkin</code>).
     *
     * @param      revisionDescriptor  the NodeRevisionDescriptor for which to
     *                                 return the value of the
     *                                 <code>&lt;auto-version&gt;</code> property.
     *
     * @return     the value of the <code>&lt;auto-version&gt;</code> property
     *             of the given <code>revisionDescriptor</code>.
     */
    public String getAutoVersionElementName(NodeRevisionDescriptor revisionDescriptor) {
        
        String autoVersionValue = null;
        NodeProperty autoVersionProperty = revisionDescriptor.getProperty(DeltavConstants.PN_AUTO_VERSION);
        if ( (autoVersionProperty != null) && (autoVersionProperty.getValue() != null) ) {
            if (autoVersionProperty.getValue().toString().length() > 0) {
                autoVersionValue = getElementName(autoVersionProperty.getValue().toString());
            }
            else {
                autoVersionValue = "";
            }
        }
        return autoVersionValue;
    }
    
    /**
     * Indicates if the (VCR) reource be checked out prior to modifying it
     * depending on its <code>&lt;auto-version&gt;</code> property.
     *
     * @param      resourceUri  the URI of the resource.
     *
     * @return     <code>true</code> if the resource must be checked out prior to
     *             modifying it.
     *
     * @throws     SlideException
     */
    public boolean mustCheckoutAutoVersionedVCR(String resourceUri) throws SlideException {
        NodeRevisionDescriptors vcrRevisionDescriptors = content.retrieve(sToken,resourceUri);
        NodeRevisionDescriptor vcrRevisionDescriptor = content.retrieve( sToken, vcrRevisionDescriptors);
        return mustCheckoutAutoVersionedVCR(vcrRevisionDescriptors, vcrRevisionDescriptor);
    }
    
    /**
     * Indicates if the (VCR) reource be checked out prior to modifying it
     * depending on its <code>&lt;auto-version&gt;</code> property.
     *
     * @param      revisionDescriptors  the NodeRevisionDescriptors of the resource.
     * @param      revisionDescriptor  the NodeRevisionDescriptor of the resource.
     *
     * @return     <code>true</code> if the resource must be checked out prior to
     *             modifying it.
     */
    public boolean mustCheckoutAutoVersionedVCR(NodeRevisionDescriptors revisionDescriptors, NodeRevisionDescriptor revisionDescriptor) {
        
        String autoVersionValue = getAutoVersionElementName(revisionDescriptor);
        return ( (autoVersionValue != null) &&
                    ( DeltavConstants.E_CHECKOUT_CHECKIN.equals(autoVersionValue) ||
                      DeltavConstants.E_CHECKOUT_UNLOCKED_CHECKIN.equals(autoVersionValue) ||
                      DeltavConstants.E_CHECKOUT.equals(autoVersionValue) ||
                      DeltavConstants.E_CHECKOUT_IGNORE_UNLOCK.equals(autoVersionValue) ||
                      DeltavConstants.E_LOCKED_CHECKOUT.equals(autoVersionValue) ) );
    }
    
    /**
     * Indicates if the (VCR) reource be checked in after modifying it
     * depending on its <code>&lt;auto-version&gt;</code> property.
     *
     * @param      slideToken           the SlideToken to use.
     * @param      resourceUri  the URI of the resource.
     *
     * @return     <code>true</code> if the resource must be checked in after
     *             modifying it.
     *
     * @throws    SlideException
     */
    public boolean mustCheckinAutoVersionedVCR(SlideToken slideToken, String resourceUri) throws SlideException {
        NodeRevisionDescriptors vcrRevisionDescriptors = content.retrieve(sToken,resourceUri);
        NodeRevisionDescriptor vcrRevisionDescriptor = content.retrieve( sToken, vcrRevisionDescriptors);
        return mustCheckinAutoVersionedVCR(slideToken, vcrRevisionDescriptors, vcrRevisionDescriptor);
    }
    
    /**
     * Indicates if the (VCR) reource be checked in after modifying it
     * depending on its <code>&lt;auto-version&gt;</code> property.
     *
     * @param      slideToken           the SlideToken to use.
     * @param      revisionDescriptors  the NodeRevisionDescriptors of the resource.
     * @param      revisionDescriptor  the NodeRevisionDescriptor of the resource.
     *
     * @return     <code>true</code> if the resource must be checked in after
     *             modifying it.
     */
    public boolean mustCheckinAutoVersionedVCR(SlideToken slideToken, NodeRevisionDescriptors revisionDescriptors, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException {
        
        boolean checkin = false;
        String autoVersionValue = getAutoVersionElementName(revisionDescriptor);
        if (autoVersionValue != null) {
            checkin = DeltavConstants.E_CHECKOUT_CHECKIN.equals(autoVersionValue);
            if ( !checkin && DeltavConstants.E_CHECKOUT_UNLOCKED_CHECKIN.equals(autoVersionValue)) {
                checkin = ! isWriteLocked(slideToken, revisionDescriptors);
            }
        }
        return checkin;
    }
    
    /**
     * Indicates if the resource specified by the given NodeRevisionDescriptors
     * is write locked. Reads all URLs in read only mode
     *
     * @param      slideToken           the SlideToken to use.
     * @param      revisionDescriptors  the NodeRevisionDescriptors of the resource.
     *
     * @return     <code>true</code> if the resource is write locked.
     */
    public boolean isWriteLocked(SlideToken slideToken, NodeRevisionDescriptors revisionDescriptors)
        throws ServiceAccessException {
        return (getWriteLock(slideToken, revisionDescriptors) != null);
    }
    
    /**
     * Returns the write lock of the resource specified by the given
     * NodeRevisionDescriptors if one exists, otherwise <code>null</code>.
     *
     * @param      slideToken           the SlideToken to use.
     * @param      revisionDescriptors  the NodeRevisionDescriptors of the resource.
     *
     * @return     the write lock of the resource.
     */
    private NodeLock getWriteLock(SlideToken slideToken, NodeRevisionDescriptors revisionDescriptors)
        throws ServiceAccessException {
        
        NodeLock writeLock = null;
        try {
            Enumeration lockEnum = lock.enumerateLocks(slideToken, revisionDescriptors.getUri());
            if (lockEnum != null && lockEnum.hasMoreElements()) {
                // there are no other types of locks beside write locks ... so take the first one if there
                writeLock = (NodeLock)lockEnum.nextElement();
            }
        }
        catch (ObjectNotFoundException e) {}
        catch (LockTokenNotFoundException e) {}
        
        return writeLock;
    }
    
    
    /**
     * Expects a String containing an XML Element like
     * <code>&lt;example&gt;value&lt;/example&gt;</code>
     * and returns the text <code>value</code> of this element.
     * If the String is not of the expected format, <code>null</code> is returned.
     *
     * @pre        true
     * @post       true
     *
     * @param      elementString  the String containing an XML Element like
     *                            <code>&lt;example&gt;value&lt;/example&gt;</code>.
     *
     * @return     the text value of the Element given by the XML String.
     */
    public static String getElementValue(String elementString) {
        
        String text = null;
        Element element = getElement(elementString);
        if (element != null) {
            text = element.getText();
        }
        return text;
    }
    
    /**
     * Expects a String containing an XML Element and returns the
     * <code>name</code> of this element.
     * If the String is not of the expected format, <code>null</code> is returned.
     *
     * @pre        true
     * @post       true
     *
     * @param      elementString  the String containing an XML Element like
     *                            <code>&lt;example&gt;value&lt;/example&gt;</code>.
     *
     * @return     the name of the Element given by the XML String.
     */
    public static String getElementName(String elementString) {
        
        String name = null;
        Element element = getElement(elementString);
        if (element != null) {
            name = element.getName();
        }
        return name;
    }
    
    /**
     * Expects a String containing at least one XML Element and returns this Element.
     * If the String is not of the expected format, <code>null</code> is returned.
     *
     * @pre        true
     * @post       true
     *
     * @param      elementString  the String containing an XML Element.
     *
     * @return     the Element given by the XML String.
     */
    public static Element getElement(String elementString) {
        
        Element element = null;
        try {
            Document document = getSAXBuilder().build(new StringReader(elementString));
            element = document.getRootElement();
        }
        catch (JDOMException e) {}
        catch (Exception e) {
            e.printStackTrace();
        }
        return element;
    }
    
    /**
     * Returns the SAXBuilder used by various methods to create JDOM Documents.
     *
     * @return     the SAXBuilder used to create JDOM Documents.
     */
    protected static SAXBuilder getSAXBuilder() {
        if (saxBuilder == null) {
            saxBuilder = new SAXBuilder();
        }
        return saxBuilder;
    }
    
    /**
     * Returns slide Uri determinating the NodeRevisionDescriptors and
     * NodeRevisionDescriptor associated with the given <code>resourcePath</code>.
     * If the given <code>label</code> is not <code>null</code>, and the
     * <code>resourcePath</code> identifies a VCR, the revision with that label
     * of the associated history is returned.
     *
     * @param      nsaToken      the NamespaceAccessToken to use.
     * @param      sToken        the SlideToken to use.
     * @param      content       the Content helper to use.
     * @param      resourcePath  the path of the resource for which to retrieve
     *                           the SlideResource.
     * @param      label         the label of the revision to return. May be
     *                           <code>null</code>.
     *
     * @return     slide Uri determinating the NodeRevisionDescriptors and
     *             NodeRevisionDescriptor associated with the given
     *             <code>resourcePath</code>.
     *
     * @throws     SlideException
     * @throws     LabeledRevisionNotFoundException if no revision with the specified
     *                                              label was found.
     */
    private static String getLabeledResourceUri(NamespaceAccessToken nsaToken, SlideToken sToken, Content content, String resourcePath, String label) throws SlideException, LabeledRevisionNotFoundException {
        
        NodeRevisionDescriptors revisionDescriptors =
            content.retrieve( sToken, resourcePath );
        NodeRevisionDescriptor revisionDescriptor =
            content.retrieve( sToken, revisionDescriptors);
        ResourceKind resourceKind = AbstractResourceKind.determineResourceKind( nsaToken, resourcePath, revisionDescriptor);
        
        if ( (resourceKind instanceof VersionControlled) && (label != null) ) {
            String vrUri = getUriOfAssociatedVR(nsaToken, sToken, content, revisionDescriptors.getUri());
            UriHandler vrUriHandler = UriHandler.getUriHandler(vrUri);
            String historyUri = vrUriHandler.getAssociatedHistoryUri();
            revisionDescriptors = content.retrieve(sToken, historyUri);
            revisionDescriptor = retrieveLabeledRevision(nsaToken, sToken, content, historyUri, label);
        }
        return getUri(nsaToken, sToken, content, revisionDescriptors, revisionDescriptor);
    }
    
    
    /**
     * If the <code>resourcePath</code> identifies a VHR, the associated revision
     * with the given <code>label</code> is returned. If the <code>resourcePath</code>
     * does not identify a VHR , <code>null</code> is returned.
     *
     * @param      nsaToken      the NamespaceAccessToken to use.
     * @param      sToken        the SlideToken to use.
     * @param      content       the Content helper to use.
     * @param      historyUri    the path of the resource for which to retrieve
     *                           the NRD.
     * @param      label         the label of the revision to return.
     *
     * @return     the associated revision with the given <code>label</code>.
     *
     * @throws     SlideException
     * @throws     LabeledRevisionNotFoundException if no revision with the specified
     *                                              label was found.
     */
    public static NodeRevisionDescriptor retrieveLabeledRevision(NamespaceAccessToken nsaToken, SlideToken sToken, Content content, String historyUri, String label) throws SlideException, LabeledRevisionNotFoundException {
        
        NodeRevisionDescriptor labeledRevision = null;
        
        UriHandler historyUriHandler = UriHandler.getUriHandler(historyUri);
        if (historyUriHandler.isHistoryUri()) {
            NodeRevisionDescriptors historyNrds = content.retrieve(sToken, historyUri);
            NodeRevisionDescriptor historyNrd =
                content.retrieve(sToken, historyNrds, NodeRevisionNumber.HIDDEN_0_0);
            NodeProperty versionSet = historyNrd.getProperty(PN_VERSION_SET);
            try {
                XMLValue versionSetValue = new XMLValue(versionSet.getValue().toString());
                NodeRevisionDescriptor vrNrd = null;
                NodeProperty labelNameSetProperty = null;
                String labelNameSetString = null;
                Iterator versionSetIterator = versionSetValue.iterator();
                String vrUri = null;
                UriHandler vrUriHandler = null;
                boolean found = false;
                while ( !found && versionSetIterator.hasNext() ) {
                    vrUri = ((Element)versionSetIterator.next()).getText();
                    vrUriHandler = UriHandler.getUriHandler(vrUri);
                    NodeRevisionNumber vrRevisionNumber = new NodeRevisionNumber(vrUriHandler.getVersionName());
                    vrNrd = content.retrieve(sToken, historyNrds, vrRevisionNumber);
                    labelNameSetProperty = vrNrd.getProperty(PN_LABEL_NAME_SET);
                    if ( (labelNameSetProperty != null) && (labelNameSetProperty.getValue() != null) ) {
                        labelNameSetString = labelNameSetProperty.getValue().toString();
                        if (labelNameSetString != null) {
                            XMLValue labelNameSet = new XMLValue(labelNameSetString);
                            Iterator labelNameSetIterator = labelNameSet.iterator();
                            while ( !found && labelNameSetIterator.hasNext() ) {
                                found = label.equals(((Element)labelNameSetIterator.next()).getText());
                            }
                        }
                    }
                }
                if (found) {
                    labeledRevision = vrNrd;
                }
                else {
                    throw new LabeledRevisionNotFoundException(historyUri, label);
                }
            }
            catch (JDOMException e) {}
            catch (IllegalArgumentException e) {}
        }
        return labeledRevision;
    }
    
    
    /**
     * If the resource described by the given <code>resourceUri</code> is a
     * VCR this method returns the URI of the associated VR,
     * otherwise <code>null</code>
     *
     * @param      nsaToken      the NamespaceAccessToken to use.
     * @param      sToken        the SlideToken to use.
     * @param      content       the Content helper to use.
     * @param      resourceUri   the URI of the resource.
     *
     * @return     the URI of the associated VR.
     *
     * @throws     SlideException
     */
    public static String getUriOfAssociatedVR(NamespaceAccessToken nsaToken,
                                              SlideToken sToken,
                                              Content content,
                                              String resourceUri) throws SlideException {
        
        String vrUri = null;
        
        NodeRevisionDescriptors revisionDescriptors = content.retrieve(sToken, resourceUri);
        if (!revisionDescriptors.isVersioned()) {
            NodeRevisionDescriptor revisionDescriptor =
                content.retrieve( sToken, revisionDescriptors);
            NodeProperty property = revisionDescriptor.getProperty(PN_CHECKED_OUT);
            if ( (property == null) || (property.getValue() == null) ) {
                property = revisionDescriptor.getProperty(PN_CHECKED_IN);
            }
            if ( (property != null) && (property.getValue() != null) ) {
                
                try {
                    XMLValue xmlValue = new XMLValue(property.getValue().toString());
                    Iterator iterator = xmlValue.iterator();
                    if (iterator.hasNext()) {
                        Element element = (Element)iterator.next();
                        vrUri = element.getText();
                    }
                }
                catch (JDOMException e) {}
                catch (IllegalArgumentException e) {}
            }
        }
        
        return vrUri;
    }
    
    
    /**
     * Returns the URI of the resource defined by the given NodeRevisionDescriptor(s).
     *
     *
     * @param      nsaToken             the NamespaceAccessToken to use.
     * @param      sToken               the SlideToken to use.
     * @param      content              the Content helper to use.
     * @param      revisionDescriptors  the NodeRevisionDescriptors of the resource.
     * @param      revisionDescriptor   the NodeRevisionDescriptor of the resource.
     *
     * @return     the URI of the resource.
     *
     * @throws     SlideException
     */
    public static String getUri(NamespaceAccessToken nsaToken,
                                SlideToken sToken,
                                Content content,
                                NodeRevisionDescriptors revisionDescriptors,
                                NodeRevisionDescriptor revisionDescriptor) throws SlideException {
        
        StringBuffer uri = new StringBuffer();
        UriHandler uriHandler = UriHandler.getUriHandler(revisionDescriptors.getUri());
        if ( ! uriHandler.isHistoryUri() ) {
            // any resource
            uri.append(revisionDescriptors.getUri());
        }
        else {
            if (revisionDescriptor.getRevisionNumber().equals(NodeRevisionNumber.HIDDEN_0_0)) {
                // history resource
                uri.append(revisionDescriptors.getUri());
            }
            else {
                // version resource
                uri.append(revisionDescriptors.getUri());
                if ( ! revisionDescriptors.getUri().endsWith("/") ) {
                    uri.append("/");
                }
                uri.append(revisionDescriptor.getRevisionNumber().toString());
            }
        }
        return uri.toString();
    }
}








