/*
 * Copyright 2003 (C) Sam Pullara. All Rights Reserved.
 * 
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided that the
 * following conditions are met: 1. Redistributions of source code must retain
 * copyright statements and notices. Redistributions must also contain a copy
 * of this document. 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.
 * The name "groovy" must not be used to endorse or promote products derived
 * from this Software without prior written permission of The Codehaus. For
 * written permission, please contact info@codehaus.org. 4. Products derived
 * from this Software may not be called "groovy" nor may "groovy" appear in
 * their names without prior written permission of The Codehaus. "groovy" is a
 * registered trademark of The Codehaus. 5. Due credit should be given to The
 * Codehaus - http://groovy.codehaus.org/
 * 
 * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``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 CODEHAUS 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.
 *  
 */

/*
 * 
 * The Seasar Software License, Version 1.1
 *
 * Copyright (c) 2003-2004 The Seasar 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. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgement:  
 *    "This product includes software developed by the 
 *    Seasar Project (http://www.seasar.org/)."
 *    Alternately, this acknowledgement may appear in the software
 *    itself, if and wherever such third-party acknowledgements 
 *    normally appear.
 *
 * 4. Neither the name "The Seasar Project" nor the names of its
 *    contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission of 
 *    the Seasar Project.
 *
 * THIS SOFTWARE IS PROVIDED ``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 SEASAR 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.
 */

package org.seasar.groovy.servlet;

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.MetaClass;
import groovy.servlet.GroovyServlet;
import groovy.servlet.ServletCategory;
import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceConnector;
import groovy.util.ResourceException;
import groovy.util.ScriptException;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.codehaus.groovy.runtime.GroovyCategorySupport;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.util.StringUtil;

/**
 * 
 * @author takai
 * @version $Revision: 1.2 $
 */
public class S2GroovyServlet extends HttpServlet implements ResourceConnector {

	private static class ServletCacheEntry {

		private Map dependencies = new HashMap();

		private long lastModified;

		private Class servletScriptClass;
	}

	public static final String CONFIG_PATH_KEY = "configPath";

	private static GroovyScriptEngine gse;

	private static ClassLoader parent;

	private static Map servletCache = Collections
			.synchronizedMap(new HashMap());

	public static S2Container getContainer() {
		return SingletonS2ContainerFactory.getContainer();
	}

	private ServletContext sc;

	public void destroy() {
		SingletonS2ContainerFactory.destroy();
	}

	public URLConnection getResourceConnection(String name)
			throws ResourceException {
		try {
			URL url = sc.getResource("/" + name);
			if (url == null) {
				url = sc.getResource("/WEB-INF/groovy/" + name);
				if (url == null) {
					throw new ResourceException("Resource " + name
							+ " not found");
				}
			}
			return url.openConnection();
		} catch (IOException ioe) {
			throw new ResourceException("Problem reading resource " + name);
		}
	}

	public ServletContext getServletContext() {
		return sc;
	}

	public void init() {
		initGroovy();
		initS2();
	}

	/**
	 * @param config
	 */
	protected void initGroovy() {
		final ServletConfig config = getServletConfig();

		// Use reflection, some containers don't load classes properly
		MetaClass.setUseReflection(true);

		// Get the servlet context
		sc = config.getServletContext();
		sc.log("Groovy servlet initialized");

		// Ensure that we use the correct classloader so that we can find
		// classes in an application server.
		parent = Thread.currentThread().getContextClassLoader();
		if (parent == null)
			parent = GroovyServlet.class.getClassLoader();

		// Set up the scripting engine
		gse = new GroovyScriptEngine(this);
	}

	protected void initS2() {
		final ServletConfig config = getServletConfig();
		String configPath = null;

		if (config != null) {
			configPath = config.getInitParameter(CONFIG_PATH_KEY);
		}
		if (!StringUtil.isEmpty(configPath)) {
			SingletonS2ContainerFactory.setConfigPath(configPath);
		}
		SingletonS2ContainerFactory.setServletContext(getServletContext());
		SingletonS2ContainerFactory.init();
	}

	public void service(ServletRequest request, ServletResponse response)
			throws ServletException, IOException {

		// Convert the generic servlet request and response to their Http
		// versions
		final HttpServletRequest httpRequest = (HttpServletRequest) request;
		final HttpServletResponse httpResponse = (HttpServletResponse) response;

		// S2Container
		final S2Container container = getContainer();
		container.setRequest(httpRequest);

		// Get the name of the Groovy script
		int contextLength = httpRequest.getContextPath().length();
		final String scriptFilename = httpRequest.getRequestURI().substring(
				contextLength).substring(1);

		// Set up the script context
		final Binding binding = new Binding();
		binding.setVariable("request", httpRequest);
		binding.setVariable("response", httpResponse);
		binding.setVariable("application", sc);
		binding.setVariable("session", httpRequest.getSession(true));
		binding.setVariable("out", httpResponse.getWriter());

		binding.setVariable("container", container);

		// Form parameters. If there are multiple its passed as a list.
		for (Enumeration paramEnum = request.getParameterNames(); paramEnum
				.hasMoreElements();) {
			String key = (String) paramEnum.nextElement();
			if (binding.getVariable(key) == null) {
				String[] values = request.getParameterValues(key);
				if (values.length == 1) {
					binding.setVariable(key, values[0]);
				} else {
					binding.setVariable(key, values);
				}
			}
		}

		// Set it to HTML by default
		response.setContentType("text/html");

		// Run the script
		try {
			Closure closure = new Closure(gse) {
				public Object call() {
					try {
						return ((GroovyScriptEngine) getDelegate()).run(
								scriptFilename, binding);
					} catch (ResourceException e) {
						throw new RuntimeException(e);
					} catch (ScriptException e) {
						throw new RuntimeException(e);
					}
				}
			};

			List categories = new ArrayList();
			categories.add(ServletCategory.class);
			categories.add(S2GroovyServletCategory.class);
			GroovyCategorySupport.use(categories, closure);

		} catch (RuntimeException re) {
			Throwable e = re.getCause();
			if (e instanceof ResourceException) {
				httpResponse.sendError(404);
			} else {
				if (e != null) {
					sc.log("An error occurred processing the request", e);
				} else {
					sc.log("An error occurred processing the request", re);
				}
				httpResponse.sendError(500);
			}
		}
	}
}