/*
    PCS - A Framework For Java Web Applications
    Copyright (C) 2002 Patrick Carl, patrick.carl@web.de
 
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.
 
    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.
 
    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
    $Id: ControllerServlet.java,v 1.3 2003/01/19 23:41:36 pcs_org Exp $
 
 */

package de.spieleck.pcs.http;

import de.spieleck.pcs.Constants;
import de.spieleck.pcs.conf.ConfigAdmin;
import de.spieleck.pcs.conf.ConfigUser;
import de.spieleck.pcs.view.View;
import de.spieleck.pcs.view.ViewException;
import de.spieleck.pcs.factory.DefaultFactory;
import de.spieleck.pcs.action.Action;
import de.spieleck.pcs.action.ActionResult;
import de.spieleck.pcs.action.ActionException;
import de.spieleck.pcs.factory.FactoryException;
import de.spieleck.pcs.factory.NoSuchKeyException;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.OutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.ServletConfig;

/**
 * This servlet acts as a Controller defined by the Front Controller Pattern.
 * The ControllerServlet is the main gateway of a pcs based web application
 * and every request should use this servlet.<br>
 * The ControllerServlet uses a file to lookup the mappings between action keys
 * and the implementing Action classes. This file is a Java Properties file
 * specified by the servlet init parameter "pcsfw.controller.actions". If this
 * is not set, "/conf/actions.properties" will be used instead.<br>
 * If a init parameter "pcsfw.controller.viewconfig" is specified, this file
 * name will be used to configure the used View
 * @author Patrick Carl
 */
public class ControllerServlet extends HttpServlet implements ConfigUser, Constants {
    
    protected static String description = "This servlet acts a Controller";
    
    protected ControllerConfig controllerConfig;
    
    /** Initializes the servlet.
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        try{
            controllerConfig = new XmlControllerConfig(
            getInitParameter(CONTOLLER_XML_CONFIG_PARAM));
        } catch(ControllerException e){
            throw new ServletException("Could not create ControllerConfig", e);
        }
        
        View.setUseJTidy(controllerConfig.useJTidy());
        ConfigAdmin.getInstance().addConfigUser(this);
    }
    
    /** Destroys the servlet.
     */
    public void destroy() {
    }
    
    /**
     * returns an Action matching the given request. To determine the key of the
     * action, at first the path is checked and as key is taken the string from
     * the beginning of the path to the second occurance of '/'. As example if a
     * request is processed with the URL
     * "http://www.server.com/controller/action/" then the String "action"
     * is used as key.
     *
     * If the ActionFactory returns no Action object for this key, the
     * Controller checks whether there is a request parameter named "action".
     * If such a parameter is found the Controller tries to get an Action from
     * the ActionFactory with such a key.
     *
     * If this fails, the Controller tries to get an Action from the
     * ActionFactory with the key "default". If again no matching action can
     * be found, a ServletException is thrown.
     * @param request the request to the Servlet
     * @return a matching action
     * @throws ServletException if no matching action could be found
     */
    
    protected ServiceInfo getService(HttpServletRequest request)
    throws ControllerException {
        
        // deal with exceptions caused before
        Exception e = (Exception) request.getSession().
        getAttribute(CONTROLLER_SESSION_EXCEPTION_PARAM_NAME);
        if( e != null){
            return controllerConfig.getServiceInfo(EXCEPTION_SERVICE_NAME);
        }
        
        String path = request.getPathInfo();
        ServiceInfo service = null;
        String serviceName = null;
        if(path != null){
            int end = path.indexOf('/', 2);
            if(end > 0)
                serviceName = path.substring(1, end);
            else
                serviceName = path.substring(1);
            if(serviceName != null && serviceName.length() > 0)
                service = lookupService(serviceName);
        }
        if (service == null) {
            serviceName = request.getParameter(REQUEST_SERVICE_NAME_PARAM);
            if(serviceName != null && serviceName.length() > 0)
                service = lookupService(serviceName);
        }
        
        if(service == null)
            throw new ControllerException("No suiting service found.");
        return service;
    }
    
    /**
     * wrapps the getServiceInfo of ControllerConfig to return null when
     * no Service with the given name can be found instead of throwing
     * an Exception
     */
    private ServiceInfo lookupService(String name){
        try{
            return controllerConfig.getServiceInfo(name);
        }
        catch(Exception e){
            return null;
        }
    }
    
    /** Processes requests for both HTTP <code>GET</code>
     * and <code>POST</code> methods.
     * It gets an action via the getAction-Method and performs this action with all
     * parameters supplied by the request and the request and the response. The
     * request is put in the hashtable of the action
     * with the key "request" and the reponse is put with the key "response".
     * @param request servlet request
     * @param response servlet response
     */
    protected void processRequest(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
        Action action = null;
        try{
            ServiceInfo service = getService(request);
            
            action = service.getAction();
            
            Hashtable h = HttpUtil.getParameters(request);
            h.put(CONTEXT_REAL_PATH, getServletContext().getRealPath("/"));
            Exception e = (Exception) request.getSession().getAttribute(
            CONTROLLER_SESSION_EXCEPTION_PARAM_NAME);
            
            if(e!= null){
                h.put(CONTROLLER_SESSION_EXCEPTION_PARAM_NAME, e);
                request.getSession().removeAttribute(
                CONTROLLER_SESSION_EXCEPTION_PARAM_NAME);
            }
            ActionResult ar = action.perform(h);
            display(request, response, ar, service);
        }
        catch(ActionException e){
            e.printStackTrace();
            handleException(request, response, e);
        }
        catch(Exception e){
            e.printStackTrace();
            throw new ServletException(e.getMessage());
        }
    }
    
    protected void display(HttpServletRequest request,
    HttpServletResponse response, ActionResult ar, ServiceInfo service)
    throws IOException{
        // add the session id to the ActionResult.parameters
        // So we do not rely on cookies
        ar.getParameters().put("sessionid", request.getSession().getId());
        // prepare the OutputStream
        response.setContentType("text/html");
        OutputStream os = response.getOutputStream();
        View view = new View(os);
        try{
            String stylesheet = null;
            if(service.isDynamicView())
                stylesheet = ar.getStyleSheet();
            if(stylesheet == null ||stylesheet.length() == 0)
                stylesheet = service.getStylesheet();
            
            view.output(ar, controllerConfig.getStyleSheetAbsoluteFileName(
            stylesheet));
            view.cleanUp();
        } catch(Exception ve){
            ve.printStackTrace();
            ve.printStackTrace(new java.io.PrintStream(os));
        }
        os.close();
    }
    
    /**
     * This method handles exceptions by addind the exception to the session
     * and call processRequest. Then the called method will display the
     * exception.
     */
    protected void handleException(HttpServletRequest request,
    HttpServletResponse response, Exception e) throws IOException,
    ServletException{
        request.getSession().setAttribute(
        CONTROLLER_SESSION_EXCEPTION_PARAM_NAME, e);
        processRequest(request, response);
    }
    
    /**
     * by this method the Controller Servlet checks, if a given request is
     * authorized or not. Should be overwritten if neccessary, since this
     * implementaiton allways returns true.
     * @returns <code>true</code> if the request is authorized <br>
     * <code>false</code> if the request is not authorized
     * @param request the request which is checked for authorization
     */
    protected boolean checkAuthorization(HttpServletRequest request){
        return true;
    }
    /** Handles the HTTP <code>GET</code> method by calling processRequest
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }
    
    /** Handles the HTTP <code>POST</code> method by calling processRequest
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }
    
    /** Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return description;
    }
    
    /** resets the configuration by calling the Servlet's init method
     *
     */
    public void resetConfig() {
        try{
            init();
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}