View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.freemarker;
17  
18  import java.io.ByteArrayOutputStream;
19  import java.io.IOException;
20  import java.io.OutputStreamWriter;
21  import java.io.PrintWriter;
22  import java.io.StringReader;
23  import java.io.UnsupportedEncodingException;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.servlet.ServletContext;
30  import javax.servlet.ServletOutputStream;
31  import javax.servlet.ServletResponseWrapper;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  import javax.servlet.http.HttpServletResponseWrapper;
35  
36  import org.kuali.rice.core.api.CoreApiServiceLocator;
37  import org.kuali.rice.krad.uif.UifConstants;
38  import org.kuali.rice.krad.uif.UifParameters;
39  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
40  import org.kuali.rice.krad.uif.util.ProcessLogger;
41  import org.kuali.rice.krad.uif.util.UifRenderHelperMethods;
42  import org.kuali.rice.krad.util.GlobalVariables;
43  import org.kuali.rice.krad.util.KRADConstants;
44  import org.springframework.web.servlet.support.RequestContext;
45  import org.springframework.web.servlet.view.AbstractTemplateView;
46  
47  import freemarker.cache.TemplateCache;
48  import freemarker.core.Environment;
49  import freemarker.core.ParseException;
50  import freemarker.ext.jsp.TaglibFactory;
51  import freemarker.ext.servlet.AllHttpScopesHashModel;
52  import freemarker.ext.servlet.FreemarkerServlet;
53  import freemarker.ext.servlet.HttpRequestHashModel;
54  import freemarker.ext.servlet.HttpRequestParametersHashModel;
55  import freemarker.ext.servlet.HttpSessionHashModel;
56  import freemarker.ext.servlet.ServletContextHashModel;
57  import freemarker.template.Configuration;
58  import freemarker.template.ObjectWrapper;
59  import freemarker.template.Template;
60  import freemarker.template.TemplateException;
61  
62  /**
63   * Encapsulates a FreeMarker environment for rendering within the view lifecycle.
64   * 
65   * @author Kuali Rice Team (rice.collab@kuali.org)
66   */
67  public class LifecycleRenderingContext {
68      
69      /**
70       * The FreeMarker environment to use for rendering.
71       */
72      private final Environment environment;
73  
74      /**
75       * Set of imported FreeMarker templates.
76       */
77      private final Set<String> importedTemplates;
78  
79      /**
80       * The buffer to use for capturing rendered output.
81       */
82      private final ByteArrayOutputStream buffer;
83  
84      /**
85       * The FreeMarker writer, for passing character data to the output buffer.
86       */
87      private final PrintWriter writer;
88  
89      /**
90       * Create FreeMarker environment for rendering within the view lifecycle.
91       * 
92       * @param request The active servlet request.
93       * @param rawresponse The active servlet response.
94       */
95      public LifecycleRenderingContext(Object model, HttpServletRequest request, HttpServletResponse rawresponse) {
96          try {
97              ProcessLogger.countBegin("render");
98              Map<String, Object> modelAttrs = new HashMap<String, Object>();
99              modelAttrs.put(UifConstants.DEFAULT_MODEL_NAME, model);
100             modelAttrs.put(KRADConstants.USER_SESSION_KEY, GlobalVariables.getUserSession());
101 
102             request.setAttribute(UifConstants.DEFAULT_MODEL_NAME, model);
103             request.setAttribute(KRADConstants.USER_SESSION_KEY, GlobalVariables.getUserSession());
104             modelAttrs.put(UifParameters.REQUEST, request);
105 
106             Map<String, String> properties = CoreApiServiceLocator.getKualiConfigurationService().getAllProperties();
107             modelAttrs.put(UifParameters.CONFIG_PROPERTIES, properties);
108 
109             modelAttrs.put(UifParameters.RENDER_HELPER_METHODS, new UifRenderHelperMethods());
110 
111             ByteArrayOutputStream out = new ByteArrayOutputStream();
112             writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
113 
114             Configuration config = FreeMarkerInlineRenderBootstrap.getFreeMarkerConfig();
115             Template template = new Template("", new StringReader(""), config);
116 
117             ServletContext servletContext = FreeMarkerInlineRenderBootstrap.getServletContext();
118             ObjectWrapper objectWrapper = FreeMarkerInlineRenderBootstrap.getObjectWrapper();
119             ServletContextHashModel servletContextHashModel = FreeMarkerInlineRenderBootstrap
120                     .getServletContextHashModel();
121             TaglibFactory taglibFactory = FreeMarkerInlineRenderBootstrap.getTaglibFactory();
122 
123             Response response = new Response(rawresponse);
124             AllHttpScopesHashModel global =
125                     new AllHttpScopesHashModel(objectWrapper, servletContext, request);
126             global.put(FreemarkerServlet.KEY_JSP_TAGLIBS, taglibFactory);
127             global.put(FreemarkerServlet.KEY_APPLICATION, servletContextHashModel);
128             global.put(FreemarkerServlet.KEY_SESSION,
129                     new HttpSessionHashModel(request.getSession(), objectWrapper));
130             global.put(FreemarkerServlet.KEY_REQUEST,
131                     new HttpRequestHashModel(request, response, objectWrapper));
132             global.put(FreemarkerServlet.KEY_REQUEST_PARAMETERS,
133                     new HttpRequestParametersHashModel(request));
134             global.put(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
135                     new RequestContext(request, response, servletContext, modelAttrs));
136 
137             global.put(UifParameters.VIEW, ViewLifecycle.getView());
138             
139 //            Map<String, String> properties = CoreApiServiceLocator.getKualiConfigurationService()
140 //                    .getAllProperties();
141 //            global.put(UifParameters.CONFIG_PROPERTIES, properties);
142 //            global.put(UifParameters.RENDER_HELPER_METHODS, new UifRenderHelperMethods());
143 
144             Environment env = template.createProcessingEnvironment(global, writer);
145             env.importLib("/krad/WEB-INF/ftl/lib/krad.ftl", "krad");
146             env.importLib("/krad/WEB-INF/ftl/lib/spring.ftl", "spring");
147             
148             environment = env;
149             buffer = out;
150             importedTemplates = new HashSet<String>();
151 
152         } catch (IOException e) {
153             throw new IllegalStateException("Failed to initialize FreeMarker for rendering", e);
154         } catch (TemplateException e) {
155             throw new IllegalStateException("Failed to initialize FreeMarker for rendering", e);
156         } finally {
157             ProcessLogger.countEnd("render", model.getClass().getName());
158         }
159     }
160 
161     /**
162      * Get the FreeMarker environment for processing the rendering phase, initializing the
163      * environment if needed.
164      * 
165      * @return The FreeMarker environment for processing the rendering phase, initializing the
166      *         environment if needed.
167      */
168     public Environment getEnvironment() {
169         return environment;
170     }
171 
172     /**
173      * Clear the output buffer used during rendering, in preparation for rendering another component
174      * using the same environment.
175      */
176     public void clearRenderingBuffer() {
177         buffer.reset();
178     }
179 
180     /**
181      * Get all output rendered in the FreeMarker environment.
182      */
183     public String getRenderedOutput() {
184         try {
185             writer.flush();
186             buffer.flush();
187             return buffer.toString("UTF-8");
188         } catch (UnsupportedEncodingException e) {
189             throw new IllegalStateException("UTF-8 is unsupported", e);
190         } catch (IOException e) {
191             throw new IllegalStateException("Unexpected exception flusing buffer", e);
192         }
193     }
194 
195     /**
196      * Import a FreeMarker template for rendering into the current environment.
197      * 
198      * @param template The path to the FreeMarker template.
199      */
200     public void importTemplate(String template) {
201         if (isImported(template)) {
202             return;
203         }
204 
205         try {
206             String templateNameString = TemplateCache.getFullTemplatePath(environment, "", template);
207             environment.include(environment.getTemplateForInclusion(templateNameString, null, true));
208         } catch (ParseException e) {
209             throw new IllegalStateException("Error parsing imported template " + template, e);
210         } catch (TemplateException e) {
211             throw new IllegalStateException("Error importing template " + template, e);
212         } catch (IOException e) {
213             throw new IllegalStateException("Error importing template " + template, e);
214         }
215     }
216     
217     public boolean isImported(String template) {
218         return template == null || !importedTemplates.add(template);
219     }
220     
221     /**
222      * Wraps the lifcycle's servlet response in order to redirect output to the rendering context
223      * buffer.
224      * 
225      * @author Kuali Rice Team (rice.collab@kuali.org)
226      */
227     private class Response extends HttpServletResponseWrapper {
228 
229         /**
230          * Constructor.
231          * 
232          * @param response The response to wrap.
233          */
234         public Response(HttpServletResponse response) {
235             super(response);
236         }
237 
238         /**
239          * {@inheritDoc}
240          */
241         @Override
242         public ServletOutputStream getOutputStream() throws IOException {
243             return new ServletOutputStream() {
244                 @Override
245                 public void write(int b) throws IOException {
246                     buffer.write(b);
247                 }
248             };
249         }
250 
251         /**
252          * {@inheritDoc}
253          */
254         @Override
255         public PrintWriter getWriter() throws IOException {
256             return writer;
257         }
258 
259         /**
260          * {@inheritDoc}
261          */
262         @Override
263         public int getBufferSize() {
264             return buffer.size();
265         }
266 
267         /**
268          * {@inheritDoc}
269          */
270         @Override
271         public void flushBuffer() throws IOException {
272             writer.flush();
273         }
274 
275         /**
276          * {@inheritDoc}
277          */
278         @Override
279         public void reset() {
280             throw new UnsupportedOperationException("reset() should not be used during rendering");
281         }
282 
283         /**
284          * {@inheritDoc}
285          */
286         @Override
287         public void resetBuffer() {
288             writer.flush();
289             buffer.reset();
290         }
291 
292     }
293 }