001/** 002 * Copyright 2005-2016 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.beans.factory.InitializingBean; 034import org.springframework.context.ApplicationContext; 035import org.springframework.context.ApplicationContextAware; 036import org.springframework.web.context.ServletContextAware; 037import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; 038 039import freemarker.core.InlineTemplateElement; 040import freemarker.ext.jsp.TaglibFactory; 041import freemarker.ext.servlet.ServletContextHashModel; 042import freemarker.template.Configuration; 043import freemarker.template.ObjectWrapper; 044import freemarker.template.TemplateException; 045 046/** 047 * Register inline template processing adaptors for high-traffic KRAD templates. 048 * 049 * @author Kuali Rice Team (rice.collab@kuali.org) 050 */ 051public class FreeMarkerInlineRenderBootstrap implements InitializingBean, ApplicationContextAware, ServletContextAware { 052 053 private static final Logger LOG = Logger.getLogger(FreeMarkerInlineRenderBootstrap.class); 054 055 /** 056 * The freemarker configuration. 057 */ 058 private static Configuration freeMarkerConfig; 059 060 /** 061 * The application context. 062 */ 063 private static ApplicationContext applicationContext; 064 065 /** 066 * The servlet context. 067 */ 068 private static ServletContext servletContext; 069 070 /** 071 * The tablib factory for use in the component rendering phase. 072 */ 073 private static TaglibFactory taglibFactory; 074 075 /** 076 * The object wrapper for use in the component rendering phase. 077 */ 078 private static ObjectWrapper objectWrapper; 079 080 /** 081 * Servlet context hash model for use in the component rendering phase. 082 */ 083 private static ServletContextHashModel servletContextHashModel; 084 085 /** 086 * Get the FreeMarker configuration initialized for the current KRAD application. 087 * 088 * @return The FreeMarker configuration initialized for the current KRAD application. 089 */ 090 public static Configuration getFreeMarkerConfig() { 091 if (freeMarkerConfig == null) { 092 throw new IllegalStateException("FreeMarker configuruation is not available, " 093 + "use krad-base-servlet.xml or define FreeMarkerInlineRenderBootstrap in servlet.xml"); 094 } 095 096 return freeMarkerConfig; 097 } 098 099 /** 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}