/** * <pre> * Copyright: Copyright(C) 2011-2012, ketayao.com * Date: 2013年8月12日 * Author: <a href="mailto:ketayao@gmail.com">ketayao</a> * Description: * * </pre> **/ package com.ketayao.fensy.mvc.view; import java.io.IOException; import java.io.PrintWriter; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Properties; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ketayao.fensy.exception.FensyException; import com.ketayao.fensy.mvc.WebContext; import freemarker.ext.jsp.TaglibFactory; import freemarker.ext.servlet.AllHttpScopesHashModel; import freemarker.ext.servlet.FreemarkerServlet; import freemarker.ext.servlet.HttpRequestHashModel; import freemarker.ext.servlet.HttpRequestParametersHashModel; 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; import freemarker.template.TemplateExceptionHandler; /** * * @author <a href="mailto:ketayao@gmail.com">ketayao</a> * @since 2013年8月12日 上午10:42:17 */ public class FreeMarkerView implements View { private static final Logger logger = LoggerFactory.getLogger(FreeMarkerView.class); private transient static final Configuration config = new Configuration(); private boolean init = false; private String encoding; private TaglibFactory taglibFactory; private ServletContextHashModel servletContextHashModel; //private static final String TEMPLATE_PATH = "TemplatePath"; /** * 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; } /** * freemarker can not load freemarker.properies automatically */ public static Configuration getConfiguration() { return config; } protected void init(ServletContext servletContext) throws IOException, ServletException { if (init == true) { return; } Properties p = new Properties(); try { p.load(FreeMarkerView.class.getClassLoader().getResourceAsStream( "freemarker.properties")); } catch (IOException e) { logger.warn("not found freemarker.properties......"); init = true; return; } config.setServletContextForTemplateLoading(servletContext, null); //p.getProperty(TEMPLATE_PATH)); // "WEB-INF/templates" config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); config.setObjectWrapper(getObjectWrapper()); try { config.setSettings(p); } catch (TemplateException e) { logger.error("freemarker init error"); throw new ServletException(e); } setEncoding(config.getDefaultEncoding()); this.taglibFactory = new TaglibFactory(servletContext); GenericServlet servlet = new GenericServletAdapter(); try { servlet.init(new DelegatingServletConfig(servletContext)); } catch (ServletException ex) { throw new ServletException("Initialization of GenericServlet adapter failed", ex); } this.servletContextHashModel = new ServletContextHashModel(servlet, getObjectWrapper()); init = true; } /** * 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); } protected SimpleHash buildTemplateModel(Map<String, Object> model, WebContext rc) { AllHttpScopesHashModel fmModel = new AllHttpScopesHashModel(getObjectWrapper(), rc.getContext(), rc.getRequest()); fmModel.put(FreemarkerServlet.KEY_JSP_TAGLIBS, this.taglibFactory); fmModel.put(FreemarkerServlet.KEY_APPLICATION, this.servletContextHashModel); fmModel.put(FreemarkerServlet.KEY_SESSION, buildSessionModel(rc.getRequest(), rc.getResponse())); fmModel.put(FreemarkerServlet.KEY_REQUEST, new HttpRequestHashModel(rc.getRequest(), rc.getResponse(), getObjectWrapper())); fmModel.put(FreemarkerServlet.KEY_REQUEST_PARAMETERS, new HttpRequestParametersHashModel(rc.getRequest())); 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 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)); } /** * @param rc * @param templatePath * @throws IOException * @throws ServletException * @see com.ketayao.fensy.mvc.view.View#render(com.ketayao.fensy.mvc.WebContext, java.lang.String) */ @Override public void render(WebContext rc, String templatePath) throws IOException, ServletException { init(rc.getContext()); rc.getResponse().setContentType("text/html; charset=" + getEncoding()); // Expose all standard FreeMarker hash models. Map<String, Object> model = new HashMap<String, Object>(); SimpleHash fmModel = buildTemplateModel(model, rc); // Grab the locale-specific version of the template. Locale locale = rc.getLocale(); PrintWriter writer = null; try { Template template = getTemplate(templatePath, locale); writer = rc.getResponse().getWriter(); template.process(fmModel, writer); // Merge the data-model and the template } catch (Exception e) { e.printStackTrace(); throw new FensyException(e); } finally { if (writer != null) writer.close(); } } /** * @return * @see com.ketayao.fensy.mvc.view.View#getExt() */ @Override public String getExt() { return ".ftl"; } /** * 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 { private ServletContext servletContext; public DelegatingServletConfig(ServletContext servletContext) { this.servletContext = servletContext; } public String getServletName() { return servletContext.getServletContextName(); } public ServletContext getServletContext() { return servletContext; } public String getInitParameter(String paramName) { return null; } public Enumeration<String> getInitParameterNames() { return Collections.enumeration(new HashSet<String>()); } } }