001/**
002 * Copyright 2005-2018 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.IOException;
019import java.util.Collections;
020import java.util.Enumeration;
021import java.util.HashSet;
022
023import javax.servlet.GenericServlet;
024import javax.servlet.ServletConfig;
025import javax.servlet.ServletContext;
026import javax.servlet.ServletException;
027import javax.servlet.ServletRequest;
028import javax.servlet.ServletResponse;
029
030import org.apache.log4j.Logger;
031import org.springframework.beans.BeansException;
032import org.springframework.beans.factory.BeanInitializationException;
033import org.springframework.context.ApplicationContext;
034import org.springframework.context.ApplicationContextAware;
035import org.springframework.web.context.ServletContextAware;
036import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
037
038import freemarker.ext.jsp.TaglibFactory;
039import freemarker.ext.servlet.ServletContextHashModel;
040import freemarker.template.Configuration;
041import freemarker.template.ObjectWrapper;
042import freemarker.template.TemplateException;
043
044/**
045 * Register inline template processing adaptors for high-traffic KRAD templates.
046 * 
047 * @author Kuali Rice Team (rice.collab@kuali.org)
048 */
049public class FreeMarkerInlineRenderBootstrap implements ApplicationContextAware, ServletContextAware {
050
051    private static final Logger LOG = Logger.getLogger(FreeMarkerInlineRenderBootstrap.class);
052    
053    /**
054     * The freemarker configuration.
055     */
056    private static Configuration freeMarkerConfig;
057
058    /**
059     * The application context.
060     */
061    private static ApplicationContext applicationContext;
062    
063    /**
064     * The servlet context.
065     */
066    private static ServletContext servletContext;
067
068    /**
069     * The tablib factory for use in the component rendering phase.
070     */
071    private static TaglibFactory taglibFactory;
072
073    /**
074     * The object wrapper for use in the component rendering phase.
075     */
076    private static ObjectWrapper objectWrapper;
077
078    /**
079     * Servlet context hash model for use in the component rendering phase.
080     */
081    private static ServletContextHashModel servletContextHashModel;
082    
083    /**
084     * Get the FreeMarker configuration initialized for the current KRAD application. 
085     * 
086     * @return The FreeMarker configuration initialized for the current KRAD application.
087     */
088    public static Configuration getFreeMarkerConfig() {
089        if (freeMarkerConfig == null) {
090            throw new IllegalStateException("FreeMarker configuruation is not available, "
091                    + "use krad-base-servlet.xml or define FreeMarkerInlineRenderBootstrap in servlet.xml");
092        }
093        
094        return freeMarkerConfig;
095    }
096
097    /**
098     * Get the servlet context initialized for the current KRAD application. 
099     * 
100     * @return The servlet context initialized for the current KRAD application.
101     */
102    public static ServletContext getServletContext() {
103        if (servletContext == null) {
104            throw new IllegalStateException("Servlet context is not available, "
105                    + "use krad-base-servlet.xml or define FreeMarkerInlineRenderBootstrap in servlet.xml");
106        }
107        
108        return servletContext;
109    }
110
111    /**
112     * Get the tablib factory for use in the component rendering phase.
113     * 
114     * @return The tablib factory for use in the component rendering phase.
115     */
116    public static TaglibFactory getTaglibFactory() {
117        return taglibFactory;
118    }
119
120    /**
121     * Get the object wrapper for use in the component rendering phase.
122     * 
123     * @return The object wrapper for use in the component rendering phase.
124     */
125    public static ObjectWrapper getObjectWrapper() {
126        return objectWrapper;
127    }
128
129    /**
130     * Get the servlet context hash model for use in the component rendering phase.
131     * 
132     * @return The servlet context hash model for use in the component rendering phase.
133     */
134    public static ServletContextHashModel getServletContextHashModel() {
135        return servletContextHashModel;
136    }
137
138    /**
139     * Needed for JSP access in FreeMarker.
140     * 
141     * <p>Derived from Spring FreeMarkerView.</p>
142     */
143    private static class ServletAdapter extends GenericServlet {
144
145        private static final long serialVersionUID = 8509364718276109450L;
146
147        @Override
148        public void service(ServletRequest servletRequest, ServletResponse servletResponse) {}
149        
150    }
151
152    /**
153     * Internal implementation of the {@link ServletConfig} interface,
154     * to be passed to the servlet adapter.
155     * 
156     * <p>Derived from Spring FreeMarkerView.</p>
157     */
158    private static class DelegatingServletConfig implements ServletConfig {
159
160        public String getServletName() {
161            return applicationContext.getDisplayName();
162        }
163
164        public ServletContext getServletContext() {
165            return servletContext;
166        }
167
168        public String getInitParameter(String paramName) {
169            return null;
170        }
171
172        public Enumeration<String> getInitParameterNames() {
173            return Collections.enumeration(new HashSet<String>());
174        }
175    }
176
177    /**
178     * Initialize FreeMarker elements after servlet context and FreeMarker configuration have both
179     * been populated.
180     */
181    private static void finishConfig() {
182        if (freeMarkerConfig != null && servletContext != null) {
183            taglibFactory = new TaglibFactory(servletContext);
184            
185            objectWrapper = freeMarkerConfig.getObjectWrapper();
186            if (objectWrapper == null) {
187                objectWrapper = ObjectWrapper.DEFAULT_WRAPPER;
188            }
189
190            GenericServlet servlet = new ServletAdapter();
191            try {
192                servlet.init(new DelegatingServletConfig());
193            } catch (ServletException ex) {
194                throw new BeanInitializationException("Initialization of GenericServlet adapter failed", ex);
195            }
196            
197            servletContextHashModel = new ServletContextHashModel(servlet, ObjectWrapper.DEFAULT_WRAPPER);
198            
199            LOG.info("Freemarker configuration complete");
200        }
201    }
202
203    /**
204     * {@inheritDoc}
205     */
206    @Override
207    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
208        try {
209            freeMarkerConfig = ((FreeMarkerConfigurer) applicationContext.getBean("freemarkerConfig"))
210                    .createConfiguration();
211            LOG.info("Set freemarker bootstrap " + freeMarkerConfig);
212        } catch (IOException e) {
213            throw new IllegalStateException("Error loading freemarker configuration", e);
214        } catch (TemplateException e) {
215            throw new IllegalStateException("Error loading freemarker configuration", e);
216        }
217        finishConfig();
218    }
219
220    /**
221     * {@inheritDoc}
222     */
223    @Override
224    public void setServletContext(ServletContext servletContext) {
225        FreeMarkerInlineRenderBootstrap.servletContext = servletContext;
226        finishConfig();
227    }
228
229}