blob: f380a2e576f84dd5d3fe4182dc51947752ab988f [file] [log] [blame]
/* Copyright 2004-2005 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.
*/
package org.codehaus.groovy.grails.web.pages;
import groovy.lang.*;
import groovy.text.Template;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.web.metaclass.ControllerDynamicMethods;
import org.codehaus.groovy.grails.web.metaclass.GetParamsDynamicProperty;
import org.codehaus.groovy.grails.web.metaclass.GetSessionDynamicProperty;
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.springframework.context.ApplicationContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
/**
* A GroovyPagesTemplateEngine based on (but not extending) the existing TemplateEngine implementations
* within Groovy. It allows GSP pages to be re-used in different context using code like the below:
*
* <code>
* Template t = new GroovyPagesTemplateEngine()
* .createTemplate(context,request,response);
* t.make()
* .writeTo(out);
* </code>
*
* @author Graeme Rocher
* @since 12-Jan-2006
*/
public class GroovyPagesTemplateEngine {
private static final Log LOG = LogFactory.getLog(GroovyPagesTemplateEngine.class);
private static Map pageCache = Collections.synchronizedMap(new HashMap());
private ClassLoader parent;
private boolean showSource;
private GroovyClassLoader classLoader;
public void setClassLoader(GroovyClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setShowSource(boolean showSource) {
this.showSource = showSource;
}
private static class PageMeta {
private Class servletScriptClass;
private long lastModified;
private Map dependencies = new HashMap();
private InputStream groovySource;
} // PageMeta
public GroovyPagesTemplateEngine() {
}
/**
* Create a template for the current request
*
* @param context
* @param request
* @param response
* @return The created template or null if the page was not found
* @throws IOException
* @throws ServletException
*/
public Template createTemplate(ServletContext context, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
parent = Thread.currentThread().getContextClassLoader();
if (parent == null) parent = getClass().getClassLoader();
String uri = getPageId(request);
return createTemplate(uri,context,request,response);
}
/**
* Creates a template for the specified uri
*
* @param uri
* @param context
* @param request
* @param response
* @return The created template or null if the page was not found for the specified uri
* @throws IOException
* @throws ServletException
*/
public Template createTemplate(String uri, ServletContext context, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
URL pageUrl = getPageUrl(context,uri);
if (pageUrl == null) {
context.log("GroovyPagesServlet: \"" + pageUrl + "\" not found");
return null;
}
boolean spillGroovy = showSource && request.getParameter("showSource") != null;
PageMeta pageMeta = getPage(uri, context,pageUrl, spillGroovy);
return new GroovyPagesTemplate(context,request,response,pageMeta,spillGroovy);
}
/**
* Return the page identifier.
* @param request
* @return The page id
*/
protected String getPageId(HttpServletRequest request) {
// Get the name of the Groovy script (intern the name so that we can
// lock on it)
int contextLength = request.getContextPath().length();
return request.getRequestURI().substring(contextLength).intern();
} // getPageId()
/**
* Return the page URL from the request path.
* @param pageId
* @return
* @throws java.net.MalformedURLException
*/
protected URL getPageUrl(ServletContext context, String pageId) throws MalformedURLException {
// Check to make sure that the file exists in the web application
if(LOG.isDebugEnabled()) {
LOG.debug("Loading GSP for url ["+pageId+"]");
}
return context.getResource(pageId);
} // getPageUrl()
/**
* Lookup page class or load new one if needed.
* @param uri
* @param pageUrl
* @param spillGroovy
* @return
* @throws IOException
* @throws javax.servlet.ServletException
*/
protected PageMeta getPage(String uri, ServletContext context,URL pageUrl, boolean spillGroovy)
throws IOException, ServletException {
// Lock on the uri to ensure that only one compile occurs for any script
synchronized (uri) {
// Get the URLConnection
URLConnection groovyScriptConn = pageUrl.openConnection();
// URL last modified
long lastModified = groovyScriptConn.getLastModified();
// Check the cache for the script
PageMeta pageMeta = (PageMeta)pageCache.get(uri);
// If the pageMeta isn't null check all the dependencies
boolean dependencyOutOfDate = false;
if (pageMeta != null && !spillGroovy) {
isPageNew(pageMeta);
}
if (pageMeta == null || pageMeta.lastModified < lastModified || dependencyOutOfDate || spillGroovy) {
pageMeta = newPage(uri, context,groovyScriptConn, lastModified, spillGroovy);
}
return pageMeta;
}
} // getPage()
/**
* Load and compile new page.
* @param uri
* @param groovyScriptConn
* @param lastModified
* @param spillGroovy
* @return
* @throws IOException
* @throws ServletException
*/
private PageMeta newPage(String uri, ServletContext context,URLConnection groovyScriptConn, long lastModified,
boolean spillGroovy) throws IOException, ServletException {
Parse parse = new Parse(uri, groovyScriptConn.getInputStream());
InputStream in = parse.parse();
// Make a new pageMeta
PageMeta pageMeta = new PageMeta();
// just return groovy and don't compile if asked
if (spillGroovy) {
pageMeta.groovySource = in;
return pageMeta;
}
// Compile the script into an object
Loader loader = new Loader(classLoader, context, uri, pageMeta.dependencies);
Class scriptClass;
try {
scriptClass =
loader.parseClass(in, uri.substring(1));
} catch (CompilationFailedException e) {
throw new ServletException("Could not parse script: " + uri, e);
}
pageMeta.servletScriptClass = scriptClass;
pageMeta.lastModified = lastModified;
pageCache.put(uri, pageMeta);
return pageMeta;
} // newPage()
/**
* Is page new or changed?
* @param pageMeta page data
* @return true if compile needed
*/
private boolean isPageNew(PageMeta pageMeta) {
for (Iterator i = pageMeta.dependencies.keySet().iterator(); i.hasNext(); ) {
URLConnection urlc;
URL url = (URL)i.next();
try {
urlc = url.openConnection();
urlc.setDoInput(false);
urlc.setDoOutput(false);
long dependentLastModified = urlc.getLastModified();
if (dependentLastModified > ((Long)pageMeta.dependencies.get(url)).longValue()) {
return true;
}
} catch (IOException ioe) {
return true;
}
}
return false;
} // isPageNew()
/**
*
* @author Graeme Rocher
* @since 12-Jan-2006
*/
protected static class GroovyPagesTemplate implements Template {
private HttpServletResponse response;
private HttpServletRequest request;
private ServletContext context;
private boolean showSource = false;
private PageMeta pageMeta;
public GroovyPagesTemplate(ServletContext context,
HttpServletRequest request,
HttpServletResponse response,
PageMeta pageMeta,
boolean showSource) {
this.request = request;
this.response = response;
this.context = context;
this.showSource = showSource;
this.pageMeta = pageMeta;
}
public Writable make() {
return new GroovyPageTemplateWritable(context,request,response,pageMeta,showSource);
}
public Writable make(Map binding) {
GroovyPageTemplateWritable gptw = new GroovyPageTemplateWritable(context,request,response,pageMeta,showSource);
gptw.setBinding(binding);
return gptw;
}
}
/**
*
* @author Graeme Rocher
* @since 12-Jan-2006
*/
protected static class GroovyPageTemplateWritable implements Writable {
private HttpServletResponse response;
private HttpServletRequest request;
private PageMeta pageMeta;
private boolean showSource;
private ServletContext context;
private Map additionalBinding = new HashMap();
public GroovyPageTemplateWritable(ServletContext context,
HttpServletRequest request,
HttpServletResponse response,
PageMeta pageMeta,
boolean showSource) {
this.request = request;
this.response = response;
this.pageMeta = pageMeta;
this.showSource = showSource;
this.context = context;
}
public void setBinding(Map binding) {
if(binding != null)
this.additionalBinding = binding;
}
public Writer writeTo(Writer out) throws IOException {
if (showSource) {
// Set it to TEXT
response.setContentType("text/plain"); // must come before response.getOutputStream()
send(pageMeta.groovySource, out);
pageMeta.groovySource = null;
} else {
// Set it to HTML by default
response.setContentType("text/html"); // must come before response.getWriter()
Binding binding = getBinding(request, response, out);
Script page = InvokerHelper.createScript(pageMeta.servletScriptClass, binding);
page.run();
}
return out;
}
/**
* Copy all of input to output.
* @param in
* @param out
* @throws IOException
*/
public static void send(InputStream in, Writer out) throws IOException {
try {
Reader reader = new InputStreamReader(in);
char[] buf = new char[8192];
for (;;) {
int read = reader.read(buf);
if (read <= 0) break;
out.write(buf, 0, read);
}
} finally {
out.close();
in.close();
}
} // send()
/**
* Prepare Bindings before instantiating page.
* @param request
* @param response
* @param out
* @return the Bindings
* @throws IOException
*/
protected Binding getBinding(HttpServletRequest request, HttpServletResponse response, Writer out)
throws IOException {
// Set up the script context
Binding binding = new Binding();
GroovyObject controller = (GroovyObject)request.getAttribute(GrailsApplicationAttributes.CONTROLLER);
binding.setVariable(GroovyPage.REQUEST, controller.getProperty(ControllerDynamicMethods.REQUEST_PROPERTY));
binding.setVariable(GroovyPage.RESPONSE, controller.getProperty(ControllerDynamicMethods.RESPONSE_PROPERTY));
binding.setVariable(GroovyPage.FLASH, controller.getProperty(ControllerDynamicMethods.FLASH_SCOPE_PROPERTY));
binding.setVariable(GroovyPage.SERVLET_CONTEXT, context);
ApplicationContext appContext = (ApplicationContext)context.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT);
binding.setVariable(GroovyPage.APPLICATION_CONTEXT, appContext);
binding.setVariable(GrailsApplication.APPLICATION_ID, appContext.getBean(GrailsApplication.APPLICATION_ID));
binding.setVariable(GrailsApplicationAttributes.CONTROLLER, controller);
binding.setVariable(GroovyPage.SESSION, controller.getProperty(GetSessionDynamicProperty.PROPERTY_NAME));
binding.setVariable(GroovyPage.PARAMS, controller.getProperty(GetParamsDynamicProperty.PROPERTY_NAME));
binding.setVariable(GroovyPage.OUT, out);
// Go through request attributes and add them to the binding as the model
for (Enumeration attributeEnum = request.getAttributeNames(); attributeEnum.hasMoreElements();) {
String key = (String) attributeEnum.nextElement();
try {
binding.getVariable(key);
}
catch(MissingPropertyException mpe) {
binding.setVariable( key, request.getAttribute(key) );
}
}
for (Iterator i = additionalBinding.keySet().iterator(); i.hasNext();) {
String key = (String)i.next();
binding.setVariable(key, additionalBinding.get(key));
}
return binding;
} // getBinding()
}
}