/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/SearchMethod.java,v 1.51 2005/03/11 09:49:35 luetzkendorf Exp $
 * $Revision: 1.51 $
 * $Date: 2005/03/11 09:49:35 $
 *
 * ====================================================================
 *
 * 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.Iterator;
import java.util.List;

import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.RequestedProperties;
import org.apache.slide.common.SlideException;
import org.apache.slide.event.EventDispatcher;
import org.apache.slide.search.BadGatewayException;
import org.apache.slide.search.BadQueryException;
import org.apache.slide.search.InvalidQueryException;
import org.apache.slide.search.InvalidScopeException;
import org.apache.slide.search.QueryScope;
import org.apache.slide.search.RequestedResource;
import org.apache.slide.search.Search;
import org.apache.slide.search.SearchException;
import org.apache.slide.search.SearchQuery;
import org.apache.slide.search.SearchQueryResult;
import org.apache.slide.search.basic.IBasicQuery;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.ObjectNotFoundException;
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.ComputedPropertyProvider;
import org.apache.slide.webdav.util.PropertyRetriever;
import org.apache.slide.webdav.util.PropertyRetrieverImpl;
import org.apache.slide.webdav.util.WebdavConstants;
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.output.XMLOutputter;

/**
 * SEARCH method.
 *  
 */
public class SearchMethod extends AbstractWebdavMethod implements WebdavConstants, ReadMethod, FineGrainedLockingMethod {
   
    
    // ----------------------------------------------------- Instance variables
    
    private SearchQuery searchQuery = null;
    private Search searchHelper  = null;
    
    private RequestedProperties requestedProperties = null;
    private PropertyRetriever retriever;
    
    /** if true, an ALL_PROP request will include computed props */
    protected boolean extendedAllprop = false;
    
    // ----------------------------------------------------------- Constructors
    
    
    /**
     * Constructor.
     *
     * @param token     the token for accessing the namespace
     * @param config    configuration of the WebDAV servlet
     */
    public SearchMethod(NamespaceAccessToken token,
                        WebdavServletConfig config) {
        super(token, config);
    }
    
    /**
     * @see org.apache.slide.webdav.method.FineGrainedLockingMethod#acquireFineGrainLocks()
     */
    public void acquireFineGrainLocks() {
        acquireGlobalReadLock();
    }

    /**
     * Method parseRequest
     *
     * @throws   WebdavException
     */
    protected void parseRequest() throws WebdavException {
        
        searchHelper = token.getSearchHelper();
        retriever = new PropertyRetrieverImpl(token, slideToken, getConfig());
        String slidePath = null;
        
        extendedAllprop = getBooleanInitParameter( "extendedAllprop" );
        
        if (Configuration.useSearch ()) {
            try {
                Element queryElement = getQueryElement();
                String grammarNamespace = queryElement.getNamespaceURI();
                
                // SearchLanguage language = searchHelper.getLanguage (grammarNamespace);
                int maxDepth = getConfig().getDepthLimit();
                
                searchQuery = searchHelper.createSearchQuery
                    (grammarNamespace, queryElement, slideToken, maxDepth,
                     new ComputedPropertyProvider(token, slideToken, 
                           getSlideContextPath(), getConfig()),
                     req.getRequestURI ());
                
                requestedProperties = searchQuery.requestedProperties ();
                
                if (searchQuery instanceof IBasicQuery)
                {
                    QueryScope scope = ((IBasicQuery)searchQuery).getScope();
                    slidePath = ((IBasicQuery)searchQuery).getSlidePath();
                    
                    // check, if scope is accessible (ACL)
                    token.getContentHelper().retrieve(slideToken, slidePath);
                    
                    scope.setIsCollection (
                        WebdavUtils.isCollection (token, slideToken, slidePath));
                }
            }
            catch (JDOMException e) {
                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                resp.setContentType (TEXT_XML_UTF_8);
                createErrorResult (SearchQueryResult.STATUS_BAD_QUERY,
                                   e.getMessage());
                throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
            }
            catch (InvalidQueryException e) {
                resp.setStatus(WebdavStatus.SC_UNPROCESSABLE_ENTITY);
                resp.setContentType (TEXT_XML_UTF_8);
                createErrorResult (SearchQueryResult.STATUS_UNPROCESSABLE_ENTITY,
                                   e.getMessage());
                throw new WebdavException(WebdavStatus.SC_UNPROCESSABLE_ENTITY);
            }
            catch (BadGatewayException e) {
                resp.setStatus(WebdavStatus.SC_BAD_GATEWAY);
                resp.setContentType (TEXT_XML_UTF_8);
                createErrorResult (SearchQueryResult.STATUS_BAD_GATEWAY,
                                   e.getMessage());
                throw new WebdavException(WebdavStatus.SC_BAD_GATEWAY);
            }
            catch (InvalidScopeException e) {
                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                resp.setContentType (TEXT_XML_UTF_8);
                createErrorResult (SearchQueryResult.STATUS_INVALID_SCOPE,
                                   e.getMessage());
                
                throw new WebdavException (WebdavStatus.SC_BAD_REQUEST);
            }
            catch (BadQueryException e) {
                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                resp.setContentType (TEXT_XML_UTF_8);
                createErrorResult (SearchQueryResult.STATUS_BAD_QUERY,
                                   e.getMessage());
                throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
            }
            catch (AccessDeniedException e) {
                
                String contextPath = getSlideContextPath();
                AccessDeniedException ade = new AccessDeniedException
                    (contextPath + e.getObjectUri(),
                     contextPath + e.getSubjectUri(),
                     contextPath + e.getActionUri());
                
                String msg = ade.getMessage();
                resp.setStatus(WebdavStatus.SC_FORBIDDEN);
                resp.setContentType (TEXT_XML_UTF_8);
                createErrorResult (SearchQueryResult.STATUS_FORBIDDEN, msg);
                throw new WebdavException(WebdavStatus.SC_FORBIDDEN);
            }
            catch (ObjectNotFoundException e) {
                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                resp.setContentType (TEXT_XML_UTF_8);
                createErrorResult (SearchQueryResult.STATUS_INVALID_SCOPE,
                                   "scope " + slidePath + " is invalid");
                
                throw new WebdavException (WebdavStatus.SC_BAD_REQUEST);
            }
            catch (SlideException e) {
                e.printStackTrace();
            }
        }
        else {
            resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
            resp.setContentType (TEXT_XML_UTF_8);
            createErrorResult (SearchQueryResult.STATUS_BAD_QUERY, "SEARCH not implemented on this server");
            throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
        }
    }
    
    
    /**
     * Method executeRequest
     *
     * @throws   WebdavException
     *
     * @version  12/28/2001
     */
    protected void executeRequest() throws WebdavException {
        
        SearchQueryResult result = null;
        
        try {
            if ( WebdavEvent.SEARCH.isEnabled() ) EventDispatcher.getInstance().fireVetoableEvent(WebdavEvent.SEARCH, new WebdavEvent(this));

            resp.setContentType (TEXT_XML_UTF_8);
            
            result = searchHelper.search (slideToken, searchQuery);
            WebdavResult webdavResult = new WebdavResult (result, retriever);
            resp.setStatus (webdavResult.getWebdavStatus());
            org.jdom.Document responseDoc = webdavResult.getWebdavResultDocument();
            sendResult (responseDoc);
        }
        
        catch (StructureException e) {
            try {
                resp.sendError
                    (WebdavStatus.SC_NOT_FOUND,
                     WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
            } catch(IOException ex) {
                ex.printStackTrace();
            }
            throw new WebdavException(WebdavStatus.SC_NOT_FOUND);
        } catch (RuntimeException e) {
            e.printStackTrace();
            resp.setStatus(getErrorCode(e));  // no special handling needed
            throw new WebdavException(WebdavStatus.SC_ACCEPTED, false); // abort the TA
            
        } catch (Exception e) {
            resp.setStatus(getErrorCode(e));  // no special handling needed
            throw new WebdavException(WebdavStatus.SC_ACCEPTED, false); // abort the TA
        }
    }
    
    /**
     * Method getSearchRequestElement
     *
     * @return   an Element
     *
     * @throws   WebdavException
     *
     * @version  12/28/2001
     */
    private Element getQueryElement() throws WebdavException, JDOMException, 
    		SearchException 
    {
        Element queryElement = null;
        try
        {
            Document document = parseRequestContent(); // TODO: check root element name
            if (document == null) {
                throw new BadQueryException("No search given.");
            }
            Element rootElement = document.getRootElement();
            List children = rootElement.getChildren();
            if (children.size() > 0) {
                queryElement = (Element)children.get(0);
            }
            return queryElement;
        }
        catch (IOException e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
            resp.setStatus(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
            throw new WebdavException
                (WebdavStatus.SC_INTERNAL_SERVER_ERROR);
        }
    }
    
    /**
     * Method sendResult
     *
     * @param    responseDoc         a  Document
     *
     * @throws   JDOMException
     * @throws   IOException
     *
     */
    private void  sendResult (org.jdom.Document responseDoc)
        throws org.jdom.JDOMException, IOException
    {
        org.jdom.output.Format format = org.jdom.output.Format.getPrettyFormat();
        format.setIndent(XML_RESPONSE_INDENT);
        XMLOutputter xmlWriter = new XMLOutputter (format);
        xmlWriter.output (responseDoc, resp.getWriter());
    }
    
    /**
     * Method createErrorResult
     *
     * @param    queryStatus         an int
     * @param    message             a  String
     *
     */
    private void createErrorResult (int queryStatus, String message) {
        SearchQueryResult result = new SearchQueryResult ();
        result.setStatus (queryStatus);
        result.setDescription (message);
        result.setHref (getSlideContextPath());
        
        try {
            WebdavResult webdavResult = new WebdavResult (result, retriever);
            org.jdom.Document responseDoc = webdavResult.getWebdavResultDocument();
            sendResult (responseDoc);
        }
        catch (Exception e) {}
    }
    
    /**
     * Represents the webdav result for a SEARCH. It encapsulates the response
     * body, and the status of the response. It contains a list of response
     * elements with propstat elements and optional a response element with
     * an error status and a responsedescription
     */
    class WebdavResult {
        
        private SearchQueryResult queryResult;
        private org.jdom.Document responseDoc;
        private PropertyRetriever retriever;
        
        /** the status of the response */
        private int webdavStatus = WebdavStatus.SC_MULTI_STATUS;
        
        /**
         * constructs a WebdavResult
         *
         * @param    queryResult         the result of the query
         * @param    retriever           the retriever to get the properties
         *
         * @throws   JDOMException
         * @throws   SlideException
         *
         */
        WebdavResult (SearchQueryResult queryResult, PropertyRetriever retriever)
            throws org.jdom.JDOMException, SlideException
        {
            this.queryResult = queryResult;
            this.retriever = retriever;
            init();
        }
        
        /**
         * Method getWebdavStatus
         *
         * @return   an int
         *
         */
        int getWebdavStatus () {
            return webdavStatus;
        }
        
        /**
         * Method getWebdabResultDocument
         *
         * @return   a Document
         *
         */
        org.jdom.Document getWebdavResultDocument () {
            return responseDoc;
        }
        
        /**
         * Method init
         *
         * @throws   JDOMException
         * @throws   SlideException
         *
         */
        private void init () throws org.jdom.JDOMException, SlideException {
            
            String href = null;
            int errorStatus = webdavStatus;
            
            org.jdom.Element rootElement =
                new org.jdom.Element (E_MULTISTATUS, DNSP);
            
            responseDoc = new org.jdom.Document (rootElement);
            
            Iterator it = queryResult.iterator();
            while (it.hasNext()) {
                
                org.jdom.Element responseElement =
                    new org.jdom.Element (E_RESPONSE, DNSP);
                
                rootElement.addContent (responseElement);
                org.jdom.Element hrefElement =
                    new org.jdom.Element (E_HREF, DNSP);
                
                RequestedResource resource = (RequestedResource)it.next();
                String internalUri = resource.getUri();
                
                String absUri = WebdavUtils.getAbsolutePath (internalUri, req, getConfig());
                
                hrefElement.addContent(absUri);
                
                responseElement.addContent (hrefElement);
                List propstatList= retriever.getPropertiesOfObject (requestedProperties,
                                                                    resource,
                                                                    getSlideContextPath(),
                                                                    extendedAllprop);
                Iterator iterator = propstatList.iterator();
                while (iterator.hasNext()) {
                    responseElement.addContent((org.jdom.Element)iterator.next());
                }
            }
            
            int status = queryResult.getStatus();
            if (status != SearchQueryResult.STATUS_OK) {
                
                //String webdavStatusText = null;
                
                switch (status) {
                    case SearchQueryResult.STATUS_BAD_QUERY:
                        webdavStatus = WebdavStatus.SC_BAD_REQUEST;
                        errorStatus = WebdavStatus.SC_BAD_REQUEST;
                        href = queryResult.getHref();
                        break;
                        
                    case SearchQueryResult.STATUS_INVALID_SCOPE:
                        webdavStatus = WebdavStatus.SC_BAD_REQUEST;
                        errorStatus = WebdavStatus.SC_NOT_FOUND;
                        href = queryResult.getHref();
                        break;
                        
                    case SearchQueryResult.STATUS_PARTIAL_RESULT:
                        errorStatus = WebdavStatus.SC_INSUFFICIENT_STORAGE;
                        href = getSlideContextPath();
                        break;
                        
                    case SearchQueryResult.STATUS_UNPROCESSABLE_ENTITY:
                        errorStatus = WebdavStatus.SC_UNPROCESSABLE_ENTITY;
                        href = getSlideContextPath();
                        break;
                        
                    case SearchQueryResult.STATUS_BAD_GATEWAY:
                        errorStatus = WebdavStatus.SC_BAD_GATEWAY;
                        href = queryResult.getHref();
                        break;
                        
                    case SearchQueryResult.STATUS_FORBIDDEN:
                        errorStatus = WebdavStatus.SC_FORBIDDEN;
                        href = queryResult.getHref();
                        break;
                        
                    default:
                        throw new WebdavException
                            (WebdavStatus.SC_INTERNAL_SERVER_ERROR);
                        
                }
                org.jdom.Element responseElement =
                    new org.jdom.Element (E_RESPONSE, DNSP);
                
                org.jdom.Element hrefElement =
                    new org.jdom.Element (E_HREF, DNSP);
                
                hrefElement.addContent (href);
                
                org.jdom.Element statusElement =
                    new org.jdom.Element (E_STATUS, DNSP);
                
                statusElement.addContent (getStatusText (errorStatus));
                responseElement.addContent (hrefElement);
                responseElement.addContent (statusElement);
                
                String description = queryResult.getDescription();
                if (description != null) {
                    org.jdom.Element responseDescriptionElement =
                        new org.jdom.Element (E_RESPONSEDESCRIPTION,
                                              DNSP);
                    
                    responseDescriptionElement.addContent (description);
                    responseElement.addContent (responseDescriptionElement);
                }
                
                if (status == SearchQueryResult.STATUS_INVALID_SCOPE) {
                    responseElement.addContent (
                        new org.jdom.Element ("scopeerror",
                                              DNSP));
                }
                rootElement.addContent (responseElement);
            }
        }
        
        /**
         * Method getStatusText
         *
         * @param    status              an int
         *
         * @return   a String
         *
         */
        private String getStatusText (int status) {
            return  "HTTP/1.1 " + status + " "
                + WebdavStatus.getStatusText (status);
        }
    }
}


