/*
* 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.util.internal.webpagelite;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.ObjectUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static com.alibaba.citrus.util.internal.webpagelite.ServletRequestContext.*;
import static com.alibaba.citrus.util.io.StreamUtil.*;
import static javax.servlet.http.HttpServletResponse.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.alibaba.citrus.util.templatelite.Template;
import com.alibaba.citrus.util.templatelite.TextWriter;
/**
* 一个简单的、利用templatelite.<code>Template</code>模板生成WEB页面及相关资源的工具类。
*
* @author Michael Zhou
*/
public abstract class RequestProcessor<RC extends RequestContext> implements PageComponentRegistry {
private final static String DEFAULT_CONTENT_TYPE = "application/octet-stream";
private final static Map<String, String> contentTypes;
private final Map<String, PageComponent> components = createTreeMap(new Comparator<String>() {
public int compare(String s1, String s2) {
int lenDiff = s2.length() - s1.length();
if (lenDiff != 0) {
return lenDiff; // 先按名称长度倒排序
} else {
return s1.compareTo(s2); // 再按字母顺序排序
}
}
});
static {
contentTypes = createHashMap();
contentTypes.put("css", "text/css");
contentTypes.put("js", "application/javascript");
contentTypes.put("html", "text/html");
contentTypes.put("htm", "text/html");
contentTypes.put("xml", "text/xml");
contentTypes.put("txt", "text/plain");
contentTypes.put("gif", "image/gif");
contentTypes.put("jpg", "image/jpeg");
contentTypes.put("png", "image/png");
contentTypes.put("ico", "image/x-icon");
}
/** 注册一个组件。 */
public void register(String componentPath, PageComponent component) {
assertNotNull(componentPath, "componentPath is null");
assertTrue(componentPath.length() == 0 || !componentPath.startsWith("/") && componentPath.endsWith("/"),
"invalid componentPath: %s", componentPath);
assertTrue(!components.containsKey(componentPath), "duplicated component: %s", componentPath);
components.put(componentPath, component);
}
/** 取得所有的componentPaths。 */
public String[] getComponentPaths() {
return components.keySet().toArray(new String[components.size()]);
}
/** 取得指定名称的组件。 */
public <PC extends PageComponent> PC getComponent(String componentPath, Class<PC> componentClass) {
componentPath = PageComponent.normalizeComponentPath(componentPath);
PageComponent component = assertNotNull(components.get(componentPath), "Component not found: %s", componentPath);
if (componentClass != null) {
return componentClass.cast(component);
} else {
@SuppressWarnings("unchecked")
PC pc = (PC) component;
return pc;
}
}
/** 处理请求。 */
public final void processRequest(final RC request) throws IOException {
final String resourceName = request.getResourceName();
// 先看看是不是css、js之类的资源文件,或者资源文件模板
URL tmpres = getRawResource(resourceName);
final boolean template;
if (tmpres == null) {
tmpres = getRawResource(resourceName + ".tpl");
template = true;
} else {
template = false;
}
final URL resource = tmpres;
if (resource != null && !resource.getPath().endsWith("/")) {
checkLastModified(request, getLastModifiedOfResource(request, resource, template), new Runnable() {
public void run() throws IOException {
if (beforeRenderingResource(request, resource, template)) {
renderResource(request, resource, template);
}
}
});
return;
}
// 查找resource
boolean found = resourceExists(resourceName);
if (!found && !resourceName.endsWith("/")) {
String indexPage = resourceName + "/";
if (resourceExists(indexPage)) {
request.redirectTo(request.getResourceURL(indexPage)); // 重定向到目录的正规形式
return;
}
}
// 报错:资源找不到
if (!found) {
request.resourceNotFound(resourceName);
return;
}
// 渲染页面
checkLastModified(request, getLastModifiedOfPage(request, resourceName), new Runnable() {
public void run() throws IOException {
if (beforeRenderingPage(request, resourceName)) {
renderPage(request, resourceName);
}
}
});
}
private void checkLastModified(RC request, long lastModified, Runnable runner) throws IOException {
ServletRequestContext servletRequest = getServletRequestContext(request);
// 假如:
// 1. request为servlet request;
// 并且,lastModified >= 0;
// 并且,GET方法;
// 2. 或者,lastModified > ifModifiedSince
// 执行runner
if (lastModified < 0 || servletRequest == null
|| !"get".equalsIgnoreCase(servletRequest.getRequest().getMethod())) {
runner.run();
} else {
long ifModifiedSince = servletRequest.getRequest().getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified / 1000 * 1000) {
if (lastModified >= 0) {
servletRequest.getResponse().setDateHeader("Last-Modified", lastModified);
}
runner.run();
} else {
servletRequest.getResponse().setStatus(SC_NOT_MODIFIED);
}
}
}
/** 判断资源是否存在。 */
protected abstract boolean resourceExists(String resourceName);
/**
* 查找指定名称的资源URL,如果没找到,就返回<code>null</code>。
* <p>
* 先查找所有注册的component,再查找当前类同级目录的classpath。
* </p>
*/
private URL getRawResource(String resourceName) {
for (String componentPath : components.keySet()) {
if (resourceName.startsWith(componentPath)) {
String componentResourceName = resourceName.substring(componentPath.length());
URL resource = components.get(componentPath).getClass().getResource(componentResourceName);
if (resource == null) {
resource = getClass().getResource(componentResourceName);
}
return resource;
}
}
URL resource = null;
Set<String> visitedPackages = createHashSet();
for (Class<?> processorClass = getClass(); processorClass != null
&& RequestProcessor.class.isAssignableFrom(processorClass); processorClass = processorClass
.getSuperclass()) {
String processorPackage = processorClass.getPackage().getName();
if (visitedPackages.contains(processorPackage)) {
continue;
}
visitedPackages.add(processorPackage);
resource = processorClass.getResource(resourceName);
if (resource != null) {
break;
}
}
return resource;
}
protected long getLastModifiedOfPage(RC request, String resourceName) throws IOException {
return -1;
}
protected long getLastModifiedOfResource(RC request, URL resource, boolean template) throws IOException {
if (!template) {
return resource.openConnection().getLastModified();
}
return -1;
}
protected boolean beforeRenderingPage(RC request, String resourceName) throws IOException {
return true;
}
protected boolean beforeRenderingResource(RC request, URL resource, boolean template) throws IOException {
return true;
}
/** 渲染页面。 */
protected abstract void renderPage(RC request, String resourceName) throws IOException;
/** 渲染css、js之类的资源文件,或者资源文件模板。 */
private void renderResource(final RC request, URL resource, boolean template) throws IOException {
String resourceName = request.getResourceName();
int extIndex = resourceName.indexOf(".", resourceName.lastIndexOf("/") + 1);
String ext = extIndex > 0 ? resourceName.substring(extIndex + 1) : null;
String contentType = defaultIfNull(contentTypes.get(ext), DEFAULT_CONTENT_TYPE);
if (!template) {
io(resource.openStream(), request.getOutputStream(contentType), true, true);
} else {
Template tpl = new Template(resource);
PrintWriter out = request.getWriter(contentType);
// 对于模板文件中,${url:relativeUrl}将被转换成完整的URL
tpl.accept(new TextWriter<PrintWriter>(out) {
@SuppressWarnings("unused")
public void visitUrl(String relativeUrl) {
out().print(request.getResourceURL(relativeUrl));
}
});
out.flush();
}
}
/** 取得所有component的资源文件。 */
protected List<String> getComponentResources(String ext) {
List<String> names = createLinkedList();
ext = trimToEmpty(ext);
if (!ext.startsWith(".")) {
ext = "." + ext;
}
for (PageComponent component : components.values()) {
String name = component.getComponentName() + ext;
if (component.getClass().getResource(name) != null) {
names.add(component.getComponentPath() + name);
}
}
return names;
}
private interface Runnable {
void run() throws IOException;
}
}