View Javadoc

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