001: /*
002: * $Id: FreemarkerTemplateEngine.java 560894 2007-07-30 09:05:32Z rgielen $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.components.template;
022:
023: import java.io.IOException;
024: import java.io.Writer;
025: import java.util.*;
026:
027: import javax.servlet.ServletContext;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.apache.struts2.ServletActionContext;
034: import org.apache.struts2.StrutsConstants;
035: import org.apache.struts2.views.freemarker.FreemarkerManager;
036:
037: import com.opensymphony.xwork2.inject.Inject;
038: import com.opensymphony.xwork2.util.ClassLoaderUtil;
039: import com.opensymphony.xwork2.ActionContext;
040: import com.opensymphony.xwork2.ActionInvocation;
041: import com.opensymphony.xwork2.util.ValueStack;
042:
043: import freemarker.template.*;
044:
045: /**
046: * Freemarker based template engine.
047: */
048: public class FreemarkerTemplateEngine extends BaseTemplateEngine {
049: static Class bodyContent = null;
050: private FreemarkerManager freemarkerManager;
051:
052: private final HashMap<String, freemarker.template.Template> templates = new HashMap<String, freemarker.template.Template>();
053: private final HashSet<String> missingTemplates = new HashSet<String>();
054: private boolean freemarkerCaching = false;
055:
056: static {
057: try {
058: bodyContent = ClassLoaderUtil.loadClass(
059: "javax.servlet.jsp.tagext.BodyContent",
060: FreemarkerTemplateEngine.class);
061: } catch (ClassNotFoundException e) {
062: // this is OK -- this just means JSP isn't even being used here, which is perfectly fine.
063: // we need this class in environments that use JSP to know when to wrap the writer
064: // and ignore flush() calls. In JSP, it is illegal for a BodyContent writer to be flushed(),
065: // so we have to take caution here.
066: }
067: }
068:
069: private static final Log LOG = LogFactory
070: .getLog(FreemarkerTemplateEngine.class);
071:
072: @Inject
073: public void setFreemarkerManager(FreemarkerManager mgr) {
074: this .freemarkerManager = mgr;
075: }
076:
077: public void renderTemplate(TemplateRenderingContext templateContext)
078: throws Exception {
079: // get the various items required from the stack
080: ValueStack stack = templateContext.getStack();
081: Map context = stack.getContext();
082: ServletContext servletContext = (ServletContext) context
083: .get(ServletActionContext.SERVLET_CONTEXT);
084: HttpServletRequest req = (HttpServletRequest) context
085: .get(ServletActionContext.HTTP_REQUEST);
086: HttpServletResponse res = (HttpServletResponse) context
087: .get(ServletActionContext.HTTP_RESPONSE);
088:
089: // prepare freemarker
090: Configuration config = freemarkerManager
091: .getConfiguration(servletContext);
092:
093: // get the list of templates we can use
094: List<Template> templates = templateContext.getTemplate()
095: .getPossibleTemplates(this );
096:
097: // find the right template
098: freemarker.template.Template template = null;
099: String templateName = null;
100: Exception exception = null;
101: for (Template t : templates) {
102: templateName = getFinalTemplateName(t);
103: if (freemarkerCaching) {
104: if (!isTemplateMissing(templateName)) {
105: try {
106: template = findInCache(templateName); // look in cache first
107: if (template == null) {
108: // try to load, and if it works, stop at the first one
109: template = config.getTemplate(templateName);
110: addToCache(templateName, template);
111: }
112: break;
113: } catch (IOException e) {
114: addToMissingTemplateCache(templateName);
115: if (exception == null) {
116: exception = e;
117: }
118: }
119: }
120: } else {
121: try {
122: // try to load, and if it works, stop at the first one
123: template = config.getTemplate(templateName);
124: break;
125: } catch (IOException e) {
126: if (exception == null) {
127: exception = e;
128: }
129: }
130: }
131: }
132:
133: if (template == null) {
134: LOG.error("Could not load template "
135: + templateContext.getTemplate());
136: if (exception != null) {
137: throw exception;
138: } else {
139: return;
140: }
141: }
142:
143: if (LOG.isDebugEnabled()) {
144: LOG.debug("Rendering template " + templateName);
145: }
146:
147: ActionInvocation ai = ActionContext.getContext()
148: .getActionInvocation();
149:
150: Object action = (ai == null) ? null : ai.getAction();
151: SimpleHash model = freemarkerManager.buildTemplateModel(stack,
152: action, servletContext, req, res, config
153: .getObjectWrapper());
154:
155: model.put("tag", templateContext.getTag());
156: model.put("themeProperties", getThemeProps(templateContext
157: .getTemplate()));
158:
159: // the BodyContent JSP writer doesn't like it when FM flushes automatically --
160: // so let's just not do it (it will be flushed eventually anyway)
161: Writer writer = templateContext.getWriter();
162: if (bodyContent != null
163: && bodyContent.isAssignableFrom(writer.getClass())) {
164: final Writer wrapped = writer;
165: writer = new Writer() {
166: public void write(char cbuf[], int off, int len)
167: throws IOException {
168: wrapped.write(cbuf, off, len);
169: }
170:
171: public void flush() throws IOException {
172: // nothing!
173: }
174:
175: public void close() throws IOException {
176: wrapped.close();
177: }
178: };
179: }
180:
181: try {
182: stack.push(templateContext.getTag());
183: template.process(model, writer);
184: } finally {
185: stack.pop();
186: }
187: }
188:
189: protected String getSuffix() {
190: return "ftl";
191: }
192:
193: protected void addToMissingTemplateCache(String templateName) {
194: synchronized (missingTemplates) {
195: missingTemplates.add(templateName);
196: }
197: }
198:
199: protected boolean isTemplateMissing(String templateName) {
200: synchronized (missingTemplates) {
201: return missingTemplates.contains(templateName);
202: }
203: }
204:
205: protected void addToCache(String templateName,
206: freemarker.template.Template template) {
207: synchronized (templates) {
208: templates.put(templateName, template);
209: }
210: }
211:
212: protected freemarker.template.Template findInCache(
213: String templateName) {
214: synchronized (templates) {
215: return templates.get(templateName);
216: }
217: }
218:
219: /**
220: * Enables or disables Struts caching of Freemarker templates. By default disabled.
221: * Set struts.freemarker.templatesCache=true to enable cache
222: * @param cacheTemplates "true" if the template engine should cache freemarker template
223: * internally
224: */
225: @Inject(StrutsConstants.STRUTS_FREEMARKER_TEMPLATES_CACHE)
226: public void setCacheTemplates(String cacheTemplates) {
227: freemarkerCaching = "true".equals(cacheTemplates);
228: }
229: }
|