/*
 * Copyright (c) 1999 The Java Apache Project.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software and design ideas developed by the Java
 *    Apache Project (http://java.apache.org/)."
 *
 * 4. The names "Cocoon", "Cocoon Servlet" and "Java Apache Project" must
 *    not be used to endorse or promote products derived from this software
 *    without prior written permission.
 *
 * 5. Products derived from this software may not be called "Cocoon"
 *    nor may "Cocoon" and "Java Apache Project" appear in their names without
 *    prior written permission of the Java Apache Project.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software and design ideas developed by the Java
 *    Apache Project (http://java.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE JAVA APACHE PROJECT "AS IS" AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE JAVA APACHE PROJECT OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Java Apache Project. For more information
 * on the Java Apache Project please see <http://java.apache.org/>.
 */

package org.apache.cocoon;

import java.io.*;
import java.net.*;
import java.util.*;
import org.w3c.dom.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.cocoon.cache.*;
import org.apache.cocoon.store.*;
import org.apache.cocoon.parser.*;
import org.apache.cocoon.producer.*;
import org.apache.cocoon.formatter.*;
import org.apache.cocoon.processor.*;
import org.apache.cocoon.framework.*;
import org.apache.cocoon.interpreter.*;

/**
 * The Cocoon publishing engine.
 *
 * This class implements the engine that does all the document processing.
 *
 * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
 * @version  */

public class Engine implements Defaults {

    Configurations configurations;

    ProducerFactory producers;
    ProcessorFactory processors;
    FormatterFactory formatters;

    Manager manager;
    Browsers browsers;
    Parser parser;
    Cache cache;
    Store store;

    /**
     * This method initializes the engine.
     */
    public Engine(String configurationFile) throws Exception {

        // Collect the configurations
        configurations = new Configurations(configurationFile);

        // Create the object manager which is both Factory and Director
        // and register it
        manager = new Manager();
        manager.setRole("factory", manager);

        // Create the parser and register it
        parser = (Parser) manager.create((String) configurations.get(PARSER_PROP, PARSER_DEFAULT));
        manager.setRole("parser", parser);

        // Create the store and register it
        store = (Store) manager.create((String) configurations.get(STORE_PROP, STORE_DEFAULT), configurations.getConfigurations(STORE_PROP));
        manager.setRole("store", store);
        
        // Create the cache and register it
        cache = (Cache) manager.create((String) configurations.get(CACHE_PROP, CACHE_DEFAULT), configurations.getConfigurations(CACHE_PROP));
        manager.setRole("cache", cache);

        // Create the interpreter factory and register it
        InterpreterFactory interpreters = (InterpreterFactory) manager.create("org.apache.cocoon.interpreter.InterpreterFactory", 
            configurations.getConfigurations(INTERPRETER_PROP));
        manager.setRole("interpreters", interpreters);
        
        // Create the browser table
        browsers = (Browsers) manager.create("org.apache.cocoon.Browsers", 
            configurations.getConfigurations(BROWSERS_PROP));

        // Create the producer factory
        producers = (ProducerFactory) manager.create("org.apache.cocoon.producer.ProducerFactory", 
            configurations.getConfigurations(PRODUCER_PROP));

        // Create the processor factory
        processors = (ProcessorFactory) manager.create("org.apache.cocoon.processor.ProcessorFactory", 
            configurations.getConfigurations(PROCESSOR_PROP));

        // Create the formatter factory
        formatters = (FormatterFactory) manager.create("org.apache.cocoon.formatter.FormatterFactory", 
            configurations.getConfigurations(FORMATTER_PROP));
    }

    /**
     * This method is called to start the processing when calling the engine
     * from the Cocoon servlet.
     */
    public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception {

        // if verbose mode is on, take a time snapshot for later evaluation
        long time = 0;
        if (VERBOSE) time = System.currentTimeMillis();

        // this may be needed if debug is turned on
        ByteArrayOutputStream debugStream = null;

        // get the output writer
        PrintWriter out = response.getWriter();
        
        // get the request flags
        boolean CACHE = getFlag(request, "cache", true);
        boolean DEBUG = getFlag(request, "debug", false);

        // turn on debugging facilities redirecting the standard
        // streams to the output stream
        // WARNING: this is not thread-safe. Debugging a request
        // while another is being processed may result in mixed
        // debug output.
        if (DEBUG) {
            debugStream = new ByteArrayOutputStream();
            PrintStream stream = new PrintStream(new BufferedOutputStream(debugStream), true);
            System.setOut(stream);
            System.setErr(stream);
        }
        
        Page page = null;
        
        // ask if the cache contains the page requested and if it's
        // a valid instance (no changeable points have changed)
        if (CACHE) page = cache.getPage(request);

        // the page was not found in the cache or the cache was 
        // disabled, we need to process it
        if (page == null) {
        	
            // create the Page wrapper
            page = new Page();
            
            // get the document producer
            Producer producer = producers.getProducer(request);
            
            // set the producer as a page changeable point
            page.setChangeable(producer);
            
            // pass the produced stream to the parser
            Document document = producer.getDocument(request);

            // pass needed parameters to the processor pipeline
            Hashtable environment = new Hashtable();
            environment.put("path", producer.getPath(request));
            environment.put("browser", browsers.map(request.getHeader("user-Agent")));
            environment.put("request", request);

            // process the document through the document processors
            try {
                while (true) {
                    Processor processor = processors.getProcessor(document);
                    document = processor.process(document, environment);
                    page.setChangeable(processor);
                }
            } catch (RuntimeException processingDone) {}
            
            // get the right formatter for the page
            Formatter formatter = formatters.getFormatter(document);
            
            // FIXME: I know it sucks to encapsulate a nice stream into
            // a long String to push it into the cache. In the future,
            // we'll find a smarter way to do it.
            
            // format the page
            StringWriter writer = new StringWriter();
            formatter.format(document, writer);
            
            // fill the page bean with content
            page.setContent(writer.toString());
            page.setContentType(formatter.getMIMEType());
        }

        if (DEBUG) {
            // send the debug message and restore the streams
            Frontend.print(out, "Debugging " + request.getPathInfo(), debugStream.toString());
            System.setOut(System.out);
            System.setErr(System.err);
        } else {
            // set the response content type and
            // send the page to the client
            response.setContentType(page.getContentType());
            out.println(page.getContent());
        }

        // if verbose mode is on the the output type allows it
        // print some processing info as a comment
        if (VERBOSE && (page.isText())) {
            time = System.currentTimeMillis() - time;
            out.println("<!-- This page was served " 
                + (page.isCached() ? "from cache " : "") 
                + "in " + time + " milliseconds by " 
                + Cocoon.version() + " -->");
        }
        
        // send all content so that client doesn't wait while caching.
        out.flush();
        
        // cache the created page.
        cache.setPage(page, request);
    }

    /**
     * Returns the value of the request flag
     */
    private boolean getFlag(HttpServletRequest request, String name, boolean normal) {
        String flag = request.getParameter(name);
        return (flag != null) ? flag.toLowerCase().equals("true") : normal;
    }
    
    /**
     * Returns an hashtable of parameters used to report the internal status.
     */
    public Hashtable getStatus() {
        Hashtable table = new Hashtable();
        table.put("Browsers", ((Status) browsers).getStatus());
        table.put("Producers", ((Status) producers).getStatus());
        table.put("Parser", ((Status) parser).getStatus());
        table.put("Processors", ((Status) processors).getStatus());
        table.put("Formatters", ((Status) formatters).getStatus());
        table.put("Cache", ((Status) cache).getStatus());
        table.put("Store", ((Status) store).getStatus());
        return table;
    }
}
