package com.gradecak.groovy;

/*
 * Copyright 2003-2007 the original author or authors.
 *
 * Licensed 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.
 */

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.servlet.AbstractHttpServlet;
import groovy.servlet.ServletBinding;
import groovy.servlet.ServletCategory;
import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;
import groovy.util.ScriptException;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.codehaus.groovy.runtime.GroovyCategorySupport;
import org.springframework.beans.BeansException;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
 * This Servlet is a copy of the original groovy.servlet.GroovyServlet.
 * It adds the capability to add your own beans definition to your groovy scripts.
 * 
 * Example using Spring's DispatcherServlet: 
 * 
 *  ...
 *  
 * <bean id="controller" class= "org.springframework.web.servlet.mvc.ServletWrappingController">
 *	  	<property name="servletClass">
 *	    	<value>com.gradecak.groovy.test.GroovyServlet</value>	    
 *	  	</property>
 *		<property name="initParameters">
 *	    	<props>
 *		      <prop key="yourService">yourServiceBean</prop>
 *	    	</props>
 *	  	</property>
 *	</bean>
 *
 * ...
 * 
 * and of course you have to update the web.xml
 * 
 * I am not providing a classical servlet solution but you could use it either. 
 *
 * @author Daniel Gradecak
 * 
 * @see groovy.servlet.GroovyServlet
 * @see groovy.servlet.ServletBinding
 */
public class GroovyServlet extends AbstractHttpServlet {

    /**
     * The script engine executing the Groovy scripts for this servlet
     */
    private GroovyScriptEngine gse;
        
    private Map<String, Object> servicesMap = new HashMap<String, Object>();
    
    /**
     * Initialize the GroovyServlet.
     *
     * @throws ServletException
     *  if this method encountered difficulties
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        // Set up the scripting engine
        gse = createGroovyScriptEngine();
        
//        gs = new GroovyShell();
        
        WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        
        Enumeration en = config.getInitParameterNames();        
        while(en.hasMoreElements()){
        	String elem = (String)en.nextElement();
        	
        	try {
				Object bean = wac.getBean(config.getInitParameter(elem));
				
				servicesMap.put(elem, bean);
			} catch (BeansException e) {
				e.printStackTrace();				
				System.err.println("The bean does not exist in the Spring context. Bean name : "+config.getInitParameter(elem));
			}
        	
        	
        }
        
        servletContext.log("Groovy servlet initialized on " + gse + ".");
    }

    /**
     * Handle web requests to the GroovyServlet
     */
    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {

        // Get the script path from the request - include aware (GROOVY-815)
        final String scriptUri = getScriptUri(request);

        // Set it to HTML by default
        response.setContentType("text/html; charset=UTF-8");

        // Set up the script context
        
        
        final Binding binding = new ServletBinding(request, response, servletContext);
        
        for (String service : servicesMap.keySet()) {		
        	binding.setVariable(service, servicesMap.get(service));
		}
        
        // Run the script
        try {

            Closure closure = new Closure(gse) {

                public Object call() {
                    try {
                    	
                    	Object obj = ((GroovyScriptEngine) getDelegate()).run(scriptUri, binding);                    	
                    	return obj;
                    } catch (ResourceException e) {
                        throw new RuntimeException(e);
                    } catch (ScriptException e) {
                        throw new RuntimeException(e);
                    }
                }

            };

			GroovyCategorySupport.use(ServletCategory.class, closure);
            /*
             * Set reponse code 200.
             */
            response.setStatus(HttpServletResponse.SC_OK);
        } catch (RuntimeException runtimeException) {
            StringBuffer error = new StringBuffer("GroovyServlet Error: ");
            error.append(" script: '");
            error.append(scriptUri);
            error.append("': ");
            Throwable e = runtimeException.getCause();
            /*
             * Null cause?!
             */
            if (e == null) {
                error.append(" Script processing failed.");
                error.append(runtimeException.getMessage());
                if (runtimeException.getStackTrace().length > 0)
                    error.append(runtimeException.getStackTrace()[0].toString());
                servletContext.log(error.toString());
                System.err.println(error.toString());
                runtimeException.printStackTrace(System.err);
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error.toString());
                return;
            }
            /*
             * Resource not found.
             */
            if (e instanceof ResourceException) {
                error.append(" Script not found, sending 404.");
                servletContext.log(error.toString());
                System.err.println(error.toString());
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
            /*
             * Other internal error. Perhaps syntax?! 
             */
            servletContext.log("An error occurred processing the request", runtimeException);
            error.append(e.getMessage());
            if (e.getStackTrace().length > 0)
                error.append(e.getStackTrace()[0].toString());
            servletContext.log(e.toString());
            System.err.println(e.toString());
            runtimeException.printStackTrace(System.err);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
        } finally {
            /*
             * Finally, flush the response buffer.
             */
            response.flushBuffer();
            // servletContext.log("Flushed response buffer.");
        }
    }

    /**
     * Hook method to setup the GroovyScriptEngine to use.<br/>
     * Subclasses may override this method to provide a custom
     * engine.
     */ 
    protected GroovyScriptEngine createGroovyScriptEngine(){
        return new GroovyScriptEngine(this);
    }
}

