package ddth.dasp.hetty.mvc;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import ddth.dasp.framework.osgi.IServiceAutoRegister;
import ddth.dasp.framework.resource.IResourceLoader;
import ddth.dasp.hetty.IRequestActionHandler;
import ddth.dasp.hetty.message.IRequest;
import ddth.dasp.hetty.message.IResponse;
import ddth.dasp.hetty.message.protobuf.ResponseUtils;
import ddth.dasp.hetty.qnt.ITopicPublisher;
/**
* An {@link IRequestActionHandler} to server static resources inside bundle.
*
* @author Thanh Ba Nguyen <btnguyen2k@gmail.com>
*/
public class BundleStaticResourceActionHandler implements IRequestActionHandler,
IServiceAutoRegister {
private static Map<String, String> DEFAULT_MIMETYPES = new HashMap<String, String>();
static {
DEFAULT_MIMETYPES.put(".gif", "image/gif");
DEFAULT_MIMETYPES.put(".jpg", "image/jpeg");
DEFAULT_MIMETYPES.put(".jpeg", "image/jpeg");
DEFAULT_MIMETYPES.put(".png", "image/png");
DEFAULT_MIMETYPES.put(".css", "text/css");
DEFAULT_MIMETYPES.put(".js", "text/javascript");
DEFAULT_MIMETYPES.put(".htm", "text/html");
DEFAULT_MIMETYPES.put(".html", "text/html");
DEFAULT_MIMETYPES.put(".txt", "text/txt");
}
private IResourceLoader resourceLoader;
private String module, action, prefix;
private String defaultMimeType = "application/octet-stream";
private Map<String, String> mimeTypes = Collections.unmodifiableMap(DEFAULT_MIMETYPES);
private Properties properties;
/**
* {@inheritDoc}
*/
@Override
public String getClassName() {
return IRequestActionHandler.class.getName();
}
/**
* {@inheritDoc}
*/
@Override
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setDefaultMimeType(String defaultMimeType) {
this.defaultMimeType = defaultMimeType;
}
protected String getDefaultMimeType() {
return defaultMimeType;
}
public Map<String, String> getMimeTypes() {
return mimeTypes;
}
public void setMimeTypes(Map<String, String> mimeTypes) {
this.mimeTypes = mimeTypes;
}
public void setResourceLoader(IResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
protected String getModule() {
return module;
}
public void setModule(String module) {
this.module = module;
}
protected String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
protected String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
protected String buildPath(String path) {
return !StringUtils.isBlank(prefix) ? prefix + path : path;
}
protected String detectMimeType(String path) {
for (Entry<String, String> mimeType : mimeTypes.entrySet()) {
if (path.endsWith(mimeType.getKey())) {
return mimeType.getValue();
}
}
return defaultMimeType;
}
/**
* {@inheritDoc}
*/
@Override
public void handleRequest(IRequest request, ITopicPublisher topicPublisher, String topicName)
throws Exception {
String path = request.getPath();
if (module != null && path.startsWith("/" + module)) {
path = path.substring(module.length() + 1);
}
if (action != null && path.startsWith("/" + action)) {
path = path.substring(action.length() + 1);
}
path = path.replaceAll("^\\/+", "");
path = buildPath(path);
IResponse response = null;
if (resourceLoader.resourceExists(path)) {
byte[] resourceContent = resourceLoader.loadResourceAsBinary(path);
String etag = DigestUtils.md5Hex(resourceContent);
String headerIfNoneMatch = request.getHeader("If-None-Match");
if (headerIfNoneMatch != null && headerIfNoneMatch.equals(etag)) {
response = ResponseUtils.response304(request);
} else {
String resourceMimeType = detectMimeType(path);
if (resourceMimeType != null && resourceMimeType.startsWith("text/")) {
resourceMimeType += "; charset=utf-8";
}
response = ResponseUtils.response200(request, resourceContent, resourceMimeType);
}
// cache headers
response.addHeader("ETag", etag).addHeader("Cache-control", "private")
.addHeader("Max-Age", 1 * 3600);
long resourceTimestamp = resourceLoader.getLastModified(path);
if (resourceTimestamp > 0) {
response.addHeader("Last-Modified", new Date(resourceTimestamp));
// 1 hour in millisecs
response.addHeader("Expires", new Date(System.currentTimeMillis() + 1 * 3600000L));
}
} else {
response = ResponseUtils.response404(request);
}
topicPublisher.publish(topicName, response);
}
}