/*
 * Copyright (C) 2002-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.web;

import com.arsdigita.util.Assert;
import com.arsdigita.dispatcher.InitialRequestContext;
import com.arsdigita.dispatcher.DispatcherHelper;
import com.arsdigita.dispatcher.RequestContext;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.util.Assert;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.web.Application;
import com.arsdigita.kernel.SiteNode;
import com.arsdigita.kernel.KernelExcursion;
import com.arsdigita.kernel.KernelRequestContext;
import com.arsdigita.kernel.security.SessionContext;
import com.arsdigita.kernel.security.UserContext;
import com.arsdigita.sitenode.SiteNodeRequestContext;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import org.apache.log4j.Logger;

/**
 * <p>The base servlet for CCM applications.  It manages database
 * transactions, prepares an execution context for the request, and
 * traps and handles requests to redirect.</p>
 *
 * <p>Most CCM applications will extend this class by implementing
 * {@link #doService(HttpServletRequest,HttpServletResponse,Application)} to
 * perform application-private dispatch to UI code.</p>
 *
 * @see com.arsdigita.web.BaseServlet
 * @see com.arsdigita.web.DispatcherServlet
 * @see com.arsdigita.web.RedirectSignal
 * @author Justin Ross &lt;<a href="mailto:jross@redhat.com">jross@redhat.com</a>&gt;
 */
public abstract class BaseApplicationServlet extends BaseServlet {
    public static final String versionId =
        "$Id: //core-platform/dev/src/com/arsdigita/web/BaseApplicationServlet.java#34 $" +
        "$Author: dennis $" +
        "$DateTime: 2004/04/07 16:07:11 $";

    private static Logger s_log = Logger.getLogger
        (BaseApplicationServlet.class);

    /**
     * <p>The ID of the application whose service is requested.  This
     * request attribute must be set by a previous servlet or filter
     * before this servlet can proceed.  In CCM, the default servlet,
     * DispatcherServlet, sets this attribute.</p>
     */
    public static final String APPLICATION_ID_ATTRIBUTE =
        BaseApplicationServlet.class.getName() + ".application_id";

    /**
     * <p>The same as {@link #APPLICATION_ID_ATTRIBUTE}, but as a
     * request parameter.  This is present so applications not using
     * the dispatcher servlet may accept requests directly to their
     * servlets, provided the application ID is given in the URL.</p>
     */
    public static final String APPLICATION_ID_PARAMETER = "app-id";

    /**
     * <p>Augments the context of the request and delegates to {@link
     * #doService(HttpServletRequest,HttpServletResponse,Application)}.</p>
     *
     * @see com.arsdigita.web.BaseServlet#doService(HttpServletRequest,HttpServletResponse)
     */
    protected final void doService(final HttpServletRequest sreq,
                                   final HttpServletResponse sresp)
            throws ServletException, IOException {
        final Application app = getApplication(sreq);

        if (app == null) {
            sresp.sendError(404, "Application not found");
            throw new IllegalStateException("Application not found");
        }

        Web.getContext().setApplication(app);

        final RequestContext rc = makeLegacyContext
            (sreq, app, Web.getUserContext());

        DispatcherHelper.setRequestContext(sreq, rc);

        final ServletException[] servletException = { null };
        final IOException[] ioException = { null };

        new KernelExcursion() {
            protected final void excurse() {
                setLocale(rc.getLocale());
                setResource(app);

                try {
                    doService(sreq, sresp, app);
                } catch (ServletException se) {
                    servletException[0] = se;
                } catch (IOException ioe) {
                    ioException[0] = ioe;
                }
            }
        }.run();

        if (servletException[0] != null) {
            throw servletException[0];
        }

        if (ioException[0] != null) {
            throw ioException[0];
        }
    }

    /**
     * <p>The method that {@link
     * #doService(HttpServletRequest,HttpServletResponse)} calls.
     * Servlet authors should implement this method to perform
     * application-specific request handling.</p>
     *
     * @see javax.servlet.http.HttpServlet#service(HttpServletRequest,HttpServletResponse)
     */
    protected abstract void doService(HttpServletRequest sreq,
                                      HttpServletResponse sresp,
                                      Application app)
            throws ServletException, IOException;

    private Application getApplication(HttpServletRequest sreq) {
        s_log.debug("Resolving the application that will handle this request");

        BigDecimal id = (BigDecimal) sreq.getAttribute
            (APPLICATION_ID_ATTRIBUTE);

        if (id == null) {
            s_log.debug("I didn't receive an application ID with the " +
                        "servlet request; trying to get it from the " +
                        "query string");

            final String value = sreq.getParameter(APPLICATION_ID_PARAMETER);

            if (value != null) {
                try {
                    id = new BigDecimal(value);
                } catch (NumberFormatException nfe) {
                    throw new IllegalStateException
                        ("Could not parse '" + value + "' into a BigDecimal");
                }
            }
        }

        Assert.assertNotNull(id, "BigDecimal id");

        if (s_log.isDebugEnabled()) {
            s_log.debug("Retrieving application " + id + " from the " +
                        "database");
        }

        return Application.retrieveApplication(id);
    }

    private RequestContext makeLegacyContext(HttpServletRequest sreq,
                                             final Application app,
                                             final UserContext uc) {
        s_log.debug("Setting up a legacy context object");

        sreq = DispatcherHelper.restoreOriginalRequest(sreq);

        final InitialRequestContext irc = new InitialRequestContext
            (sreq, getServletContext());
        final SessionContext sc = uc.getSessionContext();

        final KernelRequestContext krc = new KernelRequestContext
            (irc, sc, uc);


        SiteNode node = null;
        try {
            node = SiteNode.getSiteNode(app.getPrimaryURL(),
                                        true);
        } catch (DataObjectNotFoundException ex) {
            throw new UncheckedWrapperException("cannot find root sitenode");
        }

        if (node == null) {
            s_log.debug("There is no site node at this URL; storing a " +
                        "KernelRequestContext");

            return krc;
        } else {
            s_log.debug("Creating a SiteNodeRequestContext");

            final SiteNodeRequestContext snrc = new SiteNodeRequestContext
                (sreq, krc, node, sreq.getServletPath() + "/");

            return snrc;
        }
    }
}
