From 1f630a5fb9c60ee1d2b136a44af4a6c96843f80f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 25 Apr 2014 16:49:49 -0400 Subject: [PATCH] Add CachingResourceResolver --- .../resource/CachingResourceResolver.java | 106 +++++++++++++++++ .../CachingResourceResolverTests.java | 112 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java create mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CachingResourceResolverTests.java diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java new file mode 100644 index 00000000000..a26fad51824 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/CachingResourceResolver.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2014 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.cache.Cache; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * A {@link org.springframework.web.servlet.resource.ResourceResolver} that + * resolves resources from a {@link org.springframework.cache.Cache} or otherwise + * delegates to the resolver chain and saves the result in the cache. + * + * @author Rossen Stoyanchev + * @since 4.1 + */ +public class CachingResourceResolver extends AbstractResourceResolver { + + private static final String REQUEST_PATH_PREFIX = "requestPath:"; + + private static final String RESOURCE_URL_PATH_PREFIX = "resourceUrlPath:"; + + + private final Cache cache; + + + public CachingResourceResolver(Cache cache) { + Assert.notNull(cache, "'cache' is required"); + this.cache = cache; + } + + /** + * Return the configured {@code Cache}. + */ + public Cache getCache() { + return this.cache; + } + + @Override + protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath, + List locations, ResourceResolverChain chain) { + + String key = REQUEST_PATH_PREFIX + requestPath; + Resource resource = this.cache.get(key, Resource.class); + + if (resource != null) { + if (logger.isTraceEnabled()) { + logger.trace("Found match"); + } + return resource; + } + + resource = chain.resolveResource(request, requestPath, locations); + if (resource != null) { + if (logger.isTraceEnabled()) { + logger.trace("Putting resolved resource in cache"); + } + this.cache.put(key, resource); + } + + return resource; + } + + @Override + protected String resolveUrlPathInternal(String resourceUrlPath, + List locations, ResourceResolverChain chain) { + + String key = RESOURCE_URL_PATH_PREFIX + resourceUrlPath; + String resolvedUrlPath = this.cache.get(key, String.class); + + if (resolvedUrlPath != null) { + if (logger.isTraceEnabled()) { + logger.trace("Found match"); + } + return resolvedUrlPath; + } + + resolvedUrlPath = chain.resolveUrlPath(resourceUrlPath, locations); + if (resolvedUrlPath != null) { + if (logger.isTraceEnabled()) { + logger.trace("Putting resolved resource URL path in cache"); + } + this.cache.put(key, resolvedUrlPath); + } + + return resolvedUrlPath; + } + +} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CachingResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CachingResourceResolverTests.java new file mode 100644 index 00000000000..241bacfeb79 --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/CachingResourceResolverTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-2014 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.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.cache.Cache; +import org.springframework.cache.concurrent.ConcurrentMapCache; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +/** + * Unit tests for + * {@link org.springframework.web.servlet.resource.CachingResourceResolver}. + * + * @author Rossen Stoyanchev + */ +public class CachingResourceResolverTests { + + private Cache cache; + + private ResourceResolverChain chain; + + private List locations; + + + @Before + public void setup() { + + this.cache = new ConcurrentMapCache("resourceCache"); + + List resolvers = new ArrayList<>(); + resolvers.add(new CachingResourceResolver(this.cache)); + resolvers.add(new PathResourceResolver()); + this.chain = new DefaultResourceResolverChain(resolvers); + + this.locations = new ArrayList<>(); + this.locations.add(new ClassPathResource("test/", getClass())); + } + + + @Test + public void resolveResourceInternal() { + String file = "bar.css"; + Resource expected = new ClassPathResource("test/" + file, getClass()); + Resource actual = this.chain.resolveResource(null, file, this.locations); + + assertEquals(expected, actual); + } + + @Test + public void resolveResourceInternalFromCache() { + + Resource expected = Mockito.mock(Resource.class); + this.cache.put("requestPath:bar.css", expected); + + String file = "bar.css"; + Resource actual = this.chain.resolveResource(null, file, this.locations); + + assertSame(expected, actual); + } + + @Test + public void resolveResourceInternalNoMatch() { + assertNull(this.chain.resolveResource(null, "invalid.css", this.locations)); + } + + @Test + public void resolverUrlPath() { + String expected = "/foo.css"; + String actual = this.chain.resolveUrlPath(expected, this.locations); + + assertEquals(expected, actual); + } + + @Test + public void resolverUrlPathFromCache() { + String expected = "cached-imaginary.css"; + this.cache.put("resourceUrlPath:imaginary.css", expected); + String actual = this.chain.resolveUrlPath("imaginary.css", this.locations); + + assertEquals(expected, actual); + } + + @Test + public void resolverUrlPathNoMatch() { + assertNull(this.chain.resolveUrlPath("invalid.css", this.locations)); + } + +}