/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.cms.dispatcher;

import com.arsdigita.cms.installer.ContentCenterSetup;
import com.arsdigita.developersupport.DeveloperSupport;
import com.arsdigita.dispatcher.Dispatcher;
import com.arsdigita.dispatcher.DispatcherHelper;
import com.arsdigita.dispatcher.JSPApplicationDispatcher;
import com.arsdigita.dispatcher.RequestContext;
import com.arsdigita.kernel.security.Initializer;
import com.arsdigita.kernel.security.UserContext;
import com.arsdigita.ui.login.LoginHelper;
import com.arsdigita.util.LockableImpl;
import com.arsdigita.web.LoginSignal;
import com.arsdigita.web.Web;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;


/**
 * <p> The CMS Workspace Dispatcher serves all request made within the
 * Content Center application. This dispatcher is called by the Subsite
 * dispatcher.</p>
 *
 * <p>Here are the steps for a request to
 * <tt>http://yourserver/content-center/cheese</tt>:</p>
 *
 * <ol>
 *   <li><p>A client sends a request to the web server, which passes it on to
 *   the global ACS dispatcher.</p></li>
 *
 *   <li><p>The global ACS dispatcher examines the first part of the URL,
 *   notices that the Content Center application is mounted at
 *   <tt>/content-center</tt> and hands the request to the CMS
 *   Workspace dispatcher.</p></li>
 *
 *   <li><p>The CMS Workspace dispatcher determines whether a <tt>Page</tt>
 *   has been registered to the URL <tt>/cheese</tt>.</p></li>
 *
 *   <li><p>If no <tt>Page</tt> is registered to the URL <tt>/cheese</tt>,
 *   then the CMS Workspace dispatcher hands the request to the
 *   {@link com.arsdigita.dispatcher.JSPApplicationDispatcher}.</p></li>
 * </ol>
 *
 * @author Michael Pih (pihman@arsdigita.com)
 * @version $Revision: #17 $ $DateTime: 2004/04/07 16:07:11 $
 */
public class ContentCenterDispatcher extends LockableImpl
    implements Dispatcher {

    public static final String versionId = "$Id: //cms/dev/src/com/arsdigita/cms/dispatcher/ContentCenterDispatcher.java#17 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    /**
     * The path of the file that maps resources.
     */

    public final static String DEFAULT_MAP_FILE = "/WEB-INF/resources/content-center-map.xml";

    /**
     * Error logging.
     */
    private static Logger s_log =
        Logger.getLogger(ContentCenterDispatcher.class.getName());

    /**
     * Mapping between a relative URL and the class name of a
     * ResourceHandler.
     */
    private static HashMap s_pageClasses = ContentCenterSetup.getURLToClassMap();
    private static HashMap s_pageURLs = ContentCenterSetup.getClassToURLMap();

    /**
     * Instantiated ResourceHandlers cache. This allows for lazy loading.
     */
    private static SimpleCache s_pages = new SimpleCache();

    private Dispatcher m_notFoundHandler;
    private ArrayList m_trailingSlashList = new ArrayList();

    /**
     * Constructor.
     */
    public ContentCenterDispatcher() {
        super();

        m_trailingSlashList = new ArrayList();
        requireTrailingSlash("");

        setNotFoundDispatcher(JSPApplicationDispatcher.getInstance());

        //readFromFile(new java.io.File(MAP_FILE));
    }


    /**
     * Handles requests made to the Content Center package.
     *
     * @param request  The HTTP request
     * @param response The HTTP response
     * @param actx     The request context
     */
    public void dispatch(HttpServletRequest request,
                         HttpServletResponse response,
                         RequestContext actx)
        throws IOException, ServletException {

        DeveloperSupport.startStage("ContentCenterDispatcher.dispatch");

        String url = actx.getRemainingURLPart();
        String originalUrl = actx.getOriginalURL();

        // Empty remaining URL and doesn't end in trailing slash:
        // probably want to redirect.
        if ( m_trailingSlashList.contains(url) && !originalUrl.endsWith("/") ) {
            DispatcherHelper.sendRedirect(response, originalUrl + "/");
            return;
        }

        // Check user access.
        checkUserAccess(request, response, actx);

        ResourceHandler page = getResource(url);
        if ( page != null ) {
            // Serve the page.
            page.init();
            page.dispatch(request, response, actx);

            DispatcherHelper.maybeCacheDisable(response);
        } else {
            // Fall back on the JSP application dispatcher.
            m_notFoundHandler.dispatch(request, response, actx);
        }

        DeveloperSupport.endStage("ContentCenterDispatcher.dispatch");
    }

    /**
     * sets the default page to display if no page can be found for the
     * URL in the page map on dispatch.
     */
    public final void setNotFoundDispatcher(Dispatcher d) {
        m_notFoundHandler = d;
    }

    /**
     * Adds a URL to the list of URLs that are required to have trailing
     * slashes.  A request for url will be redirected to url + "/"
     * if the original URL request (what you see in your browser)
     * doesn't include a trailing slash.  This is required for
     * creating virtual directories, so that relative URLs and redirects
     * work.
     */
    public void requireTrailingSlash(String url) {
        m_trailingSlashList.add(url);
    }

    /** Return the URL stub for the class name, can return null if not
     * mapped */

    public static String getURLStubForClass(String classname) {
        s_log.debug("Getting URL Stub for : " + classname);
        Iterator itr = s_pageURLs.keySet().iterator();
        while (itr.hasNext()) {
            String classname2 = (String)itr.next();
            s_log.debug("key: " + classname + " value: " + (String)s_pageURLs.get(classname2));
        }
        String url = (String)s_pageURLs.get(classname);
        return url;
    }

    /**
     * Fetch a page based on the URL stub.
     *
     * @param url The URL stub following the site-node URL
     * @return A ResourceHandler or null if none exists.
     * @pre (url != null)
     */
    protected ResourceHandler getResource(String url) throws ServletException {

        // First check the pages cache for existing pages.
        ResourceHandler page = (ResourceHandler) s_pages.get(url);
        if ( page == null ) {

            // Next check if the URL maps to a page class.
            String pageClassName = (String) s_pageClasses.get(url);
            if ( pageClassName != null ) {

                Class pageClass;
                try {
                    pageClass = Class.forName(pageClassName);
                } catch (ClassNotFoundException e) {
                    s_log.error("error fetching class for ResourceHandler", e);
                    throw new ServletException(e);
                }

                // Try and instantiate the page.
                try {
                    page = (ResourceHandler) pageClass.newInstance();
                } catch (InstantiationException e) {
                    s_log.error("error instantiating a ResourceHandler", e);
                    throw new ServletException(e);
                } catch (IllegalAccessException e) {
                    s_log.error("error instantiating a ResourceHandler", e);
                    throw new ServletException(e);
                }

                page.init();
                s_pages.put(url, page);
            }
        }
        return page;
    }

    /**
     * Map a page to a URL.
     *
     * @param url The URL
     * @param className The name of the ResourceHandler class
     * @pre (url != null && className != null)
     */
    protected void addResource(String url, String className) {
        s_pageClasses.put(url, className);
        s_pageURLs.put(className, url);
    }

    /**
     * Release the page at the specified URL.
     *
     * @param url The URL
     * @pre (url != null)
     */
    public static void releaseResource(String url) {
        s_pages.remove(url);
    }

    /**
     * Verify that the user is logged in and is able to view the
     * page. Subclasses can override this method if they need to, but
     * should always be sure to call super.checkUserAccess(...)
     *
     * @param request  The HTTP request
     * @param response The HTTP response
     * @param actx     The request context
     **/
    protected void checkUserAccess(final HttpServletRequest request,
                                   final HttpServletResponse response,
                                   final RequestContext actx)
            throws ServletException {
        if (!Web.getUserContext().isLoggedIn()) {
            throw new LoginSignal(request);
        }
    }

    /**
     * Redirects the client to the login page, setting the return url to
     * the current request URI.
     *
     * @exception ServletException If there is an exception thrown while
     * trying to redirect, wrap that exception in a ServletException
     **/
    protected void redirectToLoginPage(HttpServletRequest req,
                                       HttpServletResponse resp)
        throws ServletException {
        String url = Initializer.getSecurityHelper()
            .getLoginURL(req)
            +"?"+UserContext.RETURN_URL_PARAM_NAME
            +"="+UserContext.encodeReturnURL(req);
        try {
            LoginHelper.sendRedirect(req, resp, url);
        } catch (IOException e) {
            s_log.error("IO Exception", e);
            throw new ServletException(e.getMessage(), e);
        }
    }
}
