diff --git a/src/main/java/org/springframework/hateoas/core/CachingMappingDiscoverer.java b/src/main/java/org/springframework/hateoas/core/CachingMappingDiscoverer.java index 39d64ea0..24d30ebe 100644 --- a/src/main/java/org/springframework/hateoas/core/CachingMappingDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/core/CachingMappingDiscoverer.java @@ -1,21 +1,56 @@ +/* + * Copyright 2019 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.hateoas.core; +import lombok.RequiredArgsConstructor; + import java.lang.reflect.Method; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import org.springframework.util.ConcurrentReferenceHashMap; +import org.springframework.util.StringUtils; + +/** + * Caching adapter of {@link MappingDiscoverer}. + * + * @author Michal Stochmialek + * @author Oliver Drotbohm + */ +@RequiredArgsConstructor(staticName = "of") public class CachingMappingDiscoverer implements MappingDiscoverer { - private Map mappingCache = new ConcurrentHashMap(); - private MappingDiscoverer discoverer; - public CachingMappingDiscoverer(MappingDiscoverer discoverer) { - this.discoverer = discoverer; - } + private static final Map CACHE = new ConcurrentReferenceHashMap(); + private final MappingDiscoverer discoverer; + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class) + */ @Override public String getMapping(final Class type) { + String key = key(type, null); + return getMapping(key, new CachedCall() { + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.CachingMappingDiscoverer.CachedCall#getMapping() + */ @Override public String getMapping() { return discoverer.getMapping(type); @@ -23,10 +58,21 @@ public class CachingMappingDiscoverer implements MappingDiscoverer { }); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method) + */ @Override public String getMapping(final Method method) { + String key = key(method.getDeclaringClass(), method); + return getMapping(key, new CachedCall() { + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.CachingMappingDiscoverer.CachedCall#getMapping() + */ @Override public String getMapping() { return discoverer.getMapping(method); @@ -34,10 +80,21 @@ public class CachingMappingDiscoverer implements MappingDiscoverer { }); } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method) + */ @Override public String getMapping(final Class type, final Method method) { + String key = key(type, method); + return getMapping(key, new CachedCall() { + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.CachingMappingDiscoverer.CachedCall#getMapping() + */ @Override public String getMapping() { return discoverer.getMapping(type, method); @@ -45,12 +102,17 @@ public class CachingMappingDiscoverer implements MappingDiscoverer { }); } - public String getMapping(String key, CachedCall cachedCall) { - if (mappingCache.containsKey(key)) { - return mappingCache.get(key); + private String getMapping(String key, CachedCall cachedCall) { + + if (CACHE.containsKey(key)) { + + return CACHE.get(key); + } else { + String mapping = cachedCall.getMapping(); - mappingCache.put(key, mapping); + CACHE.put(key, mapping); + return mapping; } } @@ -59,15 +121,17 @@ public class CachingMappingDiscoverer implements MappingDiscoverer { String getMapping(); } - private String key(Class type, Method method) { - StringBuilder buf = new StringBuilder(); - buf.append(type.getName()); - if (method != null) { - buf.append(method.getName()); - for (Class par: method.getParameterTypes()) { - buf.append(par.getName()); - } + private static String key(Class type, Method method) { + + StringBuilder builder = new StringBuilder(type.getName()); + + if (method == null) { + return builder.toString(); } - return buf.toString(); + + builder.append(method.getName()); + builder.append(StringUtils.arrayToCommaDelimitedString(method.getParameterTypes())); + + return builder.toString(); } } diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java index bc20f56a..5f2b0cc8 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java @@ -15,31 +15,18 @@ */ package org.springframework.hateoas.mvc; -import static org.springframework.hateoas.mvc.ForwardedHeader.*; - -import lombok.RequiredArgsConstructor; -import lombok.experimental.Delegate; - import java.lang.reflect.Method; import java.net.URI; import java.util.Map; -import javax.servlet.http.HttpServletRequest; - -import org.springframework.context.ApplicationContext; import org.springframework.hateoas.Link; import org.springframework.hateoas.TemplateVariables; import org.springframework.hateoas.core.AnnotationMappingDiscoverer; +import org.springframework.hateoas.core.CachingMappingDiscoverer; import org.springframework.hateoas.core.DummyInvocationUtils; import org.springframework.hateoas.core.LinkBuilderSupport; -import org.springframework.hateoas.core.MappingDiscoverer; import org.springframework.util.Assert; -import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.DefaultUriTemplateHandler; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; @@ -47,7 +34,7 @@ import org.springframework.web.util.UriTemplate; /** * Builder to ease building {@link Link} instances pointing to Spring MVC controllers. - * + * * @author Oliver Gierke * @author Kamill Sokol * @author Greg Turnquist @@ -58,18 +45,16 @@ import org.springframework.web.util.UriTemplate; */ public class ControllerLinkBuilder extends LinkBuilderSupport { - private static final String REQUEST_ATTRIBUTES_MISSING = "Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?"; - private static final CachingAnnotationMappingDiscoverer DISCOVERER = new CachingAnnotationMappingDiscoverer( - new AnnotationMappingDiscoverer(RequestMapping.class)); + private static final CachingMappingDiscoverer DISCOVERER = CachingMappingDiscoverer + .of(new AnnotationMappingDiscoverer(RequestMapping.class)); private static final ControllerLinkBuilderFactory FACTORY = new ControllerLinkBuilderFactory(); - private static final String CACHE_KEY = ControllerLinkBuilder.class.getName() + "#BUILDER_CACHE"; private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler(); private final TemplateVariables variables; /** * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponentsBuilder}. - * + * * @param builder must not be {@literal null}. */ ControllerLinkBuilder(UriComponentsBuilder builder) { @@ -97,7 +82,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport * @RequestMapping("/customers") * class CustomerController { - * + * * @RequestMapping("/{id}/addresses") - * HttpEntity<Addresses> showAddresses(@PathVariable Long id) { … } + * HttpEntity<Addresses> showAddresses(@PathVariable Long id) { … } * } - * + * * Link link = linkTo(methodOn(CustomerController.class).showAddresses(2L)).withRel("addresses"); * - * + * * The resulting {@link Link} instance will point to {@code /customers/2/addresses} and have a rel of * {@code addresses}. For more details on the method invocation constraints, see * {@link DummyInvocationUtils#methodOn(Class, Object...)}. - * + * * @param invocationValue * @return */ @@ -199,7 +185,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport= 5 && minorVersion >= 1) || (majorVersion > 5); - } - - /** - * Copy of {@link ServletUriComponentsBuilder#getCurrentRequest()} until SPR-10110 gets fixed. - * - * @return - */ - @SuppressWarnings("null") - private static HttpServletRequest getCurrentRequest() { - RequestAttributes requestAttributes = getRequestAttributes(); - HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); - Assert.state(servletRequest != null, "Could not find current HttpServletRequest"); - return servletRequest; - } - - private static RequestAttributes getRequestAttributes() { - RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); - Assert.state(requestAttributes != null, REQUEST_ATTRIBUTES_MISSING); - Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes); - return requestAttributes; - } - - private static void cacheBaseUri(URI uri) { - getRequestAttributes().setAttribute(CACHE_KEY, uri, RequestAttributes.SCOPE_REQUEST); - } - - private static URI getCachedBaseUri() { - return (URI) getRequestAttributes().getAttribute(CACHE_KEY, RequestAttributes.SCOPE_REQUEST); - } - - @RequiredArgsConstructor - private static class CachingAnnotationMappingDiscoverer implements MappingDiscoverer { - - private final @Delegate AnnotationMappingDiscoverer delegate; - private final Map templates = new ConcurrentReferenceHashMap(); - - public UriTemplate getMappingAsUriTemplate(Class type, Method method) { - - String mapping = delegate.getMapping(type, method); - - UriTemplate template = templates.get(mapping); - - if (template == null) { - template = new UriTemplate(mapping); - templates.put(mapping, template); - } - - return template; - } - } - private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler { public CustomUriTemplateHandler() { diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java index 80546443..3c83b1bb 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java @@ -56,7 +56,7 @@ import org.springframework.web.util.UriTemplate; /** * Factory for {@link LinkBuilderSupport} instances based on the request mapping annotated on the given controller. - * + * * @author Ricardo Gladwell * @author Oliver Gierke * @author Dietrich Schulten @@ -68,19 +68,18 @@ import org.springframework.web.util.UriTemplate; */ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory { - private static final MappingDiscoverer DISCOVERER = new CachingMappingDiscoverer( - new AnnotationMappingDiscoverer(RequestMapping.class)); + private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer + .of(new AnnotationMappingDiscoverer(RequestMapping.class)); private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor( new AnnotationAttribute(PathVariable.class)); private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new RequestParamParameterAccessor(); private List uriComponentsContributors = new ArrayList(); - private UriTemplateFactory uriTemplateFactory = new UriTemplateFactory(); /** * Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method * invocations. - * + * * @see #linkTo(Object) * @param uriComponentsContributors the uriComponentsContributors to set */ @@ -115,7 +114,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory values = new HashMap(); Iterator names = template.getVariableNames().iterator(); @@ -189,7 +188,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory= 5 && minorVersion >= 1 || majorVersion > 5; + } + + /** + * Copy of {@link ServletUriComponentsBuilder#getCurrentRequest()} until SPR-10110 gets fixed. + * + * @return + */ + private static HttpServletRequest getCurrentRequest() { + + RequestAttributes requestAttributes = getRequestAttributes(); + HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); + + Assert.state(servletRequest != null, "Could not find current HttpServletRequest"); + + return servletRequest; + } + + private static RequestAttributes getRequestAttributes() { + + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + + Assert.state(requestAttributes != null, REQUEST_ATTRIBUTES_MISSING); + Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes); + + return requestAttributes; + } + + private static UriComponentsBuilder cacheBaseUri(UriComponentsBuilder builder) { + + URI uri = builder.build().toUri(); + + getRequestAttributes().setAttribute(CACHE_KEY, uri, RequestAttributes.SCOPE_REQUEST); + + return builder; + } + + private static URI getCachedBaseUri() { + return (URI) getRequestAttributes().getAttribute(CACHE_KEY, RequestAttributes.SCOPE_REQUEST); + } +} diff --git a/src/main/java/org/springframework/hateoas/mvc/UriTemplateFactory.java b/src/main/java/org/springframework/hateoas/mvc/UriTemplateFactory.java index dc22d26d..4a3b66d7 100644 --- a/src/main/java/org/springframework/hateoas/mvc/UriTemplateFactory.java +++ b/src/main/java/org/springframework/hateoas/mvc/UriTemplateFactory.java @@ -1,21 +1,55 @@ +/* + * Copyright 2019 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.hateoas.mvc; +import java.util.Map; + +import org.springframework.util.Assert; +import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.web.util.UriTemplate; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - /** - * Builds and caches UriTemplates. + * Builds and caches {@link UriTemplate} instances. + * + * @author Michal Stochmialek + * @author Oliver Drotbohm */ class UriTemplateFactory { - private Map templateCache = new ConcurrentHashMap(); - UriTemplate templateFor(String mapping) { - if (templateCache.containsKey(mapping)) { - return templateCache.get(mapping); + + private static final Map CACHE = new ConcurrentReferenceHashMap(); + + /** + * Returns the the {@link UriTemplate} for the given mapping. + * + * @param mapping must not be {@literal null} or empty. + * @return + */ + public static UriTemplate templateFor(String mapping) { + + Assert.hasText(mapping, "Mapping must not be null or empty!"); + + if (CACHE.containsKey(mapping)) { + + return CACHE.get(mapping); + } else { + UriTemplate template = new UriTemplate(mapping); - templateCache.put(mapping, template); + CACHE.put(mapping, template); + return template; } }