View Javadoc

1   /*
2    * $Id: FreemarkerTemplateEngine.java 651946 2008-04-27 13:41:38Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.components.template;
23  
24  import java.io.IOException;
25  import java.io.Writer;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Map;
30  
31  import javax.servlet.ServletContext;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  
35  import org.apache.struts2.ServletActionContext;
36  import org.apache.struts2.StrutsConstants;
37  import org.apache.struts2.views.freemarker.FreemarkerManager;
38  
39  import com.opensymphony.xwork2.ActionContext;
40  import com.opensymphony.xwork2.ActionInvocation;
41  import com.opensymphony.xwork2.inject.Inject;
42  import com.opensymphony.xwork2.util.ClassLoaderUtil;
43  import com.opensymphony.xwork2.util.ValueStack;
44  import com.opensymphony.xwork2.util.logging.Logger;
45  import com.opensymphony.xwork2.util.logging.LoggerFactory;
46  
47  import freemarker.template.Configuration;
48  import freemarker.template.SimpleHash;
49  import freemarker.core.ParseException;
50  
51  /***
52   * Freemarker based template engine.
53   */
54  public class FreemarkerTemplateEngine extends BaseTemplateEngine {
55      static Class bodyContent = null;
56      private FreemarkerManager freemarkerManager;
57  
58      private final HashMap<String, freemarker.template.Template> templates = new HashMap<String, freemarker.template.Template>();
59      private final HashSet<String> missingTemplates = new HashSet<String>();
60      private boolean freemarkerCaching = false;
61  
62      static {
63          try {
64              bodyContent = ClassLoaderUtil.loadClass("javax.servlet.jsp.tagext.BodyContent",
65                      FreemarkerTemplateEngine.class);
66          } catch (ClassNotFoundException e) {
67              // this is OK -- this just means JSP isn't even being used here, which is perfectly fine.
68              // we need this class in environments that use JSP to know when to wrap the writer
69              // and ignore flush() calls. In JSP, it is illegal for a BodyContent writer to be flushed(),
70              // so we have to take caution here.
71          }
72      }
73  
74      private static final Logger LOG = LoggerFactory.getLogger(FreemarkerTemplateEngine.class);
75  
76      @Inject
77      public void setFreemarkerManager(FreemarkerManager mgr) {
78          this.freemarkerManager = mgr;
79      }
80      
81      public void renderTemplate(TemplateRenderingContext templateContext) throws Exception {
82      	// get the various items required from the stack
83          ValueStack stack = templateContext.getStack();
84          Map context = stack.getContext();
85          ServletContext servletContext = (ServletContext) context.get(ServletActionContext.SERVLET_CONTEXT);
86          HttpServletRequest req = (HttpServletRequest) context.get(ServletActionContext.HTTP_REQUEST);
87          HttpServletResponse res = (HttpServletResponse) context.get(ServletActionContext.HTTP_RESPONSE);
88  
89          // prepare freemarker
90          Configuration config = freemarkerManager.getConfiguration(servletContext);
91  
92          // get the list of templates we can use
93          List<Template> templates = templateContext.getTemplate().getPossibleTemplates(this);
94  
95          // find the right template
96          freemarker.template.Template template = null;
97          String templateName = null;
98          Exception exception = null;
99          for (Template t : templates) {
100             templateName = getFinalTemplateName(t);
101             if (freemarkerCaching) {
102                 if (!isTemplateMissing(templateName)) {
103                     try {
104                         template = findInCache(templateName);  // look in cache first
105                         if (template == null) {
106                             // try to load, and if it works, stop at the first one
107                             template = config.getTemplate(templateName);
108                             addToCache(templateName, template);
109                         }
110                         break;
111                     } catch (IOException e) {
112                         addToMissingTemplateCache(templateName);
113                         if (exception == null) {
114                             exception = e;
115                         }
116                     }
117                 }
118             } else {
119                 try {
120                     // try to load, and if it works, stop at the first one
121                     template = config.getTemplate(templateName);
122                     break;
123                 } catch (ParseException e) {
124                     // template was found but was invalid - always report this.
125                     exception = e;
126                     break;
127                 } catch (IOException e) {
128                     // FileNotFoundException is anticipated - report the first IOException if no template found
129                     if (exception == null) {
130                         exception = e;
131                     }
132                 }
133             }
134         }
135 
136         if (template == null) {
137             if (LOG.isErrorEnabled()) {
138                 LOG.error("Could not load the FreeMarker template named '" + templateContext.getTemplate().getName() +"':");
139                 for (Template t : templates) {
140                     LOG.error("Attempted: " + getFinalTemplateName(t));
141                 }
142                 LOG.error("The TemplateLoader provided by the FreeMarker Configuration was a: "+config.getTemplateLoader().getClass().getName());
143             }
144             if (exception != null) {
145                 throw exception;
146             } else {
147                 return;
148             }
149         }
150 
151         if (LOG.isDebugEnabled()) {
152             LOG.debug("Rendering template " + templateName);
153         }
154 
155         ActionInvocation ai = ActionContext.getContext().getActionInvocation();
156 
157         Object action = (ai == null) ? null : ai.getAction();
158         SimpleHash model = freemarkerManager.buildTemplateModel(stack, action, servletContext, req, res, config.getObjectWrapper());
159 
160         model.put("tag", templateContext.getTag());
161         model.put("themeProperties", getThemeProps(templateContext.getTemplate()));
162 
163         // the BodyContent JSP writer doesn't like it when FM flushes automatically --
164         // so let's just not do it (it will be flushed eventually anyway)
165         Writer writer = templateContext.getWriter();
166         if (bodyContent != null && bodyContent.isAssignableFrom(writer.getClass())) {
167             final Writer wrapped = writer;
168             writer = new Writer() {
169                 public void write(char cbuf[], int off, int len) throws IOException {
170                     wrapped.write(cbuf, off, len);
171                 }
172 
173                 public void flush() throws IOException {
174                     // nothing!
175                 }
176 
177                 public void close() throws IOException {
178                     wrapped.close();
179                 }
180             };
181         }
182 
183         try {
184             stack.push(templateContext.getTag());
185             template.process(model, writer);
186         } finally {
187             stack.pop();
188         }
189     }
190 
191     protected String getSuffix() {
192         return "ftl";
193     }
194 
195     protected void addToMissingTemplateCache(String templateName) {
196         synchronized(missingTemplates) {
197             missingTemplates.add(templateName);
198         }
199     }
200     
201     protected boolean isTemplateMissing(String templateName) {
202         synchronized(missingTemplates) {
203             return missingTemplates.contains(templateName);
204         }
205     }
206 
207     protected void addToCache(String templateName,
208         freemarker.template.Template template) {
209         synchronized(templates) {
210             templates.put(templateName, template);
211         }
212     }
213     
214     protected freemarker.template.Template findInCache(String templateName) {
215         synchronized(templates) {
216             return templates.get(templateName);
217         }
218     }
219     
220     /***
221      * Enables or disables Struts caching of Freemarker templates. By default disabled.
222      * Set struts.freemarker.templatesCache=true to enable cache
223      * @param cacheTemplates "true" if the template engine should cache freemarker template
224      * internally
225      */
226     @Inject(StrutsConstants.STRUTS_FREEMARKER_TEMPLATES_CACHE)
227     public void setCacheTemplates(String cacheTemplates) {
228         freemarkerCaching = "true".equals(cacheTemplates);
229     }
230 }