View Javadoc
1   /**
2    * Copyright 2005-2015 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.IOException;
19  import java.util.Collections;
20  import java.util.Enumeration;
21  import java.util.HashSet;
22  
23  import javax.servlet.GenericServlet;
24  import javax.servlet.ServletConfig;
25  import javax.servlet.ServletContext;
26  import javax.servlet.ServletException;
27  import javax.servlet.ServletRequest;
28  import javax.servlet.ServletResponse;
29  
30  import org.apache.log4j.Logger;
31  import org.springframework.beans.BeansException;
32  import org.springframework.beans.factory.BeanInitializationException;
33  import org.springframework.beans.factory.InitializingBean;
34  import org.springframework.context.ApplicationContext;
35  import org.springframework.context.ApplicationContextAware;
36  import org.springframework.web.context.ServletContextAware;
37  import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
38  
39  import freemarker.core.InlineTemplateElement;
40  import freemarker.ext.jsp.TaglibFactory;
41  import freemarker.ext.servlet.ServletContextHashModel;
42  import freemarker.template.Configuration;
43  import freemarker.template.ObjectWrapper;
44  import freemarker.template.TemplateException;
45  
46  /**
47   * Register inline template processing adaptors for high-traffic KRAD templates.
48   * 
49   * @author Kuali Rice Team (rice.collab@kuali.org)
50   */
51  public class FreeMarkerInlineRenderBootstrap implements InitializingBean, ApplicationContextAware, ServletContextAware {
52  
53      private static final Logger LOG = Logger.getLogger(FreeMarkerInlineRenderBootstrap.class);
54      
55      /**
56       * The freemarker configuration.
57       */
58      private static Configuration freeMarkerConfig;
59  
60      /**
61       * The application context.
62       */
63      private static ApplicationContext applicationContext;
64      
65      /**
66       * The servlet context.
67       */
68      private static ServletContext servletContext;
69  
70      /**
71       * The tablib factory for use in the component rendering phase.
72       */
73      private static TaglibFactory taglibFactory;
74  
75      /**
76       * The object wrapper for use in the component rendering phase.
77       */
78      private static ObjectWrapper objectWrapper;
79  
80      /**
81       * Servlet context hash model for use in the component rendering phase.
82       */
83      private static ServletContextHashModel servletContextHashModel;
84      
85      /**
86       * Get the FreeMarker configuration initialized for the current KRAD application. 
87       * 
88       * @return The FreeMarker configuration initialized for the current KRAD application.
89       */
90      public static Configuration getFreeMarkerConfig() {
91          if (freeMarkerConfig == null) {
92              throw new IllegalStateException("FreeMarker configuruation is not available, "
93                      + "use krad-base-servlet.xml or define FreeMarkerInlineRenderBootstrap in servlet.xml");
94          }
95          
96          return freeMarkerConfig;
97      }
98  
99      /**
100      * Get the servlet context initialized for the current KRAD application. 
101      * 
102      * @return The servlet context initialized for the current KRAD application.
103      */
104     public static ServletContext getServletContext() {
105         if (servletContext == null) {
106             throw new IllegalStateException("Servlet context is not available, "
107                     + "use krad-base-servlet.xml or define FreeMarkerInlineRenderBootstrap in servlet.xml");
108         }
109         
110         return servletContext;
111     }
112 
113     /**
114      * Get the tablib factory for use in the component rendering phase.
115      * 
116      * @return The tablib factory for use in the component rendering phase.
117      */
118     public static TaglibFactory getTaglibFactory() {
119         return taglibFactory;
120     }
121 
122     /**
123      * Get the object wrapper for use in the component rendering phase.
124      * 
125      * @return The object wrapper for use in the component rendering phase.
126      */
127     public static ObjectWrapper getObjectWrapper() {
128         return objectWrapper;
129     }
130 
131     /**
132      * Get the servlet context hash model for use in the component rendering phase.
133      * 
134      * @return The servlet context hash model for use in the component rendering phase.
135      */
136     public static ServletContextHashModel getServletContextHashModel() {
137         return servletContextHashModel;
138     }
139 
140     /**
141      * Needed for JSP access in FreeMarker.
142      * 
143      * <p>Derived from Spring FreeMarkerView.</p>
144      */
145     private static class ServletAdapter extends GenericServlet {
146 
147         private static final long serialVersionUID = 8509364718276109450L;
148 
149         @Override
150         public void service(ServletRequest servletRequest, ServletResponse servletResponse) {}
151         
152     }
153 
154     /**
155      * Internal implementation of the {@link ServletConfig} interface,
156      * to be passed to the servlet adapter.
157      * 
158      * <p>Derived from Spring FreeMarkerView.</p>
159      */
160     private static class DelegatingServletConfig implements ServletConfig {
161 
162         public String getServletName() {
163             return applicationContext.getDisplayName();
164         }
165 
166         public ServletContext getServletContext() {
167             return servletContext;
168         }
169 
170         public String getInitParameter(String paramName) {
171             return null;
172         }
173 
174         public Enumeration<String> getInitParameterNames() {
175             return Collections.enumeration(new HashSet<String>());
176         }
177     }
178 
179     /**
180      * Initialize FreeMarker elements after servlet context and FreeMarker configuration have both
181      * been populated.
182      */
183     private static void finishConfig() {
184         if (freeMarkerConfig != null && servletContext != null) {
185             taglibFactory = new TaglibFactory(servletContext);
186             
187             objectWrapper = freeMarkerConfig.getObjectWrapper();
188             if (objectWrapper == null) {
189                 objectWrapper = ObjectWrapper.DEFAULT_WRAPPER;
190             }
191 
192             GenericServlet servlet = new ServletAdapter();
193             try {
194                 servlet.init(new DelegatingServletConfig());
195             } catch (ServletException ex) {
196                 throw new BeanInitializationException("Initialization of GenericServlet adapter failed", ex);
197             }
198             
199             servletContextHashModel = new ServletContextHashModel(servlet, ObjectWrapper.DEFAULT_WRAPPER);
200             
201             LOG.info("Freemarker configuration complete");
202         }
203     }
204 
205     /**
206      * {@inheritDoc}
207      */
208     @Override
209     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
210         try {
211             freeMarkerConfig = ((FreeMarkerConfigurer) applicationContext.getBean("freemarkerConfig"))
212                     .createConfiguration();
213             LOG.info("Set freemarker bootstrap " + freeMarkerConfig);
214         } catch (IOException e) {
215             throw new IllegalStateException("Error loading freemarker configuration", e);
216         } catch (TemplateException e) {
217             throw new IllegalStateException("Error loading freemarker configuration", e);
218         }
219         finishConfig();
220     }
221 
222     /**
223      * {@inheritDoc}
224      */
225     @Override
226     public void setServletContext(ServletContext servletContext) {
227         FreeMarkerInlineRenderBootstrap.servletContext = servletContext;
228         finishConfig();
229     }
230 
231     /**
232      * Register high-traffic KRAD template adaptors.  
233      * 
234      * {@inheritDoc}
235      */
236     @Override
237     public void afterPropertiesSet() throws Exception {
238         InlineTemplateElement.registerAdaptor("script", new FreeMarkerScriptAdaptor());
239         InlineTemplateElement.registerAdaptor("template", new FreeMarkerTemplateAdaptor());
240         InlineTemplateElement.registerAdaptor("collectionGroup", new FreeMarkerCollectionGroupAdaptor());
241         InlineTemplateElement.registerAdaptor("stacked", new FreeMarkerStackedAdaptor());
242         InlineTemplateElement.registerAdaptor("groupWrap-open", new FreeMarkerOpenGroupWrapAdaptor());
243         InlineTemplateElement.registerAdaptor("groupWrap-close", new FreeMarkerCloseGroupWrapAdaptor());
244     }
245 
246 }