27 changed files with 1417 additions and 28 deletions
@ -0,0 +1,50 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public abstract class AbstractResourceResolver implements ResourceResolver { |
||||||
|
|
||||||
|
@Override |
||||||
|
public final Resource resolve(HttpServletRequest request, String path, |
||||||
|
List<Resource> locations, ResourceResolverChain chain) { |
||||||
|
|
||||||
|
Resource candidate = chain.next(this).resolve(request, path, locations, chain); |
||||||
|
|
||||||
|
return resolveInternal(request, path, locations, chain, candidate); |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract Resource resolveInternal(HttpServletRequest request, String path, |
||||||
|
List<Resource> locations, ResourceResolverChain chain, Resource resolved); |
||||||
|
|
||||||
|
@Override |
||||||
|
public String resolveUrl(String resourcePath, List<Resource> locations, |
||||||
|
ResourceResolverChain chain) { |
||||||
|
return chain.next(this).resolveUrl(resourcePath, locations, chain); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,111 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.apache.commons.logging.Log; |
||||||
|
import org.apache.commons.logging.LogFactory; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class DefaultResourceResolverChain implements ResourceResolverChain{ |
||||||
|
|
||||||
|
private static final ResourceResolver DEFAULT_RESOLVER = new PathMappingResourceResolver(); |
||||||
|
|
||||||
|
private final List<ResourceResolver> resolvers; |
||||||
|
|
||||||
|
private List<ResourceTransformer> transformers = new ArrayList<ResourceTransformer>(); |
||||||
|
|
||||||
|
public DefaultResourceResolverChain(List<ResourceResolver> resolvers, List<ResourceTransformer> transformers) { |
||||||
|
this.resolvers = resolvers; |
||||||
|
this.resolvers.add(DEFAULT_RESOLVER); |
||||||
|
this.transformers = transformers; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ResourceResolver next(ResourceResolver current) { |
||||||
|
return this.resolvers.get(this.resolvers.indexOf(current) + 1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Resource resolveAndTransform(HttpServletRequest request, String path, |
||||||
|
List<Resource> locations) throws IOException{ |
||||||
|
Resource resolved = this.resolvers.get(0).resolve(request, path, locations, this); |
||||||
|
return resolved != null ? applyTransformers(request, resolved) : resolved; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String resolveUrl(String resourcePath, List<Resource> locations) { |
||||||
|
return this.resolvers.get(0).resolveUrl(resourcePath, locations, this); |
||||||
|
} |
||||||
|
|
||||||
|
protected Resource applyTransformers(HttpServletRequest request, Resource resource) throws IOException{ |
||||||
|
for (ResourceTransformer transformer : transformers) { |
||||||
|
if (transformer.handles(request, resource)) { |
||||||
|
return applyTransformers(request, transformer.transform(resource)); |
||||||
|
} |
||||||
|
} |
||||||
|
return resource; |
||||||
|
} |
||||||
|
|
||||||
|
private static class PathMappingResourceResolver implements ResourceResolver { |
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(PathMappingResourceResolver.class); |
||||||
|
|
||||||
|
@Override |
||||||
|
public Resource resolve(HttpServletRequest request, String path, List<Resource> locations, ResourceResolverChain chain) { |
||||||
|
for (Resource location : locations) { |
||||||
|
try { |
||||||
|
if (logger.isDebugEnabled()) { |
||||||
|
logger.debug("Trying relative path [" + path + "] against base location: " + location); |
||||||
|
} |
||||||
|
Resource resource = location.createRelative(path); |
||||||
|
if (resource.exists() && resource.isReadable()) { |
||||||
|
if (logger.isDebugEnabled()) { |
||||||
|
logger.debug("Found matching resource: " + resource); |
||||||
|
} |
||||||
|
return resource; |
||||||
|
} |
||||||
|
else if (logger.isTraceEnabled()) { |
||||||
|
logger.trace("Relative resource doesn't exist or isn't readable: " + resource); |
||||||
|
} |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
logger.debug("Failed to create relative resource - trying next resource location", ex); |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String resolveUrl(String resourcePath, List<Resource> locations, ResourceResolverChain chain) { |
||||||
|
if (resolve(null, resourcePath, locations, chain) != null) { |
||||||
|
return resourcePath; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public interface EncodedResource extends Resource { |
||||||
|
|
||||||
|
public String getEncoding(); |
||||||
|
} |
||||||
@ -0,0 +1,117 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FilenameFilter; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.apache.commons.logging.Log; |
||||||
|
import org.apache.commons.logging.LogFactory; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class ExtensionMappingResourceResolver extends AbstractResourceResolver { |
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(getClass()); |
||||||
|
|
||||||
|
private final boolean compareTimeStamp; |
||||||
|
|
||||||
|
public ExtensionMappingResourceResolver() { |
||||||
|
this.compareTimeStamp = false; |
||||||
|
} |
||||||
|
|
||||||
|
public ExtensionMappingResourceResolver(boolean compareTimeStamp) { |
||||||
|
this.compareTimeStamp = compareTimeStamp; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Resource resolveInternal(HttpServletRequest request, String path, |
||||||
|
List<Resource> locations, ResourceResolverChain chain, Resource resolved) { |
||||||
|
if (resolved != null && !compareTimeStamp) { |
||||||
|
return resolved; |
||||||
|
} |
||||||
|
|
||||||
|
for (Resource location : locations) { |
||||||
|
String baseFilename = StringUtils.getFilename(path); |
||||||
|
|
||||||
|
try { |
||||||
|
Resource basePath = location.createRelative(StringUtils.delete(path, baseFilename)); |
||||||
|
if (basePath.getFile().isDirectory()) { |
||||||
|
for (String fileName : basePath.getFile().list(new ExtensionFilter(baseFilename))) { |
||||||
|
//Always use the first match
|
||||||
|
Resource matched = basePath.createRelative(fileName); |
||||||
|
if (resolved == null || matched.lastModified() > resolved.lastModified()) { |
||||||
|
return matched; |
||||||
|
} else { |
||||||
|
return resolved; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
catch (IOException e) { |
||||||
|
this.logger.trace("Error occurred locating resource based on file extension mapping", e); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
return resolved; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String resolveUrl(String resourcePath, List<Resource> locations, |
||||||
|
ResourceResolverChain chain) { |
||||||
|
String resolved = super.resolveUrl(resourcePath, locations, chain); |
||||||
|
if (StringUtils.hasText(resolved)) { |
||||||
|
return resolved; |
||||||
|
} |
||||||
|
|
||||||
|
Resource mappedResource = resolveInternal(null, resourcePath, locations, chain, null); |
||||||
|
if (mappedResource != null) { |
||||||
|
return resourcePath; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static final class ExtensionFilter implements FilenameFilter{ |
||||||
|
|
||||||
|
private final String baseFilename; |
||||||
|
private final String baseExtension; |
||||||
|
private final int baseExtLen; |
||||||
|
|
||||||
|
|
||||||
|
public ExtensionFilter(String baseFilename) { |
||||||
|
this.baseFilename = baseFilename; |
||||||
|
this.baseExtension = "." + StringUtils.getFilenameExtension(baseFilename); |
||||||
|
this.baseExtLen = this.baseExtension.length(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean accept(File dir, String name) { |
||||||
|
return name.contains(baseExtension) && baseFilename.equals(name.substring(0, name.lastIndexOf(baseExtension) + this.baseExtLen)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,116 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.List; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.apache.commons.logging.Log; |
||||||
|
import org.apache.commons.logging.LogFactory; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.util.DigestUtils; |
||||||
|
import org.springframework.util.FileCopyUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class FingerprintingResourceResolver extends AbstractResourceResolver { |
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(getClass()); |
||||||
|
|
||||||
|
private Pattern pattern = Pattern.compile("-(\\S*)\\."); |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Resource resolveInternal(HttpServletRequest request, String path, List<Resource> locations, |
||||||
|
ResourceResolverChain chain, Resource resolved) { |
||||||
|
//First try the resolved full path, in case resource has been written that way to disk at build-time
|
||||||
|
//or the resource is requested without fingerprint
|
||||||
|
if (resolved != null) { |
||||||
|
return resolved; |
||||||
|
} |
||||||
|
|
||||||
|
//Now try extracting and matching the hash for dev mode
|
||||||
|
String hash = extractHash(path); |
||||||
|
String simplePath = !StringUtils.isEmpty(hash) ? StringUtils.delete(path, "-" + hash) : path; |
||||||
|
Resource baseResource = chain.next(this).resolve(request, simplePath, locations, chain); |
||||||
|
|
||||||
|
if (StringUtils.isEmpty(hash) || baseResource == null) { |
||||||
|
return baseResource; |
||||||
|
} |
||||||
|
|
||||||
|
String candidateHash = calculateHash(baseResource); |
||||||
|
|
||||||
|
if (candidateHash.equals(hash)) { |
||||||
|
this.logger.debug("Fingerprint match succeeded."); |
||||||
|
return baseResource; |
||||||
|
} else { |
||||||
|
this.logger.debug("Potential resource found, but fingerprint doesn't match."); |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String resolveUrl(String resourcePath, List<Resource> locations, |
||||||
|
ResourceResolverChain chain) { |
||||||
|
//TODO - Consider caching here for better efficiency
|
||||||
|
String baseUrl = chain.next(this).resolveUrl(resourcePath, locations, chain); |
||||||
|
if (StringUtils.hasText(baseUrl)) { |
||||||
|
Resource original = chain.next(this).resolve(null, resourcePath, locations, chain); |
||||||
|
String hash = calculateHash(original); |
||||||
|
return StringUtils.stripFilenameExtension(baseUrl) + "-" + hash + "." + StringUtils.getFilenameExtension(baseUrl); |
||||||
|
} |
||||||
|
return baseUrl; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param candidate |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private String calculateHash(Resource resource) { |
||||||
|
try { |
||||||
|
byte[] content = FileCopyUtils.copyToByteArray(resource.getInputStream()); |
||||||
|
return DigestUtils.md5DigestAsHex(content); |
||||||
|
} |
||||||
|
catch (IOException e) { |
||||||
|
this.logger.error("Failed to calculate hash on resource " + resource.toString()); |
||||||
|
return ""; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param path |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private String extractHash(String path) { |
||||||
|
Matcher matcher = pattern.matcher(path); |
||||||
|
if (matcher.find()) { |
||||||
|
this.logger.debug("Found fingerprint in path: " + matcher.group(1)); |
||||||
|
String match = matcher.group(1); |
||||||
|
return match.contains("-") ? match.substring(match.lastIndexOf("-") + 1) : match; |
||||||
|
} else { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,135 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.net.URI; |
||||||
|
import java.net.URL; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.apache.commons.logging.Log; |
||||||
|
import org.apache.commons.logging.LogFactory; |
||||||
|
import org.springframework.core.io.AbstractResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class GzipResourceResolver extends AbstractResourceResolver { |
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(getClass()); |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Resource resolveInternal(HttpServletRequest request, String path, |
||||||
|
List<Resource> locations, ResourceResolverChain chain, Resource resolved) { |
||||||
|
|
||||||
|
if (!isGzipAccepted(request) || resolved == null) { |
||||||
|
return resolved; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
Resource gzipped = new GzippedResource(resolved); |
||||||
|
if (gzipped.exists()) { |
||||||
|
return gzipped; |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
this.logger.trace("Error occurred locating gzipped resource", e); |
||||||
|
} |
||||||
|
return resolved; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param request |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean isGzipAccepted(HttpServletRequest request) { |
||||||
|
String val = request.getHeader("Accept-Encoding"); |
||||||
|
return val != null && val.toLowerCase().contains("gzip"); |
||||||
|
} |
||||||
|
|
||||||
|
private static final class GzippedResource extends AbstractResource implements EncodedResource { |
||||||
|
|
||||||
|
private final Resource original; |
||||||
|
|
||||||
|
private final Resource gzipped; |
||||||
|
|
||||||
|
public GzippedResource(Resource original) throws IOException { |
||||||
|
this.original = original; |
||||||
|
this.gzipped = original.createRelative(original.getFilename()+".gz"); |
||||||
|
} |
||||||
|
|
||||||
|
public InputStream getInputStream() throws IOException { |
||||||
|
return gzipped.getInputStream(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean exists() { |
||||||
|
return gzipped.exists(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isReadable() { |
||||||
|
return gzipped.isReadable(); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isOpen() { |
||||||
|
return gzipped.isOpen(); |
||||||
|
} |
||||||
|
|
||||||
|
public URL getURL() throws IOException { |
||||||
|
return gzipped.getURL(); |
||||||
|
} |
||||||
|
|
||||||
|
public URI getURI() throws IOException { |
||||||
|
return gzipped.getURI(); |
||||||
|
} |
||||||
|
|
||||||
|
public File getFile() throws IOException { |
||||||
|
return gzipped.getFile(); |
||||||
|
} |
||||||
|
|
||||||
|
public long contentLength() throws IOException { |
||||||
|
return gzipped.contentLength(); |
||||||
|
} |
||||||
|
|
||||||
|
public long lastModified() throws IOException { |
||||||
|
return gzipped.lastModified(); |
||||||
|
} |
||||||
|
|
||||||
|
public Resource createRelative(String relativePath) throws IOException { |
||||||
|
return gzipped.createRelative(relativePath); |
||||||
|
} |
||||||
|
|
||||||
|
public String getFilename() { |
||||||
|
return original.getFilename(); |
||||||
|
} |
||||||
|
|
||||||
|
public String getDescription() { |
||||||
|
return gzipped.getDescription(); |
||||||
|
} |
||||||
|
|
||||||
|
public String getEncoding() { |
||||||
|
return "gzip"; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.lesscss.LessCompiler; |
||||||
|
import org.lesscss.LessException; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class LessResourceTransformer implements ResourceTransformer { |
||||||
|
|
||||||
|
private static final String LESS_EXT = "less"; |
||||||
|
|
||||||
|
private final LessCompiler compiler = new LessCompiler(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public Resource transform(Resource original) throws IOException { |
||||||
|
TransformedResource transformed; |
||||||
|
try { |
||||||
|
String content = ""; |
||||||
|
if (original instanceof TransformedResource) { |
||||||
|
content = ((TransformedResource) original).getContentAsString(); |
||||||
|
} else { |
||||||
|
content = compiler.compile(original.getFile()); |
||||||
|
} |
||||||
|
transformed = new TransformedResource(original.getFilename() |
||||||
|
.replace("."+LESS_EXT, ""), content.getBytes("UTF-8"), original.lastModified()); |
||||||
|
} |
||||||
|
catch (LessException le) { |
||||||
|
//TODO - Nicely print out the compilation error
|
||||||
|
le.printStackTrace(); |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return transformed; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean handles(HttpServletRequest request, Resource original) { |
||||||
|
return LESS_EXT.equals(StringUtils.getFilenameExtension(original.getFilename())); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public interface ResourceResolver { |
||||||
|
|
||||||
|
public Resource resolve(HttpServletRequest request, String path, List<Resource> locations, ResourceResolverChain chain); |
||||||
|
|
||||||
|
public String resolveUrl(String resourcePath, List<Resource> locations, |
||||||
|
ResourceResolverChain chain); |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public interface ResourceResolverChain { |
||||||
|
|
||||||
|
public Resource resolveAndTransform(HttpServletRequest request, String path, List<Resource> locations) |
||||||
|
throws IOException; |
||||||
|
|
||||||
|
public ResourceResolver next(ResourceResolver current); |
||||||
|
|
||||||
|
public String resolveUrl(String resourcePath, List<Resource> locations); |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* More than meets the eye. |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public interface ResourceTransformer { |
||||||
|
|
||||||
|
public Resource transform(Resource original) throws IOException; |
||||||
|
|
||||||
|
public boolean handles(HttpServletRequest request, Resource original); |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,91 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
import javax.servlet.FilterChain; |
||||||
|
import javax.servlet.ServletException; |
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
import javax.servlet.http.HttpServletResponseWrapper; |
||||||
|
|
||||||
|
import org.springframework.web.context.WebApplicationContext; |
||||||
|
import org.springframework.web.context.support.WebApplicationContextUtils; |
||||||
|
import org.springframework.web.filter.OncePerRequestFilter; |
||||||
|
import org.springframework.web.util.UrlPathHelper; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class ResourceUrlEncodingFilter extends OncePerRequestFilter { |
||||||
|
|
||||||
|
private ResourceUrlMapper mapper; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void doFilterInternal(HttpServletRequest request, |
||||||
|
HttpServletResponse response, FilterChain filterChain) |
||||||
|
throws ServletException, IOException { |
||||||
|
filterChain.doFilter(request, new ResourceUrlResponseWrapper(request, response)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void initFilterBean() throws ServletException { |
||||||
|
WebApplicationContext appContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); |
||||||
|
this.mapper = appContext.getBean(ResourceUrlMapper.class); |
||||||
|
} |
||||||
|
|
||||||
|
private class ResourceUrlResponseWrapper extends HttpServletResponseWrapper { |
||||||
|
|
||||||
|
private final UrlPathHelper pathHelper = new UrlPathHelper(); |
||||||
|
|
||||||
|
private String pathPrefix; |
||||||
|
|
||||||
|
private ResourceUrlResponseWrapper(HttpServletRequest request, HttpServletResponse wrapped) { |
||||||
|
super(wrapped); |
||||||
|
|
||||||
|
this.pathPrefix = pathHelper.getContextPath(request); |
||||||
|
String servletPath = pathHelper.getServletPath(request); |
||||||
|
String appPath = pathHelper.getPathWithinApplication(request); |
||||||
|
//This accounts for the behavior when servlet is mapped to "/"
|
||||||
|
if (!servletPath.equals(appPath)) { |
||||||
|
this.pathPrefix += pathHelper.getServletPath(request); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String encodeURL(String url) { |
||||||
|
if(url.startsWith(pathPrefix)) { |
||||||
|
String relativeUrl = url.replaceFirst(pathPrefix, ""); |
||||||
|
if (!relativeUrl.startsWith("/")) { |
||||||
|
relativeUrl = "/" + relativeUrl; |
||||||
|
} |
||||||
|
if (mapper.isResourceUrl(relativeUrl)) { |
||||||
|
String resourceUrl = mapper.getUrlForResource(relativeUrl); |
||||||
|
if (resourceUrl != null) { |
||||||
|
return resourceUrl; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return super.encodeURL(url); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,104 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
|
||||||
|
import org.springframework.beans.BeansException; |
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor; |
||||||
|
import org.springframework.context.ApplicationListener; |
||||||
|
import org.springframework.context.event.ContextRefreshedEvent; |
||||||
|
import org.springframework.core.OrderComparator; |
||||||
|
import org.springframework.util.AntPathMatcher; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
import org.springframework.util.PathMatcher; |
||||||
|
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class ResourceUrlMapper implements BeanPostProcessor, ApplicationListener<ContextRefreshedEvent>{ |
||||||
|
|
||||||
|
private final Map<String, ResourceHttpRequestHandler> handlers = new LinkedHashMap<String, ResourceHttpRequestHandler>(); |
||||||
|
|
||||||
|
private final List<SimpleUrlHandlerMapping> mappings = new ArrayList<SimpleUrlHandlerMapping>(); |
||||||
|
|
||||||
|
private final PathMatcher matcher = new AntPathMatcher(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object postProcessBeforeInitialization(Object bean, String beanName) |
||||||
|
throws BeansException { |
||||||
|
return bean; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object postProcessAfterInitialization(Object bean, String beanName) |
||||||
|
throws BeansException { |
||||||
|
if (ClassUtils.isAssignableValue(SimpleUrlHandlerMapping.class, bean)) { |
||||||
|
SimpleUrlHandlerMapping mapping = (SimpleUrlHandlerMapping) bean; |
||||||
|
for(Entry<String, ?> mappingEntry : mapping.getUrlMap().entrySet()) { |
||||||
|
Object val = mappingEntry.getValue(); |
||||||
|
if (val instanceof ResourceHttpRequestHandler) { |
||||||
|
this.mappings.add(mapping); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return bean; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onApplicationEvent(ContextRefreshedEvent event) { |
||||||
|
OrderComparator.sort(this.mappings); |
||||||
|
for (SimpleUrlHandlerMapping mapping : mappings) { |
||||||
|
for(Entry<String, ?> mappingEntry : mapping.getUrlMap().entrySet()) { |
||||||
|
Object val = mappingEntry.getValue(); |
||||||
|
this.handlers.put(mappingEntry.getKey(), (ResourceHttpRequestHandler) val); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public String getUrlForResource(String resourcePath) { |
||||||
|
for (Entry<String, ResourceHttpRequestHandler> mapping : this.handlers.entrySet()) { |
||||||
|
if (matcher.match(mapping.getKey(), resourcePath)) { |
||||||
|
ResourceHttpRequestHandler handler = mapping.getValue(); |
||||||
|
String nestedPath = matcher.extractPathWithinPattern(mapping.getKey(), resourcePath); |
||||||
|
String prefix = resourcePath.replace(nestedPath, ""); |
||||||
|
String url = new DefaultResourceResolverChain(handler.getResourceResolvers(), handler. |
||||||
|
getResourceTransformers()).resolveUrl(nestedPath, handler.getLocations()); |
||||||
|
if (url != null) { |
||||||
|
return prefix + url; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isResourceUrl(String relativeUrl) { |
||||||
|
for (String mapping : this.handlers.keySet()) { |
||||||
|
if (matcher.match(mapping, relativeUrl)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,68 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.io.UnsupportedEncodingException; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
import org.springframework.core.io.ByteArrayResource; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class TransformedResource extends ByteArrayResource { |
||||||
|
|
||||||
|
private final String filename; |
||||||
|
private final long lastModified; |
||||||
|
|
||||||
|
public TransformedResource(String filename, byte[] transformedContent) { |
||||||
|
super(transformedContent); |
||||||
|
this.filename = filename; |
||||||
|
this.lastModified = new Date().getTime(); |
||||||
|
} |
||||||
|
|
||||||
|
public TransformedResource(String filename, byte[] transformedContent, long lastModified) { |
||||||
|
super(transformedContent); |
||||||
|
this.filename = filename; |
||||||
|
this.lastModified = lastModified; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getFilename() { |
||||||
|
return this.filename; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long lastModified() throws IOException { |
||||||
|
return this.lastModified; |
||||||
|
} |
||||||
|
|
||||||
|
public String getContentAsString() { |
||||||
|
try { |
||||||
|
return new String(getByteArray(), "UTF-8"); |
||||||
|
} |
||||||
|
catch (UnsupportedEncodingException e) { |
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace(); |
||||||
|
return ""; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1 +0,0 @@ |
|||||||
function foo() { console.log("hello bar"); } |
|
||||||
@ -1 +0,0 @@ |
|||||||
function foo() { console.log("hello world"); } |
|
||||||
@ -0,0 +1,64 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.core.io.ClassPathResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class ExtensionMappingResourceResolverTests { |
||||||
|
|
||||||
|
private ResourceResolverChain resolver; |
||||||
|
|
||||||
|
private List<Resource> locations; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>(); |
||||||
|
resolvers.add(new ExtensionMappingResourceResolver()); |
||||||
|
resolver = new DefaultResourceResolverChain(resolvers, new ArrayList<ResourceTransformer>()); |
||||||
|
locations = new ArrayList<Resource>(); |
||||||
|
locations.add(new ClassPathResource("test/", getClass())); |
||||||
|
locations.add(new ClassPathResource("testalternatepath/", getClass())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveLessResource() throws Exception { |
||||||
|
String resourceId = "zoo.css"; |
||||||
|
Resource resource = new ClassPathResource("test/"+resourceId+".less", getClass()); |
||||||
|
Resource resolved = resolver.resolveAndTransform(null, resourceId, locations); |
||||||
|
assertEquals(resource, resolved); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveLessUrl() { |
||||||
|
String resourceId = "zoo.css"; |
||||||
|
String url = "zoo.css"; |
||||||
|
assertEquals(url, resolver.resolveUrl(resourceId, locations)); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,107 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.core.io.ClassPathResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.util.DigestUtils; |
||||||
|
import org.springframework.util.FileCopyUtils; |
||||||
|
import org.springframework.util.ReflectionUtils; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class FingerprintingResourceResolverTests { |
||||||
|
|
||||||
|
private ResourceResolverChain chain; |
||||||
|
|
||||||
|
private FingerprintingResourceResolver resolver = new FingerprintingResourceResolver(); |
||||||
|
|
||||||
|
private List<Resource> locations; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>(); |
||||||
|
resolvers.add(resolver); |
||||||
|
chain = new DefaultResourceResolverChain(resolvers, new ArrayList<ResourceTransformer>()); |
||||||
|
locations = new ArrayList<Resource>(); |
||||||
|
locations.add(new ClassPathResource("test/", getClass())); |
||||||
|
locations.add(new ClassPathResource("testalternatepath/", getClass())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveStaticFingerprintedResource() throws Exception { |
||||||
|
String file = "foo-e36d2e05253c6c7085a91522ce43a0b4.css"; |
||||||
|
Resource resource = new ClassPathResource("test/"+file, getClass()); |
||||||
|
Resource resolved = chain.resolveAndTransform(null, file, locations); |
||||||
|
assertEquals(resource, resolved); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveDynamicFingerprintedResource() throws Exception { |
||||||
|
Resource resource = new ClassPathResource("test/bar.css", getClass()); |
||||||
|
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(resource.getInputStream())); |
||||||
|
String path = "/bar-"+hash+".css"; |
||||||
|
|
||||||
|
Resource resolved = chain.resolveAndTransform(null, path, locations); |
||||||
|
|
||||||
|
assertEquals(resource, resolved); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveWithMultipleExtensions() throws Exception { |
||||||
|
Resource resource = new ClassPathResource("test/bar.min.css", getClass()); |
||||||
|
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(resource.getInputStream())); |
||||||
|
String path = "/bar.min-"+hash+".css"; |
||||||
|
|
||||||
|
Resource resolved = chain.resolveAndTransform(null, path, locations); |
||||||
|
|
||||||
|
assertEquals(resource, resolved); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveWithMultipleHyphens() throws Exception { |
||||||
|
Resource resource = new ClassPathResource("test/foo-bar/foo-bar.css", getClass()); |
||||||
|
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(resource.getInputStream())); |
||||||
|
String path = "/foo-bar/foo-bar-"+hash+".css"; |
||||||
|
|
||||||
|
Resource resolved = chain.resolveAndTransform(null, path, locations); |
||||||
|
|
||||||
|
assertEquals(resource, resolved); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void extractHash() throws Exception { |
||||||
|
String hash = "7fbe76cdac6093784895bb4989203e5a"; |
||||||
|
String path = "font-awesome/css/font-awesome.min-"+hash+".css"; |
||||||
|
|
||||||
|
Method extractHash = ReflectionUtils.findMethod(FingerprintingResourceResolver.class, "extractHash", String.class); |
||||||
|
ReflectionUtils.makeAccessible(extractHash); |
||||||
|
String result = (String) ReflectionUtils.invokeMethod(extractHash, resolver, path); |
||||||
|
assertEquals(hash, result); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,105 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.io.FileOutputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.zip.GZIPOutputStream; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.BeforeClass; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.core.io.ClassPathResource; |
||||||
|
import org.springframework.core.io.FileSystemResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.mock.web.test.MockHttpServletRequest; |
||||||
|
import org.springframework.util.FileCopyUtils; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class GzipResourceResolverTests { |
||||||
|
|
||||||
|
private ResourceResolverChain resolver; |
||||||
|
|
||||||
|
private List<Resource> locations; |
||||||
|
|
||||||
|
@BeforeClass |
||||||
|
public static void createGzippedResources() throws IOException { |
||||||
|
Resource location = new ClassPathResource("test/", GzipResourceResolverTests.class); |
||||||
|
Resource jsFile = new FileSystemResource(location.createRelative("/js/foo.js").getFile()); |
||||||
|
Resource gzJsFile = jsFile.createRelative("foo.js.gz"); |
||||||
|
Resource fingerPrintedFile = new FileSystemResource(location.createRelative("foo-e36d2e05253c6c7085a91522ce43a0b4.css").getFile()); |
||||||
|
Resource gzFingerPrintedFile = fingerPrintedFile.createRelative("foo-e36d2e05253c6c7085a91522ce43a0b4.css.gz"); |
||||||
|
|
||||||
|
if (gzJsFile.getFile().createNewFile()) { |
||||||
|
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(gzJsFile.getFile())); |
||||||
|
FileCopyUtils.copy(jsFile.getInputStream(), out); |
||||||
|
} |
||||||
|
|
||||||
|
if (gzFingerPrintedFile.getFile().createNewFile()) { |
||||||
|
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(gzFingerPrintedFile.getFile())); |
||||||
|
FileCopyUtils.copy(fingerPrintedFile.getInputStream(), out); |
||||||
|
} |
||||||
|
|
||||||
|
assertTrue(gzJsFile.exists()); |
||||||
|
assertTrue(gzFingerPrintedFile.exists()); |
||||||
|
} |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>(); |
||||||
|
resolvers.add(new GzipResourceResolver()); |
||||||
|
resolvers.add(new FingerprintingResourceResolver()); |
||||||
|
resolver = new DefaultResourceResolverChain(resolvers, new ArrayList<ResourceTransformer>()); |
||||||
|
locations = new ArrayList<Resource>(); |
||||||
|
locations.add(new ClassPathResource("test/", getClass())); |
||||||
|
locations.add(new ClassPathResource("testalternatepath/", getClass())); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveGzippedFile() throws IOException { |
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||||
|
request.addHeader("Accept-Encoding", "gzip"); |
||||||
|
String file = "js/foo.js"; |
||||||
|
String gzFile = file+".gz"; |
||||||
|
Resource resource = new ClassPathResource("test/"+gzFile, getClass()); |
||||||
|
Resource resolved = resolver.resolveAndTransform(request, file, locations); |
||||||
|
assertEquals(resource.getDescription(), resolved.getDescription()); |
||||||
|
assertEquals(new ClassPathResource("test/"+file).getFilename(), resolved.getFilename()); |
||||||
|
assertTrue("Expected " + resolved + " to be of type " + EncodedResource.class, resolved instanceof EncodedResource); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveFingerprintedGzippedFile() throws IOException { |
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||||
|
request.addHeader("Accept-Encoding", "gzip"); |
||||||
|
String file = "foo-e36d2e05253c6c7085a91522ce43a0b4.css"; |
||||||
|
String gzFile = file+".gz"; |
||||||
|
Resource resource = new ClassPathResource("test/"+gzFile, getClass()); |
||||||
|
Resource resolved = resolver.resolveAndTransform(request, file, locations); |
||||||
|
assertEquals(resource.getDescription(), resolved.getDescription()); |
||||||
|
assertEquals(new ClassPathResource("test/"+file).getFilename(), resolved.getFilename()); |
||||||
|
assertTrue("Expected " + resolved + " to be of type " + EncodedResource.class, resolved instanceof EncodedResource); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,96 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 the original author or authors. |
||||||
|
* |
||||||
|
* 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 org.springframework.web.servlet.resource; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.core.io.ClassPathResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Jeremy Grelle |
||||||
|
*/ |
||||||
|
public class ResourceUrlMapperTests { |
||||||
|
|
||||||
|
ResourceHttpRequestHandler handler; |
||||||
|
|
||||||
|
SimpleUrlHandlerMapping mapping; |
||||||
|
|
||||||
|
ResourceUrlMapper mapper; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
List<Resource> resourcePaths = new ArrayList<Resource>(); |
||||||
|
resourcePaths.add(new ClassPathResource("test/", getClass())); |
||||||
|
resourcePaths.add(new ClassPathResource("testalternatepath/", getClass())); |
||||||
|
|
||||||
|
Map<String, ResourceHttpRequestHandler> urlMap = new HashMap<String, ResourceHttpRequestHandler>(); |
||||||
|
handler = new ResourceHttpRequestHandler(); |
||||||
|
handler.setLocations(resourcePaths); |
||||||
|
urlMap.put("/resources/**", handler); |
||||||
|
|
||||||
|
mapping = new SimpleUrlHandlerMapping(); |
||||||
|
mapping.setUrlMap(urlMap); |
||||||
|
} |
||||||
|
|
||||||
|
private void resetMapper() { |
||||||
|
mapper = new ResourceUrlMapper(); |
||||||
|
mapper.postProcessAfterInitialization(mapping, "resourceMapping"); |
||||||
|
mapper.onApplicationEvent(null); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getStaticResourceUrl() { |
||||||
|
resetMapper(); |
||||||
|
|
||||||
|
String url = mapper.getUrlForResource("/resources/foo.css"); |
||||||
|
assertEquals("/resources/foo.css", url); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getFingerprintedResourceUrl() { |
||||||
|
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>(); |
||||||
|
resolvers.add(new FingerprintingResourceResolver()); |
||||||
|
handler.setResourceResolvers(resolvers); |
||||||
|
resetMapper(); |
||||||
|
|
||||||
|
String url = mapper.getUrlForResource("/resources/foo.css"); |
||||||
|
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getExtensionMappedResourceUrl() { |
||||||
|
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>(); |
||||||
|
resolvers.add(new ExtensionMappingResourceResolver()); |
||||||
|
handler.setResourceResolvers(resolvers); |
||||||
|
resetMapper(); |
||||||
|
|
||||||
|
String url = mapper.getUrlForResource("/resources/zoo.css"); |
||||||
|
assertEquals("/resources/zoo.css", url); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1 @@ |
|||||||
|
h2 { color:white; } |
||||||
@ -0,0 +1 @@ |
|||||||
|
h1 { color:red; } |
||||||
Loading…
Reference in new issue