mvc resources handler initial commit
This commit is contained in:
@@ -30,6 +30,7 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport {
|
||||
public void init() {
|
||||
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.springframework.web.servlet.config;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
|
||||
import org.springframework.web.servlet.resources.ResourceHttpRequestHandler;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
|
||||
* {@code resources} element to register a {@link ResourceHttpRequestHandler}.
|
||||
* Will also register a {@link SimpleUrlHandlerMapping} for mapping resource requests, if necessary.
|
||||
* Will also register a {@link HttpRequestHandlerAdapter} if necessary.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0.4
|
||||
*/
|
||||
public class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
|
||||
|
||||
private static final String HANDLER_MAPPING_BEAN_NAME = "org.springframework.web.servlet.config.resourcesHandlerMapping";
|
||||
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
Object source = parserContext.extractSource(element);
|
||||
|
||||
registerHandlerAdapterIfNecessary(parserContext, source);
|
||||
BeanDefinition handlerMappingDef = registerHandlerMappingIfNecessary(parserContext, source);
|
||||
|
||||
String resourceDirectory = "/resources/";
|
||||
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
|
||||
resourceHandlerDef.setSource(source);
|
||||
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
resourceHandlerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, resourceDirectory);
|
||||
|
||||
Map<String, BeanDefinition> urlMap = getUrlMap(handlerMappingDef);
|
||||
String resourceRequestPath = "/resources/**";
|
||||
urlMap.put(resourceRequestPath, resourceHandlerDef);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void registerHandlerAdapterIfNecessary(ParserContext parserContext, Object source) {
|
||||
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
|
||||
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
|
||||
handlerAdapterDef.setSource(source);
|
||||
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
private BeanDefinition registerHandlerMappingIfNecessary(ParserContext parserContext, Object source) {
|
||||
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_BEAN_NAME)) {
|
||||
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
|
||||
handlerMappingDef.setSource(source);
|
||||
handlerMappingDef.getPropertyValues().add("order", "2");
|
||||
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
|
||||
return handlerMappingDef;
|
||||
}
|
||||
else {
|
||||
return parserContext.getRegistry().getBeanDefinition(HANDLER_MAPPING_BEAN_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, BeanDefinition> getUrlMap(BeanDefinition handlerMappingDef) {
|
||||
Map<String, BeanDefinition> urlMap;
|
||||
if (handlerMappingDef.getPropertyValues().contains("urlMap")) {
|
||||
urlMap = (Map<String, BeanDefinition>) handlerMappingDef.getPropertyValues().getPropertyValue("urlMap").getValue();
|
||||
}
|
||||
else {
|
||||
urlMap = new ManagedMap<String, BeanDefinition>();
|
||||
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
|
||||
}
|
||||
return urlMap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package org.springframework.web.servlet.resources;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.activation.FileTypeMap;
|
||||
import javax.activation.MimetypesFileTypeMap;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.HttpRequestHandler;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
|
||||
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
|
||||
|
||||
public class ResourceHttpRequestHandler implements HttpRequestHandler {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
|
||||
|
||||
private Resource resourceDirectory;
|
||||
|
||||
private int maxAge = 31556926;
|
||||
|
||||
private FileMediaTypeMap fileMediaTypeMap = new DefaultFileMediaTypeMap();
|
||||
|
||||
public ResourceHttpRequestHandler(Resource resourceDirectory) {
|
||||
Assert.notNull(resourceDirectory, "The resource directory may not be null");
|
||||
this.resourceDirectory = resourceDirectory;
|
||||
}
|
||||
|
||||
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
if (!"GET".equals(request.getMethod())) {
|
||||
throw new HttpRequestMethodNotSupportedException(request.getMethod(),
|
||||
new String[] {"GET"}, "ResourceHttpRequestHandler only supports GET requests");
|
||||
}
|
||||
List<Resource> resources = getResources(request);
|
||||
if (resources.size() == 0) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
boolean notModified = checkNotModified(resources, request, response);
|
||||
if (notModified) {
|
||||
return;
|
||||
}
|
||||
prepareResponse(resources, response);
|
||||
writeResponse(resources, response);
|
||||
}
|
||||
|
||||
private List<Resource> getResources(HttpServletRequest request) throws ServletException, IOException {
|
||||
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
|
||||
if (path == null) {
|
||||
throw new IllegalStateException("Required request attribute '" + HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set");
|
||||
}
|
||||
String[] resourceElements = path.split(",");
|
||||
if (resourceElements.length == 1 && resourceElements[0].length() == 0) {
|
||||
throw new NoSuchRequestHandlingMethodException(request);
|
||||
}
|
||||
List<Resource> resources = new ArrayList<Resource>();
|
||||
String[] dirAndFilename = splitDirectoryAndFilename(resourceElements[0]);
|
||||
String dir = dirAndFilename[0];
|
||||
String filename = dirAndFilename[1];
|
||||
Resource parent = dir != null ? this.resourceDirectory.createRelative(dir) : this.resourceDirectory;
|
||||
addResource(parent, filename, resources);
|
||||
if (resourceElements.length > 1) {
|
||||
for (int i = 1; i < resourceElements.length; i++) {
|
||||
addResource(parent, resourceElements[i], resources);
|
||||
}
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
private boolean checkNotModified(List<Resource> resources,HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
long lastModifiedTimestamp = -1;
|
||||
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
|
||||
for (Resource resource : resources) {
|
||||
long resourceLastModified = resource.lastModified();
|
||||
if (resourceLastModified > lastModifiedTimestamp) {
|
||||
lastModifiedTimestamp = resourceLastModified;
|
||||
}
|
||||
}
|
||||
boolean notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000);
|
||||
if (notModified) {
|
||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
} else {
|
||||
response.setDateHeader("Last-Modified", lastModifiedTimestamp);
|
||||
}
|
||||
return notModified;
|
||||
}
|
||||
|
||||
private void prepareResponse(List<Resource> resources, HttpServletResponse response) {
|
||||
MediaType mediaType = null;
|
||||
int contentLength = 0;
|
||||
for (Resource resource : resources) {
|
||||
try {
|
||||
File file = resource.getFile();
|
||||
if (mediaType == null) {
|
||||
mediaType = fileMediaTypeMap.getMediaType(file.getName());
|
||||
}
|
||||
contentLength += file.length();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
if (mediaType != null) {
|
||||
response.setContentType(mediaType.toString());
|
||||
}
|
||||
response.setContentLength(contentLength);
|
||||
if (this.maxAge > 0) {
|
||||
// HTTP 1.0 header
|
||||
response.setDateHeader("Expires", System.currentTimeMillis() + this.maxAge * 1000L);
|
||||
// HTTP 1.1 header
|
||||
response.setHeader("Cache-Control", "max-age=" + this.maxAge);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResponse(List<Resource> resources, HttpServletResponse response) throws IOException {
|
||||
for (Resource resource : resources) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = resource.getInputStream();
|
||||
int bytesRead = -1;
|
||||
byte[] buffer = new byte[4096];
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
response.getOutputStream().write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String[] splitDirectoryAndFilename(String firstResourceElement) {
|
||||
int index = firstResourceElement.lastIndexOf("/");
|
||||
String dir;
|
||||
if (index == -1) {
|
||||
dir = null;
|
||||
} else {
|
||||
dir = firstResourceElement.substring(0, index + 1);
|
||||
}
|
||||
String filename = firstResourceElement.substring(index + 1, firstResourceElement.length());
|
||||
return new String[] { dir, filename };
|
||||
}
|
||||
|
||||
private void addResource(Resource parent, String name, List<Resource> resources) throws IOException {
|
||||
if (name.length() > 0) {
|
||||
Resource resource = parent.createRelative(name);
|
||||
if (isAllowed(resource)) {
|
||||
resources.add(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAllowed(Resource resource) throws IOException {
|
||||
return resource.exists() && resource.getFile().isFile();
|
||||
}
|
||||
|
||||
// TODO promote to top-level and make reusable
|
||||
// TODO check ServletContext.getMimeType(String) first
|
||||
|
||||
public interface FileMediaTypeMap {
|
||||
MediaType getMediaType(String fileName);
|
||||
}
|
||||
|
||||
public static class DefaultFileMediaTypeMap implements FileMediaTypeMap {
|
||||
|
||||
private static final boolean jafPresent =
|
||||
ClassUtils.isPresent("javax.activation.FileTypeMap", ContentNegotiatingViewResolver.class.getClassLoader());
|
||||
|
||||
private boolean useJaf = true;
|
||||
|
||||
private ConcurrentMap<String, MediaType> mediaTypes = new ConcurrentHashMap<String, MediaType>();
|
||||
|
||||
public MediaType getMediaType(String filename) {
|
||||
String extension = StringUtils.getFilenameExtension(filename);
|
||||
if (!StringUtils.hasText(extension)) {
|
||||
return null;
|
||||
}
|
||||
extension = extension.toLowerCase(Locale.ENGLISH);
|
||||
MediaType mediaType = this.mediaTypes.get(extension);
|
||||
if (mediaType == null && useJaf && jafPresent) {
|
||||
mediaType = ActivationMediaTypeFactory.getMediaType(filename);
|
||||
if (mediaType != null) {
|
||||
this.mediaTypes.putIfAbsent(extension, mediaType);
|
||||
}
|
||||
}
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class to avoid hard-coded JAF dependency.
|
||||
*/
|
||||
private static class ActivationMediaTypeFactory {
|
||||
|
||||
private static final FileTypeMap fileTypeMap;
|
||||
|
||||
static {
|
||||
fileTypeMap = loadFileTypeMapFromContextSupportModule();
|
||||
}
|
||||
|
||||
private static FileTypeMap loadFileTypeMapFromContextSupportModule() {
|
||||
// see if we can find the extended mime.types from the context-support module
|
||||
Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types");
|
||||
if (mappingLocation.exists()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Loading Java Activation Framework FileTypeMap from " + mappingLocation);
|
||||
}
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = mappingLocation.getInputStream();
|
||||
return new MimetypesFileTypeMap(inputStream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Loading default Java Activation Framework FileTypeMap");
|
||||
}
|
||||
return FileTypeMap.getDefaultFileTypeMap();
|
||||
}
|
||||
|
||||
public static MediaType getMediaType(String fileName) {
|
||||
String mediaType = fileTypeMap.getContentType(fileName);
|
||||
return StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user