001/** 002 * Copyright 2005-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.freemarker; 017 018import java.io.ByteArrayOutputStream; 019import java.io.IOException; 020import java.io.OutputStreamWriter; 021import java.io.PrintWriter; 022import java.io.StringReader; 023import java.io.UnsupportedEncodingException; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.Map; 027import java.util.Set; 028 029import javax.servlet.ServletContext; 030import javax.servlet.ServletOutputStream; 031import javax.servlet.http.HttpServletRequest; 032import javax.servlet.http.HttpServletResponse; 033import javax.servlet.http.HttpServletResponseWrapper; 034 035import org.kuali.rice.core.api.CoreApiServiceLocator; 036import org.kuali.rice.krad.uif.UifConstants; 037import org.kuali.rice.krad.uif.UifParameters; 038import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 039import org.kuali.rice.krad.uif.util.ProcessLogger; 040import org.kuali.rice.krad.uif.util.UifRenderHelperMethods; 041import org.kuali.rice.krad.util.GlobalVariables; 042import org.kuali.rice.krad.util.KRADConstants; 043import org.springframework.mock.web.MockHttpServletResponse; 044import org.springframework.web.servlet.support.RequestContext; 045import org.springframework.web.servlet.view.AbstractTemplateView; 046 047import freemarker.cache.TemplateCache; 048import freemarker.core.Environment; 049import freemarker.core.ParseException; 050import freemarker.ext.jsp.TaglibFactory; 051import freemarker.ext.servlet.AllHttpScopesHashModel; 052import freemarker.ext.servlet.FreemarkerServlet; 053import freemarker.ext.servlet.HttpRequestHashModel; 054import freemarker.ext.servlet.HttpRequestParametersHashModel; 055import freemarker.ext.servlet.HttpSessionHashModel; 056import freemarker.ext.servlet.ServletContextHashModel; 057import freemarker.template.Configuration; 058import freemarker.template.ObjectWrapper; 059import freemarker.template.Template; 060import freemarker.template.TemplateException; 061 062/** 063 * Encapsulates a FreeMarker environment for rendering within the view lifecycle. 064 * 065 * @author Kuali Rice Team (rice.collab@kuali.org) 066 */ 067public class LifecycleRenderingContext { 068 069 /** 070 * The FreeMarker environment to use for rendering. 071 */ 072 private final Environment environment; 073 074 /** 075 * Set of imported FreeMarker templates. 076 */ 077 private final Set<String> importedTemplates; 078 079 /** 080 * The buffer to use for capturing rendered output. 081 */ 082 private final ByteArrayOutputStream buffer; 083 084 /** 085 * The FreeMarker writer, for passing character data to the output buffer. 086 */ 087 private final PrintWriter writer; 088 089 /** 090 * Create FreeMarker environment for rendering within the view lifecycle. 091 * 092 * @param request The active servlet request. 093 * @param rawresponse The active servlet response. 094 */ 095 public LifecycleRenderingContext(Object model, HttpServletRequest request) { 096 try { 097 ProcessLogger.countBegin("render"); 098 Map<String, Object> modelAttrs = new HashMap<String, Object>(); 099 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(new MockHttpServletResponse()); 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}