用FreeMarker做CMS模板--用户自定义模板

  最近在客户需求的要求就定制开发一套CMS,模板采用了Freemarker。最初的时候是设想用Freemarker标签开发的模板,也做网站页面的模板。然后可以直接在页面模板中使用开发好的标签,来显示网站内容。但总还是觉得这样的模板不够灵活,局限性比较大。后来通过和领导的商议决定采用一下方式,最终可以直接在Freemarker页面模板层对已封装好的API进行调用,可以实现在模板中的简单编程。

  这种做法也是在探索中,如果大家觉得有什么不妥之处还望点拨。razz

  • 模板存放在数据库中(通常我们都是编写Freemarker的ftl模板文件)
  • 采用StringTemplateLoader
  • 采用BeansWrapper

  首先我们要开发一个Servlet去拦截网站的请求,因为我们要去解析模板。FreeMarker为我们提供了一个现成的Servlet:freemarker.ext.servlet.FreemarkerServlet。但是这个不能满足我们的要求,它是按照URL请求的文件名称来去查找解析模板的,例如/article/demo.ftl,那么它只能去模板存放目录寻找demo.ftl模板。呵呵,其实这个 Servlet是未来让FreeMarker代替jsp使用的,而且他的它也无法装载数据库中的模板信息,所以我就参考它开发了自己的Servlet。

  

package freemarker.ext.servlet;


/**
 * 参考自freemarker.ext.servlet.FreemarkerServlet
 *  支持自定义标签的使用,支持自定义扩展名拦截.
 */
public class FreeMarkerStringTemplateViewServlet extends
		javax.servlet.http.HttpServlet {

	/**
	 * 为子类提供Log功能,方便子类使用
	 */
	protected Log log = LogFactory.getLog(getClass());

	/** TemplatePath */
	private static final String TEMPLATE_PATH = "TemplatePath";

	/** NoCache */
	private static final String NOCACHE = "NoCache";

	/** TemplateDelay */
	private static final String TEMPLATE_DELAY = "template_update_delay";

	/** DefaultEncoding */
	private static final String DEF_ENCODING = "default_encoding";

	/** Request */
	public static final String KEY_REQUEST = "Request";

	/** __FreeMarkerServlet.Request__ */
	public static final String KEY_REQUEST_PRIVATE =
										"__FreeMarkerServlet.Request__";

	/** RequestParameters */
	public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";

	/** Session */
	public static final String KEY_SESSION = "Session";

	/** Application */
	public static final String KEY_APPLICATION = "Application";

	/** __FreeMarkerServlet.Application__ */
	public static final String KEY_APPLICATION_PRIVATE =
								"__FreeMarkerServlet.Application__";

	/** JspTaglibs */
	public static final String KEY_JSP_TAGLIBS = "JspTaglibs";

	/** .freemarker.Request */
	private static final String ATTR_REQUEST_MODEL = ".freemarker.Request";

	/** .freemarker.RequestParameters */
	private static final String ATTR_REQUEST_PARAMETERS_MODEL =
											".freemarker.RequestParameters";

	/** .freemarker.Session */
	private static final String ATTR_SESSION_MODEL = ".freemarker.Session";

	/** .freemarker.Application */
	private static final String ATTR_APPLICATION_MODEL =
													".freemarker.Application";

	/** .freemarker.JspTaglibs */
	private static final String ATTR_JSP_TAGLIBS_MODEL =
													".freemarker.JspTaglibs";

	/** 日期 */
	private static final String EXPIRATION_DATE;

	static {
		GregorianCalendar expiration = new GregorianCalendar();
		expiration.roll(Calendar.YEAR, -1);
		SimpleDateFormat httpDate = new SimpleDateFormat(
				"yyyy-MMM-dd HH:mm:ss", java.util.Locale.CHINA);
		EXPIRATION_DATE = httpDate.format(expiration.getTime());
	}


	/**
	 * response返回是否使用缓存
	 */
	private boolean nocache;

	/**
	 * 创新Freemarker模板的必要条件
	 */
	private Configuration config;

	/**
	 * 采用BEANS_WRAPPER
	 */
	private ObjectWrapper wrapper;

	/**
	 * text/html
	 */
	private String contentType;

	/**
	 * 采用StringLoader,从数据库中读取模板信息
	 */
	//private StringTemplateLoader strTmpt;

	/**
	 * Servlet 初始化
	 */
	public void init() throws ServletException {

		try {
			config = new Configuration();

			config.setTemplateExceptionHandler(
					TemplateExceptionHandler.HTML_DEBUG_HANDLER);

			contentType = "text/html";

			// 采用BEANS_WRAPPER
			wrapper = ObjectWrapper.BEANS_WRAPPER;
			config.setObjectWrapper(wrapper);

			// 初始化所有的Servlet参数
			Enumeration initpnames = getServletConfig().getInitParameterNames();
			while (initpnames.hasMoreElements()) {
				String name = (String) initpnames.nextElement();
				String value = getInitParameter(name);

				if (name == null) {
					throw new ServletException(this.getClass().toString()
							+ "需要一些初始化参数,web.xml可能尚未完成.");
				}
				if (value == null) {
					throw new ServletException(this.getClass().toString()
							+ "有部分初始化参数未被赋值,web.xml可能尚未完成.");
				}

				if (name.equals(TEMPLATE_PATH)) {
					// ignore: we have already processed these do nothing..
					log.debug("忽略" + TEMPLATE_PATH);

				} else if (name.equals(DEF_ENCODING)) { // set DefaultEncoding
					log.debug(DEF_ENCODING + " value is:" + value);
					config.setDefaultEncoding(value);

				} else if (name.equals(TEMPLATE_DELAY)) { // 模板延迟更新时间
					try {
						log.debug(TEMPLATE_DELAY + " value is:" + value);
						config.setTemplateUpdateDelay(Integer.parseInt(value));
					} catch (NumberFormatException e) {
						throw new ServletException(e.getMessage() + ". '"
								+ TEMPLATE_DELAY + "'必须是整数");
					}

				} else if (name.equals(NOCACHE)) { // 设置缓存
					log.debug(NOCACHE + " value is :" + value);
					nocache = StringUtil.getYesNo(value);

				} else {
					// 设置其它参数,嘿嘿,如果参数名称不符合Configuration要求肯定要Exception
					config.setSetting(name, value);
				}
			}
		} catch (ServletException e) {
			throw e;
		} catch (Exception e) {
			throw new ServletException(e);
		}
	}

	/** Get请求 */
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			process(request, response);
	}

	/** Post请求 */
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			process(request, response);
	}

	/**
	 * 向StringTemplateLoader中添加模板内容
	 * @throws ServletException 
	 */
	private StringTemplateLoader addStringTemplate(
			StringTemplateContext strTmpCtx) throws ServletException {
		if (strTmpCtx == null) {
			throw new ServletException("StringTemplateContext is null");
		}
		
		if (strTmpCtx.getTemplateContent() != null) {
			StringTemplateLoader stl = getStrTmpt();
			stl.putTemplate(strTmpCtx.getTemplateName(),
					strTmpCtx.getTemplateContent());
			setStrTmpt(stl);
			//重写添加TemplateLoader
			log.debug("StringTemplateLoader成功添加模板'"
					+ strTmpCtx.getTemplateName() + "'");
		}
		
		return getStrTmpt();

	}

	/**
	 * 模板解析过程
	 * @throws ParseURLToTemplateException 
	 */
	private void process(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		
		StringTemplateLoader  strTmpt = getStrTmpt();
		if (strTmpt == null) {
			strTmpt = new StringTemplateLoader();
			setStrTmpt(strTmpt);
		}

		// 自定义类型StringTemplateContext,存放[模板名称]和[模板内容]
		StringTemplateContext strTmpCtx = null;
		try {
			strTmpCtx = reqUrlToModelCtx(req, strTmpt);
		} catch (ParseURLToTemplateException pe) {
			resp.sendError(HttpServletResponse.SC_NOT_FOUND);
			pe.printStackTrace();
		}

		//添加模板
		strTmpt = addStringTemplate(strTmpCtx);
		config.setTemplateLoader(strTmpt);

		Template template = config.getTemplate(strTmpCtx.getTemplateName());
		Object attrContentType = template.getCustomAttribute("content_type");
		if (attrContentType != null) {
			resp.setContentType(attrContentType.toString());
		} else {
			resp.setContentType(contentType + "; charset="
					+ template.getEncoding());
		}

		setBrowserCaching(resp);

		ServletContext servletContext = getServletContext();

		try {
			TemplateModel model = createModel(wrapper, servletContext, req,
					resp, strTmpCtx.getModel());

			template.process(model, resp.getWriter());

		} catch (TemplateException te) {
			ServletException e = new ServletException(
					"Error executing FreeMarker template", te);
			try {
				e.getClass().getMethod("initCause",
						new Class[] { Throwable.class }).invoke(e,
						new Object[] { te });
			} catch (Exception ex) {
				// Can't set init cause, we're probably running on a pre-1.4
				// JDK, oh well...
			}
			throw e;
		}
	}

	/**
	 * 创建Freemarker模板的model
	 */
	protected TemplateModel createModel(ObjectWrapper wrap,
			ServletContext servletContext, HttpServletRequest request,
			HttpServletResponse response, Map model)
			throws TemplateModelException {

		AllHttpScopesHashModel params = new AllHttpScopesHashModel(wrap,
				servletContext, request);

		// Create hash model wrapper for servlet context (the application)
		ServletContextHashModel servletContextModel =
					(ServletContextHashModel) servletContext
							.getAttribute(ATTR_APPLICATION_MODEL);

		if (servletContextModel == null) {
			servletContextModel = new ServletContextHashModel(this, wrap);

			servletContext.setAttribute(ATTR_APPLICATION_MODEL,
					servletContextModel);

			TaglibFactory taglibs = new TaglibFactory(servletContext);
			servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibs);
		}
		params.putUnlistedModel(KEY_APPLICATION, servletContextModel);
		params.putUnlistedModel(KEY_APPLICATION_PRIVATE, servletContextModel);
		params.putUnlistedModel(KEY_JSP_TAGLIBS, (TemplateModel) servletContext
				.getAttribute(ATTR_JSP_TAGLIBS_MODEL));

		// Create hash model wrapper for session
		HttpSessionHashModel sessionModel;
		HttpSession session = request.getSession();
		sessionModel = (HttpSessionHashModel) session
				.getAttribute(ATTR_SESSION_MODEL);
		if (sessionModel == null || sessionModel.isZombie()) {
			sessionModel = new HttpSessionHashModel(session, wrap);
			session.setAttribute(ATTR_SESSION_MODEL, sessionModel);
		}
		params.putUnlistedModel(KEY_SESSION, sessionModel);

		// Create hash model wrapper for request
		HttpRequestHashModel requestModel = (HttpRequestHashModel) request
				.getAttribute(ATTR_REQUEST_MODEL);
		if (requestModel == null || requestModel.getRequest() != request) {
			requestModel = new HttpRequestHashModel(request, response, wrap);
			request.setAttribute(ATTR_REQUEST_MODEL, requestModel);
			request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL,
					new HttpRequestParametersHashModel(request));
		}
		params.putUnlistedModel(KEY_REQUEST, requestModel);
		params.putUnlistedModel(KEY_REQUEST_PRIVATE, requestModel);

		// Create hash model wrapper for request parameters
		HttpRequestParametersHashModel requestParametersModel = 
							(HttpRequestParametersHashModel) request
							.getAttribute(ATTR_REQUEST_PARAMETERS_MODEL);

		params.putUnlistedModel(KEY_REQUEST_PARAMETERS, requestParametersModel);

		params.putAll(model);

		return params;
	}

	/**
	 * 需要有自类重写,返回需要的TemplateModelContext[templateName Model]
	 * 
	 * @return
	 * @throws ParseURLToTemplateException 
	 * @throws Exception 
	 */
	protected StringTemplateContext reqUrlToModelCtx(HttpServletRequest req,
			StringTemplateLoader tmLoader) throws ParseURLToTemplateException {

		// 需要子类去重写,完成封装数据
		return null;
	}

	/**
	 * If the parameter "nocache" was set to true, generate a set of headers
	 * that will advise the HTTP client not to cache the returned page.
	 */
	private void setBrowserCaching(HttpServletResponse res) {
		if (nocache) {
			// HTTP/1.1 + IE extensions
			res.setHeader("Cache-Control",
					"no-store, no-cache, must-revalidate, "
							+ "post-check=0, pre-check=0");
			// HTTP/1.0
			res.setHeader("Pragma", "no-cache");
			// Last resort for those that ignore all of the above
			res.setHeader("Expires", EXPIRATION_DATE);
		}
	}

	/**
	 * getter strTmpt
	 * @return
	 */
	private StringTemplateLoader getStrTmpt() {
		return (StringTemplateLoader) getServletContext().getAttribute(
		"stringTemplateLoader");
	}

	/**
	 * setter strTmpt
	 * @param strTmpt
	 */
	private void setStrTmpt(StringTemplateLoader strTmpt) {
		getServletContext().setAttribute("stringTemplateLoader", strTmpt);
	}
}

 

未完待续....就像这人世间错综复杂、乱七八糟的种种破事

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值