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}