/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.citrus.service.jsp.impl;
import static com.alibaba.citrus.springext.util.SpringExtUtil.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.FileUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URL;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.citrus.service.AbstractService;
import com.alibaba.citrus.service.jsp.JspEngine;
import com.alibaba.citrus.service.template.TemplateContext;
import com.alibaba.citrus.service.template.TemplateException;
import com.alibaba.citrus.service.template.TemplateNotFoundException;
import com.alibaba.citrus.util.ToStringBuilder;
import com.alibaba.citrus.util.ToStringBuilder.MapBuilder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
/**
* Jsp模板引擎的实现。
*
* @author Michael Zhou
*/
public class JspEngineImpl extends AbstractService<JspEngine> implements JspEngine, ResourceLoaderAware,
InitializingBean {
private final ServletContext servletContext;
private final HttpServletRequest request;
private final HttpServletResponse response;
private ResourceLoader resourceLoader;
private String contextRoot;
private String path;
/**
* 创建jsp引擎。
* <p>
* 需要注意的是,用来创建jsp引擎的参数必须是“全局”作用域的,而不是“request”作用域的。这一点可由
* <code>RequestContextChainingService</code>来保证。
* </p>
*/
public JspEngineImpl(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response) {
this.servletContext = assertNotNull(servletContext, "servletContext");
this.request = assertProxy(assertNotNull(request, "request"));
this.response = assertProxy(assertNotNull(response, "response"));
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void setPath(String path) {
this.path = trimToNull(path);
}
@Override
protected void init() throws Exception {
assertNotNull(resourceLoader, "resourceLoader");
// 取得搜索路径(相对)。
if (path == null) {
path = "/templates";
}
// 规格化路径,以"/"结尾。
path = normalizeAbsolutePath(path + "/");
// 取得webroot根目录的URL
URL url = servletContext.getResource("/");
if (url != null) {
contextRoot = url.toExternalForm();
} else {
// 如果取不到webroot根目录,则试着取得web.xml的URL,以此为基准,计算相对于webroot的URL。
url = servletContext.getResource("/WEB-INF/web.xml");
if (url != null) {
String urlstr = url.toExternalForm();
if (urlstr.endsWith("/WEB-INF/web.xml")) {
contextRoot = urlstr.substring(0, urlstr.length() - "WEB-INF/web.xml".length());
}
}
}
if (contextRoot == null) {
throw new IllegalArgumentException("Could not find WEBROOT. Are you sure you are in webapp?");
}
if (!contextRoot.endsWith("/")) {
contextRoot += "/";
}
if (getLogger().isDebugEnabled()) {
MapBuilder mb = new MapBuilder();
mb.append("path", path);
mb.append("contextRoot", contextRoot);
getLogger().debug(new ToStringBuilder().append("Initialized JSP Template Engine").append(mb).toString());
}
}
/**
* 取得默认的模板名后缀列表。
* <p>
* 当<code>TemplateService</code>没有指定到当前engine的mapping时,将取得本方法所返回的后缀名列表。
* </p>
*/
public String[] getDefaultExtensions() {
return new String[] { "jsp", "jspx" };
}
/** 判定模板是否存在。 */
public boolean exists(String templateName) {
return getPathWithinServletContextInternal(templateName) != null;
}
/**
* 渲染模板,并以字符串的形式取得渲染的结果。
*
* @param template 模板名
* @param context template context
* @return 模板渲然的结果字符串
* @throws TemplateException 渲染失败
*/
public String getText(String template, TemplateContext context) throws TemplateException, IOException {
// 取得JSP相对于webapp的路径。
String relativeTemplateName = getPathWithinServletContext(template);
// 取得JSP的RequestDispatcher。
RequestDispatcher dispatcher = servletContext.getRequestDispatcher(relativeTemplateName);
if (dispatcher == null) {
throw new TemplateNotFoundException("Could not dispatch to JSP template " + template);
}
try {
// 将template context适配到request
HttpServletRequest requestWrapper = new TemplateContextAdapter(request, context);
// 避免在jsp中修改content type、locale和charset,这应该在模板外部来控制
HttpServletResponse responseWrapper = new JspResponse(response);
dispatcher.include(requestWrapper, responseWrapper);
} catch (ServletException e) {
throw new TemplateException(e);
}
return "";
}
/** 渲染模板,并将渲染的结果送到字节输出流中。 */
public void writeTo(String templateName, TemplateContext context, OutputStream ostream) throws TemplateException,
IOException {
getText(templateName, context);
}
/** 渲染模板,并将渲染的结果送到字符输出流中。 */
public void writeTo(String templateName, TemplateContext context, Writer writer) throws TemplateException,
IOException {
getText(templateName, context);
}
/**
* 取得相对于servletContext的模板路径。这个路径可被
* <code>javax.servlet.RequestDispatcher</code> 使用,以便找到jsp的实例。
*/
public String getPathWithinServletContext(String templateName) throws TemplateNotFoundException {
String path = getPathWithinServletContextInternal(templateName);
if (path == null) {
throw new TemplateNotFoundException("Template " + templateName + " not found");
}
return path;
}
private String getPathWithinServletContextInternal(String templateName) {
assertInitialized();
String resourceName = path + (templateName.startsWith("/") ? templateName.substring(1) : templateName);
Resource resource = resourceLoader.getResource(resourceName);
String path = null;
if (resource != null && resource.exists()) {
try {
String url = resource.getURL().toExternalForm();
if (url.startsWith(contextRoot)) {
path = url.substring(contextRoot.length() - 1); // 保留slash:/
}
} catch (IOException e) {
// ignore
}
}
return path;
}
@Override
public String toString() {
return "JspEngine[" + path + "]";
}
}