/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/CopyMethod.java,v 1.74 2005/02/25 17:03:52 luetzkendorf Exp $
 * $Revision: 1.74 $
 * $Date: 2005/02/25 17:03:52 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.apache.slide.webdav.method;

import java.util.*;
import org.apache.slide.content.*;
import org.apache.slide.macro.*;
import org.apache.slide.webdav.util.*;

import java.io.IOException;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.event.EventDispatcher;
import org.apache.slide.event.VetoException;
import org.apache.slide.lock.ObjectLockedException;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.LinkedObjectNotFoundException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
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.event.WebdavEvent;
import org.apache.slide.webdav.util.resourcekind.AbstractResourceKind;
import org.apache.slide.webdav.util.resourcekind.CheckedInVersionControlled;
import org.apache.slide.webdav.util.resourcekind.ResourceKind;
import org.apache.slide.webdav.util.resourcekind.VersionableImpl;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;

/**
 * COPY Method.
 *
 */
public class CopyMethod extends AbstractMultistatusResponseMethod implements DeltavConstants,
        AclConstants, BindConstants, CopyListener, DeleteListener, CopyRouteRedirector,
        WriteMethod, FineGrainedLockingMethod {
        
    /**
     * Maps the URI of a destination to its descriptor.
     * Used by {@link #beforeCopy beforeCopy()} and {@link #afterCopy afterCopy()}.
     */
    protected Map destinationDescriptorMap = new HashMap();
    
    /**
     * Maps the URI of a destination to its backup descriptor.
     * Used by {@link #beforeCopy beforeCopy()} and {@link #afterCopy afterCopy()}.
     */
    protected Map destinationBackupDescriptorMap = new HashMap();
    
    /**
     * The value of the <code>Label</code> header.
     */
    protected String labelHeader = null;
    
    private MacroParameters macroParameters = null;
    
    
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Constructor.
     *
     * @param token     the token for accessing the namespace
     * @param config    configuration of the WebDAV servlet
     */
    public CopyMethod(NamespaceAccessToken token, WebdavServletConfig config) {
        super(token, config);
    }
    
    /**
     * @see org.apache.slide.webdav.method.FineGrainedLockingMethod#acquireFineGrainLocks()
     */
    public void acquireFineGrainLocks() {
        acquireStandardLocks(destinationUri);
        acquireStandardLocks(sourceUri);
        // changes this and parent
        acquireLock(destinationUri, WRITE_LOCK); // effectively means recursive write locks 
        acquireParentLock(destinationUri, WRITE_LOCK);
        // XXX fake resursivly lock to source uri using write lock
        acquireLock(sourceUri, WRITE_LOCK);
    }

    // ------------------------------------------------------ Protected Methods
    
    /**
     * Parse request.
     *
     * @exception WebdavException Does not happen
     */
    protected void parseRequest() throws WebdavException {
        super.parseRequest();
        
        labelHeader = WebdavUtils.fixTomcatHeader(requestHeaders.getLabel(), "UTF-8");
    }
    
    /**
     * Execute request.
     *
     * @exception WebdavException Unrecoverable error occured while copying
     */
    protected void executeRequest()
        throws WebdavException, IOException {
        
        // Prevent dirty reads
        slideToken.setForceStoreEnlistment(true);
        
        boolean isCollection = isCollection(sourceUri);
        
        // check lock-null resources
        try {
            if (isLockNull(sourceUri)) {
                int statusCode = WebdavStatus.SC_NOT_FOUND;
                sendError( statusCode, "lock-null resource", new Object[]{sourceUri} );
                throw new WebdavException( statusCode );
            }
        }
        catch (ServiceAccessException e) {
            int statusCode = getErrorCode((Exception)e);
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        }
        
        // check destination URI
        UriHandler destUh = UriHandler.getUriHandler(destinationUri);
        
        if (destUh.isRestrictedUri()) {
            boolean sendError = true;
            if( destUh.isWorkspaceUri() || destUh.isWorkingresourceUri() ) {
                // COPY on existing WSs or WRs is *not* restricted !!!
                try {
                    content.retrieve(slideToken, destinationUri);
                    sendError = false;
                }
                catch( SlideException x ) {
                    int statusCode = getErrorCode(x);
                    sendError( statusCode, x );
                    throw new WebdavException( statusCode );
                }
            }
            if( sendError ) {
                int statusCode = WebdavStatus.SC_FORBIDDEN;
                sendError( statusCode, getClass().getName()+".restrictedDestinationUri", new Object[]{destinationUri} );
                throw new WebdavException( statusCode );
            }
        }
        
        try {
            // compare resource types of source and destination
            int depth = requestHeaders.getDepth(INFINITY);
            if (depth != 0 && depth != INFINITY) {
                int sc = WebdavStatus.SC_PRECONDITION_FAILED;
                sendError( sc, "Invalid header Depth: "+depth );
                throw new WebdavException( sc );
            }
            
            boolean recursive = (depth == INFINITY);
            
            if (overwrite) {
                macroParameters = new MacroParameters(recursive, true, true);
            } else {
                macroParameters = new MacroParameters(recursive, false, false);
            }
            
            boolean destinationExistsBefore = exists( destinationUri );
            
            if (!overwrite && destinationExistsBefore) {
                int statusCode = WebdavStatus.SC_PRECONDITION_FAILED;
                sendError( statusCode, getClass().getName()+".noOverwrite", new Object[]{destinationUri} );
                throw new WebdavException( statusCode );
            }
            
            if ( WebdavEvent.COPY.isEnabled() ) EventDispatcher.getInstance().fireVetoableEvent(WebdavEvent.COPY, new WebdavEvent(this));
            
            macro.copy(slideToken, sourceUri, destinationUri, macroParameters, this, this, null, this);
            
            if (overwrite && destinationExistsBefore) {
                resp.setStatus(WebdavStatus.SC_NO_CONTENT);
            } else {
                resp.setStatus(WebdavStatus.SC_CREATED);
            }
        } catch (MacroException e) {
            if(generateMultiStatusResponse(isCollection, e, requestUri)) {
                String errorMessage = generateErrorMessage(e);
                // Write it on the servlet writer
                resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
                try {
                    resp.setContentType(TEXT_XML_UTF_8);
                    resp.getWriter().write(errorMessage);
                } catch(IOException ex) {
                    // Critical error ... Servlet container is dead or something
                    int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
                    sendError( statusCode, e );
                    throw new WebdavException( statusCode );
                }
            } else {
                // Returning 207 on non-collection requests is generally
                // considered bad. So let's not do it, since this way
                // makes clients generally behave better.
                SlideException exception = (SlideException)e.enumerateExceptions().nextElement();
                if (exception instanceof PreconditionViolationException) {
                    try {
                        sendPreconditionViolation((PreconditionViolationException)exception);
                    } catch(IOException ex) {
                        // Critical error ... Servlet container is dead or something
                        int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
                        sendError( statusCode, e );
                        throw new WebdavException( statusCode );
                    }
                }
                else {
                    int statusCode = getErrorCode( exception );
                    sendError( statusCode, exception );
                    throw new WebdavException( statusCode );
                }
            }
            //
            // make sure the transaction is aborted
            // throw any WebDAV exception to indicate the transaction wants to be aborted
            //
            throw new WebdavException(WebdavStatus.SC_ACCEPTED, false);
        }
        catch (WebdavException e) {
            throw e;
        }
        catch (SlideException e) {
            int statusCode = getErrorCode( e );
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        }
    }
    
    /**
     * Get return status based on exception type.
     */
    protected int getErrorCode(SlideException ex) {
        try {
            throw ex;
        } catch(RevisionNotFoundException e) {
            return WebdavStatus.SC_NOT_FOUND;
        } catch (SlideException e) {
            return super.getErrorCode(e);
        }
    }
    
    /**
     * Restores all live properties that should not be copied.
     *
     * @param      destinationRevisionDescriptor          the descriptor to restore.
     * @param      existingDestinationRevisionDescriptor  the descriptor that has been overwritten.
     */
    private void restoreLiveProperties(String destinationUri, NodeRevisionDescriptor destinationNrd, NodeRevisionDescriptor existingNrd) {
        
        // remove all live properties
        Enumeration propertyEnum = destinationNrd.enumerateProperties();
        NodeProperty property = null;
        while (propertyEnum.hasMoreElements()) {
            property = (NodeProperty)propertyEnum.nextElement();
            if (isLivePropertyToRestore(destinationUri, property)) {
                destinationNrd.removeProperty(property);
            }
        }
        
        // copy all live properties of the existing destination
        propertyEnum = existingNrd.enumerateProperties();
        property = null;
        while (propertyEnum.hasMoreElements()) {
            property = (NodeProperty)propertyEnum.nextElement();
            if (isLivePropertyToRestore(destinationUri, property)) {
                destinationNrd.setProperty(property);
            }
        }
    }
    
    /**
     * Indicates if the given property is a live property to restore.
     *
     * @param      property  the NodeProperty to decide.
     *
     * @return     <code>true</code> if this is a live property to restore.
     */
    private boolean isLivePropertyToRestore(String uri, NodeProperty property) {
        boolean isLivePropertyToRestore = /*property.isLiveProperty() &&*/ (
            DeltavConstants.DELTAV_PROPERTY_LIST.contains(property.getName()) ||
                AclConstants.ACL_PROPERTY_LIST.contains(property.getName())   ||
                BindConstants.BIND_PROPERTY_LIST.contains(property.getName()) ||
                P_CREATIONDATE.equals(property.getName())                     ||
            ( P_DISPLAYNAME.equals(property.getName()) &&
                 Configuration.useBinding(token.getUri(slideToken, uri).getStore()) )
        );
        return isLivePropertyToRestore;
    }
    
    /**
     * Sets all DeltaV specific properties of the given NodeRevisionDescriptor
     * to their initial value.
     *
     * @param      revisionDescriptor  the NodeRevisionDescriptor whose DeltaV
     *                                 properties should be reset.
     */
    private void resetDeltavProperties(NodeRevisionDescriptor revisionDescriptor, String resourcePath) {
        
        // use initial values for DeltaV properties
        PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(slideToken, token, getConfig());
        ResourceKind resourceKind = VersionableImpl.getInstance();
        Iterator initialPropertyIterator =
            propertyHelper.createInitialProperties(resourceKind, resourcePath).iterator();
        NodeProperty property = null;
        List initialDeltavProperties = new ArrayList();
        while (initialPropertyIterator.hasNext()) {
            property = (NodeProperty)initialPropertyIterator.next();
            if (DeltavConstants.DELTAV_PROPERTY_LIST.contains(property.getName())) {
                initialDeltavProperties.add(property);
            }
        }
        
        Enumeration propertyEnum = revisionDescriptor.enumerateProperties();
        property = null;
        int index = 0;
        while (propertyEnum.hasMoreElements()) {
            property = (NodeProperty)propertyEnum.nextElement();
            if (DeltavConstants.DELTAV_PROPERTY_LIST.contains(property.getName())) {
                index = initialDeltavProperties.indexOf(property);
                if (index >= 0) {
                    revisionDescriptor.setProperty((NodeProperty)initialDeltavProperties.get(index));
                }
                else {
                    revisionDescriptor.removeProperty(property);
                }
            }
        }
    }
    
    /**
     * Restores the "backup" NodeRevisionDescriptor which has been saved in
     * method {@link #beforeDelete beforeDelete()}.
     *
     * @param      destinationUri                  the Uri of the resource.
     * @param      destinationRevisionDescriptors  the NodeRevisionDescriptors of
     *                                             the resource.
     */
    private void restoreBackupRevisionDescriptor(String destinationUri, NodeRevisionDescriptors destinationNrds) throws RevisionNotFoundException, ServiceAccessException, RevisionAlreadyExistException, ObjectNotFoundException, LinkedObjectNotFoundException, ObjectLockedException, AccessDeniedException, RevisionDescriptorNotFoundException, BranchNotFoundException, NodeNotVersionedException, VetoException {
        
        NodeRevisionDescriptor backupNrd =
            (NodeRevisionDescriptor)destinationBackupDescriptorMap.get(destinationUri);
        if (backupNrd != null) {
            try {
                content.retrieve( slideToken,
                                 destinationNrds,
                                 NodeRevisionNumber.HIDDEN_0_0 );
                content.store( slideToken,
                              destinationNrds.getUri(),
                              backupNrd,
                              null );
            }
            catch (RevisionDescriptorNotFoundException e) {
                content.create( slideToken,
                               destinationNrds.getUri(),
                               null,
                               backupNrd,
                               null ); // branch=null, revisionContent=null
            }
        }
    }
    
    
    // ------------------------------------------------------ Interface CopyRouteRedirector
    
    /**
     * Returns the (redirected) CopyRoute to use. Must not be <code>null</code>.
     *
     * @param      the original CopyRoute.
     *
     * @return     the (redirected) CopyRoute to use.
     *
     * @throws     SlideException  this Exception will be passed to the caller
     *                             of the Macro helper (contained in the
     *                             MacroCopyException).
     */
    public CopyRoute getRedirectedCopyRoute(CopyRoute copyRoute) throws SlideException {
        
        if (Configuration.useVersionControl()) {
            
            String sourceUri = copyRoute.getSourceUri();
            String destinationUri = copyRoute.getDestinationUri();
            
            try {
                sourceUri = versioningHelper.getLabeledResourceUri(sourceUri, labelHeader);
                copyRoute = new CopyRoute(sourceUri, destinationUri);
            }
            catch (LabeledRevisionNotFoundException e) {
                ViolatedPrecondition violatedPrecondition =
                    new ViolatedPrecondition(DeltavConstants.C_MUST_SELECT_VERSION_IN_HISTORY,
                                             WebdavStatus.SC_CONFLICT);
                throw new PreconditionViolationException(violatedPrecondition, sourceUri);
            }
        }
        
        return copyRoute;
    }
    
    
    // ------------------------------------------------------ Interface CopyListener
    
    /**
     * This method is called prior to copying the resource associated by
     * the given <code>sourceUri</code>. The copy can be prohibited by
     * throwing a SlideException.
     *
     * @param      sourceUri       the Uri of the resource that will be copied.
     * @param      destinationUri  the Uri of the copy.
     *
     * @throws     SlideException  this Exception will be passed to the caller
     *                             of the Macro helper (contained in the
     *                             MacroDeleteException.
     */
    public void beforeCopy(String sourceUri, String destinationUri, boolean isRootOfCopy) throws SlideException {
        
        if (DetailedWebdavEvent.COPY_BEFORE_COPY.isEnabled()) {
            EventDispatcher.getInstance().fireVetoableEvent(
                    DetailedWebdavEvent.COPY_BEFORE_COPY,
                    new DetailedWebdavEvent(this, destinationUri, sourceUri));
        }
        
        if(Configuration.useVersionControl()) {
            
            UriHandler sourceUh = UriHandler.getUriHandler(sourceUri);
            if (sourceUh.isHistoryUri()) {
                throw new PreconditionViolationException(
                    new ViolatedPrecondition(DeltavConstants.C_CANNOT_COPY_HISTORY, WebdavStatus.SC_FORBIDDEN), sourceUri);
            }
            
            if (!macroParameters.isDeleteCreate()) {
                beforeUpdateOrDelete( destinationUri );
            }
        }
        if (isRootOfCopy && Configuration.useBinding(token.getUri(slideToken, destinationUri).getStore())) {
            // collect the parent bindings of the destination node
            Map parentBindings = new HashMap();
            try {
                NodeRevisionDescriptor destinationNrd =
                    content.retrieve( slideToken, content.retrieve(slideToken, destinationUri) );
                XMLValue v = new XMLValue( (String)destinationNrd.getProperty(PN_PARENT_SET).getValue());
                Iterator i = v.iterator();
                while (i.hasNext()) {
                    Namespace dnsp = Namespace.getNamespace(S_DAV);
                    Element parentElm = (Element)i.next();
                    String segment = parentElm.getChild(E_SEGMENT, dnsp).getTextTrim();
                    String href = parentElm.getChild(E_HREF, dnsp).getTextTrim();
                    parentBindings.put( href, segment );
                }
            }
            catch( ServiceAccessException x ) {
                throw x;
            }
            catch (SlideException e) {}
            catch (JDOMException e) {}
            macroParameters.setParameter( Macro.PARENT_BINDINGS, parentBindings );
        }
    }
    
    /**
     * This method is called after copying the resource to
     * the given <code>destinationUri</code>.
     *
     * @param      sourceUri       the Uri of the resource that has been copied.
     * @param      destinationUri  the Uri of the copy.
     *
     * @throws     SlideException  this Exception will be passed to the caller
     *                             of the Macro helper (contained in the
     *                             MacroDeleteException.
     */
    public void afterCopy(String sourceUri, String destinationUri, boolean isRootOfCopy, boolean destinationExists) throws SlideException {
        
        if(Configuration.useVersionControl()) {
            
            NodeRevisionDescriptors destinationNrds = content.retrieve( slideToken, destinationUri);
            NodeRevisionDescriptor destinationNrd = content.retrieve( slideToken, destinationNrds);
            
            // restore backup descriptor
            restoreBackupRevisionDescriptor(destinationUri, destinationNrds);
            
            NodeRevisionDescriptor existingNrd =
                (NodeRevisionDescriptor)destinationDescriptorMap.get(destinationUri);
            if (existingNrd != null) {
                
                // there has been an existing destination, so restore live properties
                restoreLiveProperties(destinationUri, destinationNrd, existingNrd);
            }
            else {
                
                // DAV:must-not-copy-versioning-property
                resetDeltavProperties(destinationNrd, destinationUri);
            }
            
            // set <workspace> property
            versioningHelper.setWorkspaceProperty(destinationUri, destinationNrd);
            
            // set some other properties
            destinationNrd.setLastModified( new Date() ); // P_GETLASTMODIFIED
            destinationNrd.setETag( PropertyHelper.computeEtag(destinationUri, destinationNrd) ); // P_GETETAG
            if (!destinationExists) {
                // copy is creating a new resource
                String creator = ((SubjectNode)security.getPrincipal(slideToken)).getPath().lastSegment();
                destinationNrd.setOwner(creator);
            }
            
            content.store(slideToken, destinationNrds.getUri(), destinationNrd, null);
            
            // checkin if necessary
            boolean mustCheckin = versioningHelper.mustCheckinAutoVersionedVCR(slideToken, destinationNrds, destinationNrd);
            if (mustCheckin) {
                try {
                    versioningHelper.checkin(destinationUri, false, false, true ); //forkOk=false, keepCheckedOut=false
                }
                catch (IOException e) {
                    throw new SlideException("Checkin failed: " + e.getMessage());
                }
                catch (JDOMException e) {
                    throw new SlideException("Checkin failed: " + e.getMessage());
                }
            }
            
            // check if the resource should be put under version-control
            if( isAutoVersionControl(destinationUri) && !isCollection(destinationUri) && !isExcludedForVersionControl(destinationUri) ) {
                versioningHelper.versionControl(destinationUri);
            }
            
        }
        if (isRootOfCopy && Configuration.useBinding(token.getUri(slideToken, destinationUri).getStore())) {
            // try to restore the parent bindings
            if (macroParameters.getParameter(Macro.PARENT_BINDINGS) != null) {
                Map parentBindings = (Map)macroParameters.getParameter(Macro.PARENT_BINDINGS);
                Iterator i = parentBindings.entrySet().iterator();
                while (i.hasNext()) {
                    Map.Entry me = (Map.Entry)i.next();
                    ObjectNode parentNode = structure.retrieve( slideToken, (String)me.getKey() );
                    ObjectNode destinationNode = structure.retrieve( slideToken, destinationUri );
                    String segment = (String)me.getValue();
                    structure.addBinding( slideToken, parentNode, segment, destinationNode );
                }
            }
        }
        
        if (DetailedWebdavEvent.COPY_AFTER_COPY.isEnabled()) {
            EventDispatcher.getInstance().fireVetoableEvent(
                    DetailedWebdavEvent.COPY_AFTER_COPY,
                    new DetailedWebdavEvent(this, destinationUri, sourceUri));
        }
    }
    
    
    
    // ------------------------------------------------------ Interface DeleteListener
    
    /**
     * This method is called prior to deleting the resource associated by
     * the given <code>targetUri</code>. The deletion can be prohibited by
     * throwing a SlideException.
     *
     * @param      destinationUri  the Uri of the resource that will be deleted.
     *
     * @throws     SlideException  this Exception will be passed to the caller
     *                             of the Macro helper (contained in the
     *                             MacroDeleteException.
     */
    public void beforeDelete(String destinationUri) throws SlideException {
        
        if (DetailedWebdavEvent.COPY_BEFORE_DELETE.isEnabled()) {
            EventDispatcher.getInstance().fireVetoableEvent(
                    DetailedWebdavEvent.COPY_BEFORE_DELETE,
                    new DetailedWebdavEvent(this, destinationUri, sourceUri));
        }
        
        beforeUpdateOrDelete( destinationUri );
    }
    
    /**
     * This method is called prior to deleting the resource associated by
     * the given <code>targetUri</code>. The deletion can be prohibited by
     * throwing a SlideException.
     *
     * @param      destinationUri  the Uri of the resource that will be deleted.
     *
     * @throws     SlideException  this Exception will be passed to the caller
     *                             of the Macro helper (contained in the
     *                             MacroDeleteException.
     */
    private void beforeUpdateOrDelete(String destinationUri) throws SlideException {
        if( Configuration.useVersionControl() ) {
            NodeRevisionDescriptors destinationNrds = null;
            NodeRevisionDescriptor destinationNrd = null;
            
            try {
                destinationNrds = content.retrieve( slideToken, destinationUri);
                destinationNrd = content.retrieve( slideToken, destinationNrds);
            }
            catch (ObjectNotFoundException e) {}
            
            if (destinationNrds != null && destinationNrd != null) {
                
                ResourceKind resourceKind = AbstractResourceKind.determineResourceKind(token, destinationUri, destinationNrd);
                if (resourceKind instanceof CheckedInVersionControlled) {
                    
                    // check precondition DAV:cannot-modify-version-controlled-content
                    String autoVersion = versioningHelper.getAutoVersionElementName(destinationNrd);
                    if (autoVersion == null) {
                        autoVersion = "";
                    }
                    if ( !E_CHECKOUT_CHECKIN.equals(autoVersion) &&
                        !E_CHECKOUT_UNLOCKED_CHECKIN.equals(autoVersion) &&
                        !E_CHECKOUT.equals(autoVersion) &&
                        !E_CHECKOUT_IGNORE_UNLOCK.equals(autoVersion) &&
                        !E_LOCKED_CHECKOUT.equals(autoVersion) ) {
                        throw new PreconditionViolationException(new ViolatedPrecondition(C_CANNOT_MODIFY_VERSION_CONTROLLED_CONTENT,
                                                                                          WebdavStatus.SC_FORBIDDEN), destinationUri);
                    }
                    if ( E_LOCKED_CHECKOUT.equals(autoVersion) &&
                            ( !versioningHelper.isWriteLocked(slideToken, destinationNrds) ) ) {
                        throw new PreconditionViolationException(new ViolatedPrecondition(C_CANNOT_MODIFY_VERSION_CONTROLLED_CONTENT,
                                                                                          WebdavStatus.SC_FORBIDDEN), destinationUri);
                    }
                }
                
                // check precondition DAV:cannot-modify-version
                UriHandler uriHandler = UriHandler.getUriHandler(destinationUri);
                if (uriHandler.isVersionUri()) {
                    throw new PreconditionViolationException(new ViolatedPrecondition(C_CANNOT_MODIFY_VERSION,
                                                                                      WebdavStatus.SC_FORBIDDEN), destinationUri);
                }
                
                // checkout if necessary
                if( Configuration.useVersionControl() &&
                       (resourceKind instanceof CheckedInVersionControlled) &&
                   versioningHelper.mustCheckoutAutoVersionedVCR(destinationNrds, destinationNrd) ) {
                    
                    try {
                        versioningHelper.checkout(destinationNrds, destinationNrd, false, false, true );
                    }
                    catch (IOException e) {
                        throw new SlideException("Checkout failed: " + e.getMessage());
                    }
                    catch (JDOMException e) {
                        throw new SlideException("Checkout failed: " + e.getMessage());
                    }
                }
                
                // store the descriptor(s) in order to restore it in afterDelete()
                // (the COPY specification for DeltaV says that an existing destination
                //  must not be deleted)
                try {
                    NodeRevisionDescriptor backupNrd =
                        content.retrieve( slideToken, destinationNrds, NodeRevisionNumber.HIDDEN_0_0 );
                    destinationBackupDescriptorMap.put(destinationUri, backupNrd);
                }
                catch (RevisionDescriptorNotFoundException e) {
                }
                destinationDescriptorMap.put(destinationUri, destinationNrd);
            }
        }
    }
    
    /**
     * This method is called after deleting the resource associated by
     * the given <code>targetUri</code>.
     *
     * @param      targetUri       the Uri of the resource that has been deleted.
     *
     * @throws     SlideException  this Exception will be passed to the caller
     *                             of the Macro helper (contained in the
     *                     targetUricroDeleteException.
     */
    public void afterDelete(String targetUri) throws SlideException {
        if (DetailedWebdavEvent.COPY_AFTER_DELETE.isEnabled()) {
            EventDispatcher.getInstance().fireVetoableEvent(
                    DetailedWebdavEvent.COPY_AFTER_DELETE,
                    new DetailedWebdavEvent(this, destinationUri, sourceUri));
        }
    }
}






