/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.sitemesh;
import com.opensymphony.module.sitemesh.HTMLPage;
import com.opensymphony.module.sitemesh.RequestConstants;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import freemarker.core.InvalidReferenceException;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.StrutsRequestWrapper;
import org.apache.struts2.dispatcher.ng.listener.StrutsListener;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.freemarker.ScopesHashModel;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Locale;
/**
* <p>This is a SiteMesh FreeMarker view servlet.</p>
* <p/>
* <p>It overrides the SiteMesh servlet to rely on the
* Freemarker Manager in Struts instead of creating it's
* own manager</p>
*/
public class FreemarkerDecoratorServlet extends freemarker.ext.servlet.FreemarkerServlet {
private static final com.opensymphony.xwork2.util.logging.Logger LOG = LoggerFactory.getLogger(FreemarkerDecoratorServlet.class);
protected FreemarkerManager freemarkerManager;
public static final long serialVersionUID = -2440216393145762479L;
/*
private static final String EXPIRATION_DATE;
static {
// Generate expiration date that is one year from now in the past
GregorianCalendar expiration = new GregorianCalendar();
expiration.roll(Calendar.YEAR, -1);
SimpleDateFormat httpDate = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", java.util.Locale.US);
EXPIRATION_DATE = httpDate.format(expiration.getTime());
}
*/
protected String templatePath;
protected boolean nocache;
protected boolean debug;
protected Configuration config;
private ObjectWrapper wrapper;
private String contentType;
private boolean noCharsetInContentType;
public void init() throws ServletException {
try {
Dispatcher dispatcher = (Dispatcher) getServletContext().getAttribute(StrutsStatics.SERVLET_DISPATCHER);
if (dispatcher == null)
throw new IllegalStateException("Unable to find the Dispatcher in the Servlet Context. Is '" + StrutsListener.class.getName() + "' missing in web.xml?");
freemarkerManager = dispatcher.getContainer().getInstance(FreemarkerManager.class);
config = createConfiguration();
// Process object_wrapper init-param out of order:
wrapper = config.getObjectWrapper();
if (LOG.isDebugEnabled()) {
LOG.debug("Using object wrapper of class " + wrapper.getClass().getName());
}
// Process all other init-params:
Enumeration initpnames = getServletConfig().getInitParameterNames();
while (initpnames.hasMoreElements()) {
String name = (String) initpnames.nextElement();
String value = getInitParameter(name);
if (name == null) {
throw new ServletException("init-param without param-name. " + "Maybe the web.xml is not well-formed?");
}
if (value == null) {
throw new ServletException("init-param without param-value. " + "Maybe the web.xml is not well-formed?");
}
// template path is already handled!
if (!FreemarkerManager.INITPARAM_TEMPLATE_PATH.equals(name)) freemarkerManager.addSetting(name, value);
}
nocache = freemarkerManager.getNocache();
debug = freemarkerManager.getDebug();
contentType = freemarkerManager.getContentType();
noCharsetInContentType = freemarkerManager.getNoCharsetInContentType();
} catch (ServletException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
process(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
process(request, response);
}
private void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Give chance to subclasses to perform preprocessing
if (preprocessRequest(request, response)) {
return;
}
String path = requestUrlToTemplatePath(request);
if (debug) {
log("Requested template: " + path);
}
Template template = null;
try {
template = config.getTemplate(path, deduceLocale(path, request, response));
} catch (FileNotFoundException e) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
Object attrContentType = template.getCustomAttribute("content_type");
if (attrContentType != null) {
response.setContentType(attrContentType.toString());
} else {
if (noCharsetInContentType) {
response.setContentType(contentType + "; charset=" + template.getEncoding());
} else {
response.setContentType(contentType);
}
}
// Set cache policy
setBrowserCachingPolicy(response);
ServletContext servletContext = getServletContext();
ScopesHashModel model = (ScopesHashModel) request.getAttribute(freemarkerManager.ATTR_TEMPLATE_MODEL);
try {
if (model == null) {
ActionContext ctx = ServletActionContext.getActionContext(request);
model = freemarkerManager.buildTemplateModel(ctx.getValueStack(), ctx.getActionInvocation().getAction(), servletContext, request, response, wrapper);
}
// Give subclasses a chance to hook into preprocessing
if (preTemplateProcess(request, response, template, model)) {
try {
// Process the template
template.process(model, response.getWriter());
} finally {
// Give subclasses a chance to hook into postprocessing
postTemplateProcess(request, response, template, model);
}
}
} catch (InvalidReferenceException x) {
// this exception is thrown if there is an error processing a reference. We want to report these!
HttpServletRequest req = ((StrutsRequestWrapper) ActionContext.getContext().get("com.opensymphony.xwork2.dispatcher.HttpServletRequest"));
String resultCode = ActionContext.getContext().getActionInvocation().getResultCode();
if (req == null) req = request;
StringBuilder msgBuf = new StringBuilder("Error applying freemarker template to\n request: ");
msgBuf.append(req.getRequestURL());
if (req.getQueryString() != null) msgBuf.append("?").append(req.getQueryString());
msgBuf.append(" with resultCode: ").append(resultCode).append(".\n\n").append(x.getMessage());
String msg = msgBuf.toString();
LOG.error(msg, x);
ServletException e = new ServletException(msg, x);
// Attempt to set init cause, but don't freak out if the method
// is not available (i.e. pre-1.4 JRE). This is required as the
// constructor-passed throwable won't show up automatically in
// stack traces.
try {
e.getClass().getMethod("initCause", new Class[]{Throwable.class}).invoke(e, new Object[]{x});
} catch (Exception ex) {
// Can't set init cause, we're probably running on a pre-1.4
// JDK, oh well...
}
throw e;
} catch (TemplateException te) {
if (config.getTemplateExceptionHandler().getClass().getName().indexOf("Debug") != -1) {
this.log("Error executing FreeMarker template", te);
} else {
ServletException e = new ServletException("Error executing FreeMarker template", te);
// Attempt to set init cause, but don't freak out if the method
// is not available (i.e. pre-1.4 JRE). This is required as the
// constructor-passed throwable won't show up automatically in
// stack traces.
try {
e.getClass().getMethod("initCause", new Class[]{Throwable.class}).invoke(e, new Object[]{te});
} catch (Exception ex) {
// Can't set init cause, we're probably running on a pre-1.4
// JDK, oh well...
}
throw e;
}
}
}
/**
* Returns the locale used for the
* {@link Configuration#getTemplate(String, Locale)} call.
* The base implementation simply returns the locale setting of the
* configuration. Override this method to provide different behaviour, i.e.
* to use the locale indicated in the request.
*/
protected Locale deduceLocale(String templatePath, HttpServletRequest request, HttpServletResponse response) {
return config.getLocale();
}
/**
* Create the instance of the freemarker Configuration object.
* <p/>
* this implementation
* <ul>
* <li>obtains the default configuration from Configuration.getDefaultConfiguration()
* <li>sets up template loading from a ClassTemplateLoader and a WebappTemplateLoader
* <li>sets up the object wrapper to be the BeansWrapper
* <li>loads settings from the classpath file /freemarker.properties
* </ul>
*/
protected freemarker.template.Configuration createConfiguration() {
return freemarkerManager.getConfiguration(this.getServletContext());
}
/**
* Called before the execution is passed to template.process().
* This is a generic hook you might use in subclasses to perform a specific
* action before the template is processed. By default does nothing.
* A typical action to perform here is to inject application-specific
* objects into the model root
* <p/>
* <p>Example: Expose the Serlvet context path as "baseDir" for all templates:
* <p/>
* <pre>
* ((SimpleHash) data).put("baseDir", request.getContextPath() + "/");
* return true;
* </pre>
*
* @param request the actual HTTP request
* @param response the actual HTTP response
* @param template the template that will get executed
* @return true to process the template, false to suppress template processing.
* @see freemarker.ext.servlet.FreemarkerServlet#preTemplateProcess(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, freemarker.template.Template, freemarker.template.TemplateModel)
*/
protected boolean preTemplateProcess(HttpServletRequest request, HttpServletResponse response, Template template, TemplateModel templateModel) throws ServletException, IOException {
boolean result = super.preTemplateProcess(request, response, template, templateModel);
SimpleHash hash = (SimpleHash) templateModel;
HTMLPage htmlPage = (HTMLPage) request.getAttribute(RequestConstants.PAGE);
String title, body, head;
if (htmlPage == null) {
title = "No Title";
body = "No Body";
head = "<!-- No head -->";
} else {
title = htmlPage.getTitle();
StringWriter buffer = new StringWriter();
htmlPage.writeBody(buffer);
body = buffer.toString();
buffer = new StringWriter();
htmlPage.writeHead(buffer);
head = buffer.toString();
hash.put("page", htmlPage);
}
hash.put("title", title);
hash.put("body", body);
hash.put("head", head);
hash.put("base", request.getContextPath());
/*
Factory factory = Factory.getInstance(new Config(getServletConfig()));
Decorator decorator = factory.getDecoratorMapper().getDecorator(request, htmlPage);
-> decorator.getPage()
*/
return result;
}
/**
* If the parameter "nocache" was set to true, generate a set of headers
* that will advise the HTTP client not to cache the returned page.
*/
private void setBrowserCachingPolicy(HttpServletResponse res) {
if (nocache) {
// HTTP/1.1 + IE extensions
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, " + "post-check=0, pre-check=0");
// HTTP/1.0
res.setHeader("Pragma", "no-cache");
// Last resort for those that ignore all of the above
res.setHeader("Expires", freemarkerManager.EXPIRATION_DATE);
}
}
}