View Javadoc

1   /*
2    * $Id: FreemarkerTemplateEngine.java 719643 2008-11-21 17:13:32Z musachy $
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.*;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.CopyOnWriteArraySet;
29  
30  import javax.servlet.ServletContext;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.apache.struts2.ServletActionContext;
35  import org.apache.struts2.StrutsConstants;
36  import org.apache.struts2.views.freemarker.FreemarkerManager;
37  
38  import com.opensymphony.xwork2.ActionContext;
39  import com.opensymphony.xwork2.ActionInvocation;
40  import com.opensymphony.xwork2.inject.Inject;
41  import com.opensymphony.xwork2.util.ClassLoaderUtil;
42  import com.opensymphony.xwork2.util.ValueStack;
43  import com.opensymphony.xwork2.util.logging.Logger;
44  import com.opensymphony.xwork2.util.logging.LoggerFactory;
45  
46  import freemarker.template.Configuration;
47  import freemarker.template.SimpleHash;
48  import freemarker.core.ParseException;
49  
50  /***
51   * Freemarker based template engine.
52   */
53  public class FreemarkerTemplateEngine extends BaseTemplateEngine {
54      static Class bodyContent = null;
55      private FreemarkerManager freemarkerManager;
56  
57      private final Map<String, freemarker.template.Template> templates = new ConcurrentHashMap<String, freemarker.template.Template>();
58      private final Set<String> missingTemplates = new CopyOnWriteArraySet<String>();
59      private boolean freemarkerCaching = false;
60  
61      static {
62          try {
63              bodyContent = ClassLoaderUtil.loadClass("javax.servlet.jsp.tagext.BodyContent",
64                      FreemarkerTemplateEngine.class);
65          } catch (ClassNotFoundException e) {
66              // this is OK -- this just means JSP isn't even being used here, which is perfectly fine.
67              // we need this class in environments that use JSP to know when to wrap the writer
68              // and ignore flush() calls. In JSP, it is illegal for a BodyContent writer to be flushed(),
69              // so we have to take caution here.
70          }
71      }
72  
73      private static final Logger LOG = LoggerFactory.getLogger(FreemarkerTemplateEngine.class);
74  
75      @Inject
76      public void setFreemarkerManager(FreemarkerManager mgr) {
77          this.freemarkerManager = mgr;
78      }
79      
80      public void renderTemplate(TemplateRenderingContext templateContext) throws Exception {
81      	// get the various items required from the stack
82          ValueStack stack = templateContext.getStack();
83          Map context = stack.getContext();
84          ServletContext servletContext = (ServletContext) context.get(ServletActionContext.SERVLET_CONTEXT);
85          HttpServletRequest req = (HttpServletRequest) context.get(ServletActionContext.HTTP_REQUEST);
86          HttpServletResponse res = (HttpServletResponse) context.get(ServletActionContext.HTTP_RESPONSE);
87  
88          // prepare freemarker
89          Configuration config = freemarkerManager.getConfiguration(servletContext);
90  
91          // get the list of templates we can use
92          List<Template> templates = templateContext.getTemplate().getPossibleTemplates(this);
93  
94          // find the right template
95          freemarker.template.Template template = null;
96          String templateName = null;
97          Exception exception = null;
98          for (Template t : templates) {
99              templateName = getFinalTemplateName(t);
100             if (freemarkerCaching) {
101                 if (!isTemplateMissing(templateName)) {
102                     try {
103                         template = findInCache(templateName);  // look in cache first
104                         if (template == null) {
105                             // try to load, and if it works, stop at the first one
106                             template = config.getTemplate(templateName);
107                             addToCache(templateName, template);
108                         }
109                         break;
110                     } catch (IOException e) {
111                         addToMissingTemplateCache(templateName);
112                         if (exception == null) {
113                             exception = e;
114                         }
115                     }
116                 }
117             } else {
118                 try {
119                     // try to load, and if it works, stop at the first one
120                     template = config.getTemplate(templateName);
121                     break;
122                 } catch (ParseException e) {
123                     // template was found but was invalid - always report this.
124                     exception = e;
125                     break;
126                 } catch (IOException e) {
127                     // FileNotFoundException is anticipated - report the first IOException if no template found
128                     if (exception == null) {
129                         exception = e;
130                     }
131                 }
132             }
133         }
134 
135         if (template == null) {
136             if (LOG.isErrorEnabled()) {
137                 LOG.error("Could not load the FreeMarker template named '" + templateContext.getTemplate().getName() +"':");
138                 for (Template t : templates) {
139                     LOG.error("Attempted: " + getFinalTemplateName(t));
140                 }
141                 LOG.error("The TemplateLoader provided by the FreeMarker Configuration was a: "+config.getTemplateLoader().getClass().getName());
142             }
143             if (exception != null) {
144                 throw exception;
145             } else {
146                 return;
147             }
148         }
149 
150         if (LOG.isDebugEnabled()) {
151             LOG.debug("Rendering template " + templateName);
152         }
153 
154         ActionInvocation ai = ActionContext.getContext().getActionInvocation();
155 
156         Object action = (ai == null) ? null : ai.getAction();
157         SimpleHash model = freemarkerManager.buildTemplateModel(stack, action, servletContext, req, res, config.getObjectWrapper());
158 
159         model.put("tag", templateContext.getTag());
160         model.put("themeProperties", getThemeProps(templateContext.getTemplate()));
161 
162         // the BodyContent JSP writer doesn't like it when FM flushes automatically --
163         // so let's just not do it (it will be flushed eventually anyway)
164         Writer writer = templateContext.getWriter();
165         final Writer wrapped = writer;
166         writer = new Writer() {
167             public void write(char cbuf[], int off, int len) 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         try {
181             stack.push(templateContext.getTag());
182             template.process(model, writer);
183         } finally {
184             stack.pop();
185         }
186     }
187 
188     protected String getSuffix() {
189         return "ftl";
190     }
191 
192     protected void addToMissingTemplateCache(String templateName) {
193         missingTemplates.add(templateName);
194     }
195     
196     protected boolean isTemplateMissing(String templateName) {
197         return missingTemplates.contains(templateName);
198     }
199 
200     protected void addToCache(String templateName,
201         freemarker.template.Template template) {
202         templates.put(templateName, template);
203     }
204     
205     protected freemarker.template.Template findInCache(String templateName) {
206         return templates.get(templateName);
207     }
208     
209     /***
210      * Enables or disables Struts caching of Freemarker templates. By default disabled.
211      * Set struts.freemarker.templatesCache=true to enable cache
212      * @param cacheTemplates "true" if the template engine should cache freemarker template
213      * internally
214      */
215     @Inject(StrutsConstants.STRUTS_FREEMARKER_TEMPLATES_CACHE)
216     public void setCacheTemplates(String cacheTemplates) {
217         freemarkerCaching = "true".equals(cacheTemplates);
218     }
219 }