package com.jspxcms.common.freemarker; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Locale; import java.util.Map; import javax.servlet.GenericServlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContextException; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.servlet.view.AbstractTemplateView; import org.springframework.web.servlet.view.freemarker.FreeMarkerConfig; import freemarker.core.ParseException; import freemarker.ext.jsp.TaglibFactory; import freemarker.ext.servlet.AllHttpScopesHashModel; import freemarker.ext.servlet.FreemarkerServlet; import freemarker.ext.servlet.HttpRequestHashModel; import freemarker.ext.servlet.HttpSessionHashModel; import freemarker.ext.servlet.ServletContextHashModel; import freemarker.template.Configuration; import freemarker.template.ObjectWrapper; import freemarker.template.SimpleHash; import freemarker.template.Template; import freemarker.template.TemplateException; /** * FreeMarker视图 * * 绑定Param、ParamValues参数 * * @author liufang * */ public class FreeMarkerView extends AbstractTemplateView { private String encoding; private Configuration configuration; private TaglibFactory taglibFactory; private ServletContextHashModel servletContextHashModel; /** * Set the encoding of the FreeMarker template file. Default is determined * by the FreeMarker Configuration: "ISO-8859-1" if not specified otherwise. * <p> * Specify the encoding in the FreeMarker Configuration rather than per * template if all your templates share a common encoding. */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * Return the encoding for the FreeMarker template. */ protected String getEncoding() { return this.encoding; } /** * Set the FreeMarker Configuration to be used by this view. * <p> * If this is not set, the default lookup will occur: a single * {@link FreeMarkerConfig} is expected in the current web application * context, with any bean name. <strong>Note:</strong> using this method * will cause a new instance of {@link TaglibFactory} to created for every * single {@link FreeMarkerView} instance. This can be quite expensive in * terms of memory and initial CPU usage. In production it is recommended * that you use a {@link FreeMarkerConfig} which exposes a single shared * {@link TaglibFactory}. */ public void setConfiguration(Configuration configuration) { this.configuration = configuration; } /** * Return the FreeMarker configuration used by this view. */ protected Configuration getConfiguration() { return this.configuration; } /** * Invoked on startup. Looks for a single FreeMarkerConfig bean to find the * relevant Configuration for this factory. * <p> * Checks that the template for the default Locale can be found: FreeMarker * will check non-Locale-specific templates if a locale-specific one is not * found. * * @see freemarker.cache.TemplateCache#getTemplate */ @Override protected void initServletContext(ServletContext servletContext) throws BeansException { if (getConfiguration() != null) { this.taglibFactory = new TaglibFactory(servletContext); } else { FreeMarkerConfig config = autodetectConfiguration(); setConfiguration(config.getConfiguration()); this.taglibFactory = config.getTaglibFactory(); } GenericServlet servlet = new GenericServletAdapter(); try { servlet.init(new DelegatingServletConfig()); } catch (ServletException ex) { throw new BeanInitializationException( "Initialization of GenericServlet adapter failed", ex); } this.servletContextHashModel = new ServletContextHashModel(servlet, getObjectWrapper()); } /** * Autodetect a {@link FreeMarkerConfig} object via the ApplicationContext. * * @return the Configuration instance to use for FreeMarkerViews * @throws BeansException * if no Configuration instance could be found * @see #getApplicationContext * @see #setConfiguration */ protected FreeMarkerConfig autodetectConfiguration() throws BeansException { try { return BeanFactoryUtils.beanOfTypeIncludingAncestors( getApplicationContext(), FreeMarkerConfig.class, true, false); } catch (NoSuchBeanDefinitionException ex) { throw new ApplicationContextException( "Must define a single FreeMarkerConfig bean in this web application context " + "(may be inherited): FreeMarkerConfigurer is the usual implementation. " + "This bean may be given any name.", ex); } } /** * Return the configured FreeMarker {@link ObjectWrapper}, or the * {@link ObjectWrapper#DEFAULT_WRAPPER default wrapper} if none specified. * * @see freemarker.template.Configuration#getObjectWrapper() */ protected ObjectWrapper getObjectWrapper() { ObjectWrapper ow = getConfiguration().getObjectWrapper(); return (ow != null ? ow : ObjectWrapper.DEFAULT_WRAPPER); } /** * Check that the FreeMarker template used for this view exists and is * valid. * <p> * Can be overridden to customize the behavior, for example in case of * multiple templates to be rendered into a single view. */ @Override public boolean checkResource(Locale locale) throws Exception { try { // Check that we can get the template, even if we might subsequently // get it again. getTemplate(getUrl(), locale); return true; } catch (FileNotFoundException ex) { if (logger.isDebugEnabled()) { logger.debug("No FreeMarker view found for URL: " + getUrl()); } return false; } catch (ParseException ex) { throw new ApplicationContextException( "Failed to parse FreeMarker template for URL [" + getUrl() + "]", ex); } catch (IOException ex) { throw new ApplicationContextException( "Could not load FreeMarker template for URL [" + getUrl() + "]", ex); } } /** * Process the model map by merging it with the FreeMarker template. Output * is directed to the servlet response. * <p> * This method can be overridden if custom behavior is needed. */ @Override protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { exposeHelpers(model, request); doRender(model, request, response); } /** * Expose helpers unique to each rendering operation. This is necessary so * that different rendering operations can't overwrite each other's formats * etc. * <p> * Called by {@code renderMergedTemplateModel}. The default implementation * is empty. This method can be overridden to add custom helpers to the * model. * * @param model * The model that will be passed to the template at merge time * @param request * current HTTP request * @throws Exception * if there's a fatal error while we're adding information to * the context * @see #renderMergedTemplateModel */ protected void exposeHelpers(Map<String, Object> model, HttpServletRequest request) throws Exception { } /** * Render the FreeMarker view to the given response, using the given model * map which contains the complete template model to use. * <p> * The default implementation renders the template specified by the "url" * bean property, retrieved via {@code getTemplate}. It delegates to the * {@code processTemplate} method to merge the template instance with the * given template model. * <p> * Adds the standard Freemarker hash models to the model: request * parameters, request, session and application (ServletContext), as well as * the JSP tag library hash model. * <p> * Can be overridden to customize the behavior, for example to render * multiple templates into a single view. * * @param model * the model to use for rendering * @param request * current HTTP request * @param response * current servlet response * @throws IOException * if the template file could not be retrieved * @throws Exception * if rendering failed * @see #setUrl * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale * @see #getTemplate(java.util.Locale) * @see #processTemplate * @see freemarker.ext.servlet.FreemarkerServlet */ protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose model to JSP tags (as request attributes). exposeModelAsRequestAttributes(model, request); // Expose all standard FreeMarker hash models. SimpleHash fmModel = buildTemplateModel(model, request, response); if (logger.isDebugEnabled()) { logger.debug("Rendering FreeMarker template [" + getUrl() + "] in FreeMarkerView '" + getBeanName() + "'"); } // Grab the locale-specific version of the template. Locale locale = RequestContextUtils.getLocale(request); processTemplate(getTemplate(locale), fmModel, response); } /** * Build a FreeMarker template model for the given model Map. * <p> * The default implementation builds a {@link AllHttpScopesHashModel}. * * @param model * the model to use for rendering * @param request * current HTTP request * @param response * current servlet response * @return the FreeMarker template model, as a {@link SimpleHash} or * subclass thereof */ protected SimpleHash buildTemplateModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) { AllHttpScopesHashModel fmModel = new AllHttpScopesHashModel( getObjectWrapper(), getServletContext(), request); fmModel.put(FreemarkerServlet.KEY_JSP_TAGLIBS, this.taglibFactory); fmModel.put(FreemarkerServlet.KEY_APPLICATION, this.servletContextHashModel); fmModel.put(FreemarkerServlet.KEY_SESSION, buildSessionModel(request, response)); fmModel.put(FreemarkerServlet.KEY_REQUEST, new HttpRequestHashModel( request, response, getObjectWrapper())); fmModel.put(Freemarkers.KEY_PARAMETERS, new HttpRequestParametersHashModel(request)); fmModel.put(Freemarkers.KEY_PARAMETER_VALUES, new HttpRequestParameterValuesHashModel(request)); fmModel.putAll(model); return fmModel; } /** * Build a FreeMarker {@link HttpSessionHashModel} for the given request, * detecting whether a session already exists and reacting accordingly. * * @param request * current HTTP request * @param response * current servlet response * @return the FreeMarker HttpSessionHashModel */ private HttpSessionHashModel buildSessionModel(HttpServletRequest request, HttpServletResponse response) { HttpSession session = request.getSession(false); if (session != null) { return new HttpSessionHashModel(session, getObjectWrapper()); } else { return new HttpSessionHashModel(null, request, response, getObjectWrapper()); } } /** * Retrieve the FreeMarker template for the given locale, to be rendering by * this view. * <p> * By default, the template specified by the "url" bean property will be * retrieved. * * @param locale * the current locale * @return the FreeMarker template to render * @throws IOException * if the template file could not be retrieved * @see #setUrl * @see #getTemplate(String, java.util.Locale) */ protected Template getTemplate(Locale locale) throws IOException { return getTemplate(getUrl(), locale); } /** * Retrieve the FreeMarker template specified by the given name, using the * encoding specified by the "encoding" bean property. * <p> * Can be called by subclasses to retrieve a specific template, for example * to render multiple templates into a single view. * * @param name * the file name of the desired template * @param locale * the current locale * @return the FreeMarker template * @throws IOException * if the template file could not be retrieved */ protected Template getTemplate(String name, Locale locale) throws IOException { return (getEncoding() != null ? getConfiguration().getTemplate(name, locale, getEncoding()) : getConfiguration().getTemplate(name, locale)); } /** * Process the FreeMarker template to the servlet response. * <p> * Can be overridden to customize the behavior. * * @param template * the template to process * @param model * the model for the template * @param response * servlet response (use this to get the OutputStream or Writer) * @throws IOException * if the template file could not be retrieved * @throws TemplateException * if thrown by FreeMarker * @see freemarker.template.Template#process(Object, java.io.Writer) */ protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response) throws IOException, TemplateException { template.process(model, response.getWriter()); } /** * Simple adapter class that extends {@link GenericServlet}. Needed for JSP * access in FreeMarker. */ @SuppressWarnings("serial") private static class GenericServletAdapter extends GenericServlet { @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) { // no-op } } /** * Internal implementation of the {@link ServletConfig} interface, to be * passed to the servlet adapter. */ private class DelegatingServletConfig implements ServletConfig { public String getServletName() { return FreeMarkerView.this.getBeanName(); } public ServletContext getServletContext() { return FreeMarkerView.this.getServletContext(); } public String getInitParameter(String paramName) { return null; } public Enumeration<String> getInitParameterNames() { return Collections.enumeration(new HashSet<String>()); } } }