/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.commons.chain2.web.servlet;

import java.io.IOException;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.chain2.Catalog;
import org.apache.commons.chain2.Command;
import org.apache.commons.chain2.config.ConfigUtil;
import org.apache.commons.chain2.impl.CatalogFactoryBase;
import org.apache.commons.chain2.web.ChainServlet;

/**
 * <p>Custom subclass of {@link ChainServlet} that also dispatches incoming
 * requests to a configurable {@link Command} loaded from the specified
 * {@link Catalog}.</p>
 *
 * <p>In addition to the <em>servlet</em> init parameters supported by
 * {@link ChainServlet}, this class supports the following additional
 * parameters:</p>
 * <ul>
 * <li><strong>org.apache.commons.chain2.CATALOG</strong> - Name of the
 *     catalog from which to acquire commands to be executed.  If not
 *     specified, the default catalog for this application will be used.</li>
 * <li><strong>org.apache.commons.chain2.COMMAND</strong> - Name of the
 *     {@link Command} (looked up in our configured {@link Catalog} used
 *     to process all incoming servlet requests.  If not specified,
 *     defaults to <code>command</code>.</li>
 * </ul>
 *
 * <p>Also, the <code>org.apache.commons.chain2.CONFIG_ATTR</code>
 * init parameter is also used to identify the
 * {@link org.apache.commons.chain2.Context} attribute under
 * which our configured {@link Catalog} will be made available to
 * {@link Command}s processing our requests, in addition to its definition
 * of the <code>ServletContext</code> attribute key under which the
 * {@link Catalog} is available.</p>
 *
 * @version $Id$
 */
public class ChainProcessor extends ChainServlet {

    // ------------------------------------------------------ Manifest Constants

    /**
     * <p>The name of the servlet init parameter containing the name of the
     * {@link Catalog} to use for processing incoming requests.</p>
     */
    public static final String CATALOG = "org.apache.commons.chain2.CATALOG";

    /**
     * <p>The default request attribute under which we expose the
     * {@link Catalog} being used to subordinate {@link Command}s.</p>
     */
    public static final String CATALOG_DEFAULT = "org.apache.commons.chain2.CATALOG";

    /**
     * <p>The name of the servlet init parameter containing the name of the
     * {@link Command} (loaded from our configured {@link Catalog} to use
     * for processing each incoming request.</p>
     */
    public static final String COMMAND = "org.apache.commons.chain2.COMMAND";

    /**
     * <p>The default command name.</p>
     */
    private static final String COMMAND_DEFAULT = "command";

    /** serialVersionUID */
    private static final long serialVersionUID = 20120724L;

    /**
     * <p>Configure a {@link ServletWebContext} for the current request, and
     * pass it to the <code>execute()</code> method of the specified
     * {@link Command}, loaded from our configured {@link Catalog}.</p>
     *
     * @param request The request we are processing
     * @param response The response we are creating
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet exception occurs
     */
    @Override
    public void service(final HttpServletRequest request, final HttpServletResponse response)
            throws IOException, ServletException {
        String catalog = getServletConfig().getInitParameter(CATALOG);
        Catalog<String, Object, ServletWebContext<String, Object>> theCatalog;
        String attribute = getServletConfig().getInitParameter(CONFIG_ATTR);
        if (attribute != null) {
            ServletContext servletContext = getServletContext();
            Object testAttribute = servletContext.getAttribute(attribute);

            if (testAttribute instanceof Catalog) {
                /* "attribute" should always contain an instance of a
                 * Catalog class according to the historical chain convention.
                 * Although, we now double-check that it is in fact a catalog
                 * here, we still need to suppress warnings because of the
                 * type erasure of generics. */
                Catalog<String, Object, ServletWebContext<String, Object>> attributeCatalog =
                    ConfigUtil.cast(testAttribute);
                theCatalog = attributeCatalog;
            } else {
                throw new IllegalArgumentException(String.format(
                    "The object stored as the attribute [%s] was not of the expected type[Catalog]",
                    attribute));
            }
        } else if (catalog != null) {
            theCatalog = CatalogFactoryBase.<String, Object, ServletWebContext<String, Object>>
                            getInstance().getCatalog(catalog);
        } else {
            theCatalog = CatalogFactoryBase.<String, Object, ServletWebContext<String, Object>>
                            getInstance().getCatalog();
        }

        if (attribute == null) {
            request.setAttribute(CATALOG_DEFAULT, theCatalog);
        }

        Command<String, Object, ServletWebContext<String, Object>> cmd =
                theCatalog.getCommand(getCommand());
        ServletWebContext<String, Object> context =
                new ServletWebContextBase(getServletContext(), request, response);
        cmd.execute(context);
    }

    private String getCommand() {
        String command = getServletConfig().getInitParameter(COMMAND);
        if (command == null) {
            command = COMMAND_DEFAULT;
        }
        return command;
    }

}
