/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/WebdavServlet.java,v 1.68 2005/03/29 08:08:28 luetzkendorf Exp $
 * $Revision: 1.68 $
 * $Date: 2005/03/29 08:08:28 $
 *
 * ====================================================================
 *
 * 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;

import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.slide.authenticate.SecurityToken;
import org.apache.slide.common.Domain;
import org.apache.slide.common.DomainInitializationFailedError;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.SlideToken;
import org.apache.slide.common.SlideTokenWrapper;
import org.apache.slide.util.logger.Logger;
import org.apache.slide.webdav.util.DirectoryIndexGenerator;
import org.apache.slide.webdav.util.WebdavConstants;
import org.apache.slide.webdav.util.WebdavStatus;
import org.apache.slide.webdav.util.WebdavUtils;

/**
 * The WebDAV servlet. It is responsible for dispatching incoming requests to
 * implementations of the WebdavMethod interface.
 *
 * @version $Revision: 1.68 $
 */
public class WebdavServlet extends HttpServlet {
    
    // -------------------------------------------------------------- Constants
    
    
    /**
     * Name of the log channel used by the WebDAV servlet.
     */
    private static final String LOG_CHANNEL = WebdavServlet.class.getName();
    
    
    /**
     * Name under which the namespace access token is stored in the application
     * attributes. This is used when the WebDAV servlet doesn't initialize
     * Slide itself, but rather the initialization is done outside.
     */
    public static final String ATTRIBUTE_NAME =
        "org.apache.slide.NamespaceAccessToken";
    
    
    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Access token to the namespace.
     */
    protected NamespaceAccessToken token;
    
    
    /**
     * Directory browsing enabled.
     */
    protected boolean directoryBrowsing = true;
    
    
    /**
     * RequestDispatcher to the directory browsing template, if specified.
     */
    protected RequestDispatcher directoryBrowsingTemplate;
    
    
    /**
     * Directory browsing enabled.
     */
    protected DirectoryIndexGenerator directoryIndexGenerator;
    
    
    /**
     * Set to true if the servlet is handling the lifecycle of the domain.
     */
    protected boolean handleLifecycle = true;
    
    
    /**
     * Instance of the WebdavMethodFactory to use by this servlet.
     */
    protected WebdavMethodFactory methodFactory;
    
    
    // -------------------------------------------------------- Servlet Methods
    
    
    protected void service (HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        
        SimpleDateFormat sdf = new SimpleDateFormat();
        if( token.getLogger().isEnabled(LOG_CHANNEL, Logger.DEBUG) )
            token.getLogger().log("==> "+req.getMethod()+" start: "+sdf.format(new Date(System.currentTimeMillis()))+" ["+Thread.currentThread().getName()+"]", LOG_CHANNEL, Logger.DEBUG);
        
        req.setAttribute("slide_uri", WebdavUtils.getRelativePath(req, (WebdavServletConfig) getServletConfig()));
        
        try {
            
            if (token == null) {
                String namespaceName = req.getContextPath();
                if ((namespaceName == null) || (namespaceName.equals(""))) {
                    namespaceName = Domain.getDefaultNamespace();
                }
                while (namespaceName.startsWith("/")) {
                    namespaceName = namespaceName.substring(1);
                }
                token = Domain.accessNamespace(new SecurityToken(this), namespaceName);
            }
            
            resp.setStatus(WebdavStatus.SC_OK);
            
            String methodName = req.getMethod();
            WebdavMethod method = methodFactory.createMethod(methodName);
            if (method == null) {
                throw new WebdavException(WebdavStatus.SC_METHOD_NOT_ALLOWED);
            } else {
                method.run(req, resp);
            }
        } catch (WebdavException e) {
            // There has been an error somewhere ...
            token.getLogger().log(e,LOG_CHANNEL, Logger.ERROR);
            try { resp.sendError(e.getStatusCode()); } catch (Throwable ex) { }
        } catch (Throwable e) {
            // If something goes really wrong ...
            token.getLogger().log(e,LOG_CHANNEL, Logger.ERROR);
            try { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } catch (Throwable ex) { }
        }
        finally {
            if( token.getLogger().isEnabled(LOG_CHANNEL, Logger.DEBUG) )
                token.getLogger().log("<== "+req.getMethod()+" end: "+sdf.format(new Date(System.currentTimeMillis()))+" ["+Thread.currentThread().getName()+"]", LOG_CHANNEL, Logger.DEBUG);
        }
        
    }
    
    private boolean isExtTx(HttpServletRequest req) {
        String hTxIdStr = req.getHeader(WebdavConstants.H_TRANSACTION);
        return (hTxIdStr != null);
    }
    
    private boolean isCollection(HttpServletRequest req) {
        SlideToken slideToken = new SlideTokenWrapper(WebdavUtils.getSlideToken(req));
        // necessary as no transaction has been started, yet
        slideToken.setForceStoreEnlistment(false);
        slideToken.setForceSecurity(false);
        slideToken.setForceLock(false);
        
        return WebdavUtils.isCollection(token, slideToken, WebdavUtils.getRelativePath(req, (WebdavServletConfig)getServletConfig()));
    }
    
    /**
     * Implemented to wrap the ServletConfig object inside a
     * WebdavServletConfig
     */
    public void init(ServletConfig config)
        throws ServletException {
        
        super.init(new WebdavServletConfig(config));
        
        // all the actual initialization is inside init()
    }
    
    
    /**
     * Manages some initialization stuff on the server.
     */
    public void init()
        throws ServletException {
        
        if (!isDomLevel2Parser()) {
            System.out.println("======================================================");
            System.out.println("!!! Unable to start Slide Servlet !!!");
            System.out.println("------------------------------------------------------");
            System.out.println("You are using using an incorrect older XML parser");
            System.out.println("that doesn't provide Element::getElementsByTagNameNS");
            System.out.println("consult the documentation for a list of valid XML parsers.");
            System.out.println("======================================================");
            
            log("======================================================");
            log("!!! Unable to start Slide Servlet !!!");
            log("------------------------------------------------------");
            log("======================================================");
            log("You are using using an incorrect older XML parser");
            log("that doesn't provide Element::getElementsByTagNameNS");
            log("consult the documentation for a list of valid XML parsers.");
            log("======================================================");
            throw new ServletException("Invalid XML parser");
        }
        
        String value = null;
        
        // Lookup for the NAT using JNDI
        // FIXME
        
        // Lookup for the NAT using the servlet context
        token = (NamespaceAccessToken)getServletContext().getAttribute(ATTRIBUTE_NAME);
        if (token == null) {
            // Dafault initialization
            String namespaceName = null;
            String domainConfigFile = null;
            value = getInitParameter("namespace");
            if (value != null) {
                namespaceName = value;
            }
            value = getInitParameter("domain");
            if (value != null) {
                domainConfigFile = value;
            }
            try {
                
                if (domainConfigFile != null) {
                    String rootPath = getServletContext().getRealPath("/");
                    Domain.setProperty("org.apache.slide.webapp.rootpath", 
                            rootPath);
                    URL domainConfigFileURL =
                        getServletContext().getResource(domainConfigFile);
                    if (domainConfigFileURL != null) {
                        Domain.init(domainConfigFileURL);
                    }
                }
                
                if (namespaceName == null) {
                    namespaceName = Domain.getDefaultNamespace();
                    log("No namespace specified, will use default namespace: " +
                            namespaceName);
                }
                
                token = Domain.accessNamespace(new SecurityToken(this), namespaceName);
                if (token == null) {
                    log("Could not access namespace " + namespaceName + ".");
                    throw new UnavailableException("Namespace " + namespaceName +
                                                       " not accessible");
                }
                getServletContext().setAttribute(ATTRIBUTE_NAME, token);
                
            } catch (DomainInitializationFailedError e) {
                log("Could not initialize domain", e);
                throw new UnavailableException(e.toString());
            } catch (Throwable t) {
                t.printStackTrace();
                throw new ServletException(t.toString());
            }
        } else {
            handleLifecycle = false;
        }
        
        // Setup the method factory
        methodFactory =
            WebdavMethodFactory.newInstance((WebdavServletConfig)getServletConfig());
        
        // Check whether directory browsing is enabled, and how it should be
        // accomplished
//        value = getInitParameter("directory-browsing");
//        if (value != null) {
//            if (value.startsWith("/")) {
//                directoryBrowsingTemplate =
//                    getServletContext().getRequestDispatcher(value);
//                if (directoryBrowsingTemplate == null) {
//                    directoryBrowsing = false;
//                }
//            } else {
//                directoryBrowsing = Boolean.valueOf(value).booleanValue();
//            }
//        }
//        if (directoryBrowsing) {
//            directoryIndexGenerator =
//                new DirectoryIndexGenerator
//                (token, (WebdavServletConfig)getServletConfig());
//        }
    }
    
    
    /**
     * Destroy servlet.
     */
    public void destroy() {
        
        if (handleLifecycle) {
            if (token != null) {
                Domain.closeNamespace(token);
            }
        }
    }
    
    
    
    // ------------------------------------------------------ Protected Methods
    
    
    /**
     * Handle a GET request on a collection resource.
     */
//    protected void doGet(HttpServletRequest req, HttpServletResponse res)
//        throws ServletException, IOException {
//        
//        if (directoryBrowsing) {
//            if (directoryBrowsingTemplate != null) {
//                // attributes used by the tag library
//                req.setAttribute("org.apache.slide.NamespaceName",
//                                 token.getName());
//                // attributes for general use
//                req.setAttribute("slide_namespace", token.getName());
//                directoryBrowsingTemplate.forward(req, res);
//            } else {
//                try {
//                    directoryIndexGenerator.generate(req, res);
//                } catch (AccessDeniedException e) {
//                    res.sendError(WebdavStatus.SC_FORBIDDEN);
//                } catch (ObjectNotFoundException e) {
//                    res.sendError(WebdavStatus.SC_NOT_FOUND);
//                } catch (LinkedObjectNotFoundException e) {
//                    res.sendError(WebdavStatus.SC_NOT_FOUND);
//                } catch (SlideException e) {
//                    res.setStatus(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
//                }
//            }
//        } else {
//            res.sendError(WebdavStatus.SC_FORBIDDEN);
//        }
//    }
    
    static boolean isDomLevel2Parser() {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder()
                .getDOMImplementation().hasFeature("Core", "2.0");
        } catch (ParserConfigurationException e) {
            return false;
        } catch (FactoryConfigurationError e) {
            return false;
        }
    }

}

