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

package org.apache.slide.webdav.method;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.PropertyParseException;
import org.apache.slide.common.RequestedProperties;
import org.apache.slide.common.RequestedPropertiesImpl;
import org.apache.slide.common.RequestedProperty;
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.common.UriPath;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.event.EventDispatcher;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.StructureException;
import org.apache.slide.util.Configuration;
import org.apache.slide.webdav.WebdavException;
import org.apache.slide.webdav.WebdavServletConfig;
import org.apache.slide.webdav.event.WebdavEvent;
import org.apache.slide.webdav.util.AclConstants;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.LabeledRevisionNotFoundException;
import org.apache.slide.webdav.util.PropertyRetrieverImpl;
import org.apache.slide.webdav.util.UnlockListenerImpl;
import org.apache.slide.webdav.util.VersioningHelper;
import org.apache.slide.webdav.util.WebdavStatus;
import org.apache.slide.webdav.util.WebdavUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

/**
 * PROPFIND method.
 *
 */
public class PropFindMethod extends AbstractWebdavMethod implements DeltavConstants, AclConstants, ReadMethod {
    
    /**
     * Specify a property mask.
     */
    protected static final int FIND_BY_PROPERTY = 0;
    
    
    /**
     * Display all properties.
     */
    protected static final int FIND_ALL_PROP = 1;
    
    
    /**
     * Return property names.
     */
    protected static final int FIND_PROPERTY_NAMES = 2;
    
    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Depth.
     */
    protected int depth;
    
    
    /**
     * Type of the PROPFIND method.
     */
    protected int propFindType;
    
    /** if true, an ALL_PROP request will include computed props */
    protected boolean extendedAllprop = false;
    
    
    
    /**
     ** The SAXBuilder used to create JDOM Documents.
     **/
    protected static SAXBuilder saxBuilder = null;
    
    /**
     * The list of requested properties.
     */
    protected RequestedProperties requestedProperties = null;
    
    
    /**
     * Resource to be retrieved.
     */
    protected String resourcePath;
    
    /**
     * The VersioningHelper used by this instance.
     */
    protected VersioningHelper versioningHelper = null;
    
    /**
     * The value of the <code>Label</code> header.
     */
    protected String labelHeader = null;
    
    /**
     * If set <code>true</code>, instead of creating the complete response document
     * in memory and then sending it to the client, available parts of the response
     * are send immediatly in order to reduce the memory footprint.
     */
    protected boolean outputOptimized = true;
    
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Constructor.
     *
     * @param token     the token for accessing the namespace
     * @param config    configuration of the WebDAV servlet
     */
    public PropFindMethod(NamespaceAccessToken token,
                          WebdavServletConfig config) {
        super(token, config);
    }
    
    
    // ------------------------------------------------------ Protected Methods
    
    
    /**
     * Parse the request.
     *
     * @exception WebdavException Bad request
     */
    protected void parseRequest() throws WebdavException {
        
        versioningHelper =  VersioningHelper.getVersioningHelper(
            slideToken, token, req, resp, getConfig() );
        //        readRequestContent();
        
        depth = INFINITY;
        propFindType = FIND_ALL_PROP;
        extendedAllprop = getBooleanInitParameter( "extendedAllprop" );
        outputOptimized = getBooleanInitParameter( "optimizePropfindOutput" );
        
        resourcePath = requestUri;
        if (resourcePath == null) {
            resourcePath = "/";
        }
        
        labelHeader = WebdavUtils.fixTomcatHeader(requestHeaders.getLabel(), "UTF-8");
        
        retrieveDepth();
        
        if (req.getContentLength() == 0) {
            requestedProperties = new RequestedPropertiesImpl();
            requestedProperties.setIsAllProp(true);
            propFindType = FIND_ALL_PROP;
        }
        else {
            
            try {
                
                Element element = parseRequestContent(E_PROPFIND);
                try {
                    element = (Element)element.getChildren().get(0);
                }
                catch (Exception e) {
                    int statusCode = WebdavStatus.SC_BAD_REQUEST;
                    sendError( statusCode, getClass().getName()+".missingRootElementChildren", new Object[]{"DAV:"+E_PROPFIND} );
                    throw new WebdavException( statusCode );
                }
                
                if (element.getName().equalsIgnoreCase(E_PROPNAME)){
                    propFindType = FIND_PROPERTY_NAMES;
                }
                else if ( element.getName().equalsIgnoreCase(E_PROP) ) {
                    requestedProperties = new RequestedPropertiesImpl(element);
                    propFindType = FIND_BY_PROPERTY;
                }
                else if ( element.getName().equalsIgnoreCase(E_ALLPROP) ) {
                    requestedProperties = new RequestedPropertiesImpl(element);
                    propFindType = FIND_ALL_PROP;
                }
                else {
                    int statusCode = WebdavStatus.SC_BAD_REQUEST;
                    sendError( statusCode, getClass().getName()+".invalidChildOfRootElement", new Object[]{element.getNamespace()+":"+element.getName(),"DAV:"+E_PROPFIND} );
                    throw new WebdavException( statusCode );
                }
                
            }
            catch (JDOMException  e){
                int statusCode = WebdavStatus.SC_BAD_REQUEST;
                sendError( statusCode, e );
                throw new WebdavException( statusCode );
            }
            catch (PropertyParseException  e){
                int statusCode = WebdavStatus.SC_BAD_REQUEST;
                sendError( statusCode, e );
                throw new WebdavException( statusCode );
            }
            catch (IOException  e){
                int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
                sendError( statusCode, e );
                throw new WebdavException( statusCode );
            }
        }
    }
    
    /**
     * Retrieves the <code>Depth</code> header from the request.
     */
    private void retrieveDepth() throws WebdavException {
        depth = requestHeaders.getDepth(INFINITY);
        
        // limit tree browsing a bit
        if (depth > getConfig().getDepthLimit()) {
            depth = getConfig().getDepthLimit();
        }
    }
    
    
    
    /**
     * Execute the request.
     *
     * @exception WebdavException
     */
    protected void executeRequest() throws IOException, WebdavException {
        
        resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
        
        // Loads the associated object from the store.
        
        // Get the object from Data.
        ObjectNode resource = null;
        
        try {
            if ( WebdavEvent.PROPFIND.isEnabled() ) EventDispatcher.getInstance().fireVetoableEvent(WebdavEvent.PROPFIND, new WebdavEvent(this));
            resource = structure.retrieve(slideToken, resourcePath);
        } catch (StructureException e) {
            int statusCode = WebdavStatus.SC_NOT_FOUND;
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        } catch (Exception e) {
            int statusCode = getErrorCode( e );
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        }
        
        resp.setContentType(TEXT_XML_UTF_8);
        
        // Create multistatus object
        Element multistatusElement = new Element(E_MULTISTATUS, DNSP);
        org.jdom.output.Format format = org.jdom.output.Format.getPrettyFormat();
        format.setIndent(XML_RESPONSE_INDENT);

        //      ======== ESUP ==============
        //      Microsoft Windows XP WebDAV redirector (MRXDAV.SYS) does not like pretty format
        String	userAgent = req.getHeader("User-Agent");
        if (userAgent != null && userAgent.toLowerCase().indexOf("microsoft-webdav-miniredir") != -1)
        	format = org.jdom.output.Format.getCompactFormat();
        //     ======== ESUP ================
        
        XMLOutputter xmlOutputter = new XMLOutputter(format);
        
        if (resource != null) {
            if (depth == 0) {
                multistatusElement.addContent(getPropertiesOfObject(resource.getUri()));
                xmlOutputter.output(new Document(multistatusElement), resp.getWriter());
            } else {
                // The stack always contains the object of the current level
                Stack stack = new Stack();
                stack.push(resource);
                
                // Stack of the objects one level below
                Stack stackBelow = new Stack();
                
                StringBuffer buffer = new StringBuffer();
                if (outputOptimized) {
                    
                	// TB
                    resp.getWriter().write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                    //resp.getWriter().write("<?xml version=\"1.0\" encoding=\""+Configuration.realUrlEncoding()+"\"?>");
                    // END TB
                    
                    resp.getWriter().write("\n");
                    String namespacePrefix = multistatusElement.getNamespacePrefix();
                    if ( (namespacePrefix != null) && (namespacePrefix.length() == 0) ) {
                        namespacePrefix = null;
                    }
                    String namespaceUri = multistatusElement.getNamespaceURI();
                    if ( (namespaceUri != null) && (namespaceUri.length() == 0) ) {
                        namespaceUri = null;
                    }
                    buffer.append("<");
                    buffer.append(multistatusElement.getQualifiedName());
                    if (namespaceUri != null) {
                        buffer.append(" xmlns");
                        if (namespacePrefix != null) {
                            buffer.append(":");
                            buffer.append(namespacePrefix);
                        }
                        buffer.append("=\"");
                        buffer.append(namespaceUri);
                        buffer.append("\"");
                    }
                    buffer.append(">");
                    resp.getWriter().write(buffer.toString());
                    resp.getWriter().write("\n");
                }
                
                while ((!stack.isEmpty()) && (depth >= 0)) {
                    
                    ObjectNode cur = (ObjectNode) stack.pop();
                    Element response = getPropertiesOfObject(cur.getUri());
                    if (outputOptimized) {
                        xmlOutputter.output(response, resp.getWriter());
                    }
                    else {
                        multistatusElement.addContent(response);
                    }
                    
                    if (depth > 0) {
                        
                        Enumeration enum = null;
                        
                        try {
                            enum = structure.getChildren(slideToken, cur);
                        } catch (Exception e) {
                            int statusCode = getErrorCode( e );
                            sendError( statusCode, e );
                            throw new WebdavException( statusCode );
                        }
                        
                        while (enum.hasMoreElements()) {
                            // PROPFIND must not list Lock-Null resources that
                            // are timed out
                            ObjectNode node = (ObjectNode)enum.nextElement();
                            UnlockListenerImpl listener = new UnlockListenerImpl(
                                    slideToken, token, config, req, resp);
                            try {
                                lock.clearExpiredLocks(slideToken, node.getUri(), listener);
                                if (!listener.isRemovedLockResource(node.getUri())) {
                                    stackBelow.push(node);
                                }
                            } catch (SlideException e) { /* ignore */}
                        }
                        
                    }
                    
                    if (stack.isEmpty()) {
                        depth--;
                        stack = stackBelow;
                        stackBelow = new Stack();
                    }
                    
                }
                
                if (outputOptimized) {
                    resp.getWriter().write("\n");
                    buffer.setLength(0);
                    buffer.append("</");
                    buffer.append(multistatusElement.getQualifiedName());
                    buffer.append(">");
                    resp.getWriter().write(buffer.toString());
                    resp.getWriter().write("\n");
                }
                else {
                    xmlOutputter.output(new Document(multistatusElement), resp.getWriter());
                }
            }
        }
        
    }
    
    
    /**
     * Return the properties of an object as a <code>&lt;response&gt;</code>.
     *
     * @param   resourceUri  the slide URI of the resource.
     *
     * @return  the <code>&lt;response&gt;</code> Element.
     *
     * @exception WebdavException
     */
    protected Element getPropertiesOfObject(String resourceUri)
        throws WebdavException {
        
        SlideToken lightSToken = new SlideTokenWrapper(slideToken);
        lightSToken.setForceLock(false);
        
        // evaluate "Label" header
        if (Configuration.useVersionControl()) {
            try {
                resourceUri = versioningHelper.getLabeledResourceUri(resourceUri, labelHeader);
            }
            catch (LabeledRevisionNotFoundException e) {
                return getErrorResponse(resourceUri,
                                        WebdavStatus.SC_CONFLICT,
                                        DeltavConstants.C_MUST_SELECT_VERSION_IN_HISTORY);
            }
            catch( RevisionDescriptorNotFoundException x ) {
            }
            catch (SlideException e) {
                return getErrorResponse(resourceUri, getErrorCode(e), null);
            }
        }
        
        ObjectNode object = null;
        try {
            object = structure.retrieve(lightSToken, resourceUri);
        }
        catch (SlideException e) {
            return getErrorResponse(resourceUri, getErrorCode(e), null);
        }
        
        Element responseElement = new Element(E_RESPONSE, DNSP);
        
        String status = new String(HTTP_VERSION + WebdavStatus.SC_OK + " "
                                       + WebdavStatus.getStatusText
                                       (WebdavStatus.SC_OK));
        NodeRevisionDescriptors revisionDescriptors = null;
        NodeRevisionDescriptor revisionDescriptor = null;
        
        //boolean isCollection = false;
        
//        NodeLock objectLockToken = null;
        
        try {
            Element hrefElement = new Element(E_HREF, DNSP);
            
            //VersioningHelper vHelp =  VersioningHelper.getVersioningHelper(
            //    lightSToken, token, req, resp, getConfig() );
            String resourcePath = object.getUri();
            
            revisionDescriptors =
                content.retrieve(lightSToken, resourcePath);
            
            try {
                revisionDescriptor = content.retrieve(lightSToken,
                                                      revisionDescriptors);
                
                //isCollection = WebdavUtils.isCollection(revisionDescriptor);
                
                hrefElement.setText(
                    WebdavUtils.getAbsolutePath(object.getUri(), req,
                                                getConfig()));
                
            } catch (RevisionDescriptorNotFoundException e) {
                
                // The object doesn't have any revision, we create a dummy
                // NodeRevisionDescriptor object
                //isCollection = true;
                revisionDescriptor = new NodeRevisionDescriptor(0);
                
                if (!Configuration.useBinding(token.getUri(lightSToken, object.getUri()).getStore())) {
                    revisionDescriptor.setName(new UriPath(object.getUri()).lastSegment());
                }
                
                hrefElement.setText(
                    WebdavUtils.getAbsolutePath(object.getUri(), req,
                                                getConfig()));
            }
            
            responseElement.addContent(hrefElement);
            
//            Enumeration lockTokens = lock.enumerateLocks(lightSToken, object.getUri(), true);
            
//            if (lockTokens.hasMoreElements()) {
//                objectLockToken = (NodeLock) lockTokens.nextElement();
//            }
            
        } catch (AccessDeniedException e) {
            if (revisionDescriptor == null) {
                revisionDescriptor = new NodeRevisionDescriptor(0);
            }
        } catch (Exception e) {
            int statusCode = getErrorCode( e );
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        }

        PropertyRetrieverImpl propertyRetriever = new PropertyRetrieverImpl(token, lightSToken, getConfig());

        switch (propFindType) {
            case FIND_ALL_PROP :
            case FIND_BY_PROPERTY :
                try {
                    List propstatList= propertyRetriever.getPropertiesOfObject(requestedProperties, revisionDescriptors, revisionDescriptor, getSlideContextPath(), extendedAllprop);
                    Iterator iterator = propstatList.iterator();
                    while (iterator.hasNext()) {
                        responseElement.addContent((Element)iterator.next());
                    }
                } catch (ServiceAccessException e) {
                    int statusCode = WebdavStatus.SC_FORBIDDEN;
                    sendError( statusCode, e );
                    throw new WebdavException( statusCode );
                } catch (Exception e) {
                    int statusCode = getErrorCode( e );
                    sendError( statusCode, e );
                    throw new WebdavException( statusCode );
                }
                break;
            case FIND_PROPERTY_NAMES :

                try {
                    status = new String("HTTP/1.1 " + WebdavStatus.SC_OK
                                            + " " + WebdavStatus.getStatusText
                                            (WebdavStatus.SC_OK));

                    Element propstatElement = new Element(E_PROPSTAT, DNSP);
                    Element propElement = new Element(E_PROP, DNSP);
                    RequestedProperties propnames =
                        propertyRetriever.getAllPropertyNames(object.getUri(), true);
                    Iterator iterator = propnames.getRequestedProperties();
                    while (iterator.hasNext()) {
                        RequestedProperty p = (RequestedProperty)iterator.next();
                        Namespace nsp = DNSP.getURI().equals(p.getNamespace())
                            ? DNSP
                            : Namespace.getNamespace(p.getNamespace());
                        propElement.addContent(new Element(p.getName(), nsp));
                    }
                    Element statusElement = new Element(E_STATUS, DNSP);
                    statusElement.setText(status);
                    propstatElement.addContent(propElement);
                    propstatElement.addContent(statusElement);
                    responseElement.addContent(propstatElement);
                }
                catch (ServiceAccessException e) {
                    int statusCode = WebdavStatus.SC_FORBIDDEN;
                    sendError( statusCode, e );
                    throw new WebdavException( statusCode );
                }
                catch (Exception e) {
                    int statusCode = getErrorCode( e );
                    sendError( statusCode, e );
                    throw new WebdavException( statusCode );
                }
                break;
        }

        return responseElement;
    }

    /**
     * Returns the appropriate <code>&lt;response&gt;</code> due to the given
     * <code>exception</code> to the <code>generatedXML</code>
     *
     * @param      resourcePath     the URI of the request to display in the
     *                            <code>&lt;href&gt;</code> element.
     * @param      errorCode      the HTTP error code.
     * @param      condition      the condition that has been violated.
     */
    private Element getErrorResponse(String resourcePath, int errorCode, String condition) {
        
        Element response = new Element(E_RESPONSE, DNSP);
        
        Element href = new Element(E_HREF, DNSP);
        href.setText(HTTP_PROTOCOL +
                         req.getServerName()+ ":" +
                         req.getServerPort() +
                         getSlideContextPath() +
                         resourcePath);
        response.addContent(href);
        Element propStat = new Element(E_PROPSTAT, DNSP);
        response.addContent(propStat);
        
        Element status = new Element(E_STATUS, DNSP);
        status.setText(HTTP_VERSION + " " + errorCode  + " " + WebdavStatus.getStatusText(errorCode));
        propStat.addContent(status);
        
        if (condition != null) {
            Element responseDescriptiont = new Element(E_RESPONSEDESCRIPTION, DNSP);
            Element errorElement = new Element(E_ERROR, DNSP);
            responseDescriptiont.addContent(errorElement);
            Element conditionElement = new Element(condition, DNSP);
            errorElement.addContent(conditionElement);
            propStat.addContent(responseDescriptiont);
        }
        return response;
    }
}









