From e5ab67bd71d4261c07a6f23c59aca089b37cb512 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 11 Jan 2021 18:39:24 +0000 Subject: [PATCH] ResourceHandlerRegistration exposes List locations --- .../ResourceHandlerRegistration.java | 22 ++- .../resource/ResourceHttpRequestHandler.java | 135 +++++++++--------- 2 files changed, 85 insertions(+), 72 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java index f5c31028b7..fd822bedd5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ResourceHandlerRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -42,6 +42,8 @@ public class ResourceHandlerRegistration { private final List locationValues = new ArrayList<>(); + private final List locationsResources = new ArrayList<>(); + @Nullable private Integer cachePeriod; @@ -82,8 +84,21 @@ public class ResourceHandlerRegistration { * @return the same {@link ResourceHandlerRegistration} instance, for * chained method invocation */ - public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) { - this.locationValues.addAll(Arrays.asList(resourceLocations)); + public ResourceHandlerRegistration addResourceLocations(String... locations) { + this.locationValues.addAll(Arrays.asList(locations)); + return this; + } + + /** + * Configure locations to serve static resources from based on pre-resolved + * {@code Resource} references. + * @param locations the resource locations to use + * @return the same {@link ResourceHandlerRegistration} instance, for + * chained method invocation + * @since 5.3.3 + */ + public ResourceHandlerRegistration addResourceLocations(Resource... locations) { + this.locationsResources.addAll(Arrays.asList(locations)); return this; } @@ -181,6 +196,7 @@ public class ResourceHandlerRegistration { handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers()); } handler.setLocationValues(this.locationValues); + handler.setLocations(this.locationsResources); if (this.cacheControl != null) { handler.setCacheControl(this.cacheControl); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 9408527f7a..538913b16a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -21,12 +21,10 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -69,9 +67,10 @@ import org.springframework.web.util.UrlPathHelper; * {@code HttpRequestHandler} that serves static resources in an optimized way * according to the guidelines of Page Speed, YSlow, etc. * - *

The {@linkplain #setLocations "locations"} property takes a list of Spring - * {@link Resource} locations from which static resources are allowed to be served - * by this handler. Resources could be served from a classpath location, e.g. + *

The properties {@linkplain #setLocations "locations"} and + * {@linkplain #setLocationValues "locationValues"} accept locations from which + * static resources can be served by this handler. This can be relative to the + * root of the web application, or from the classpath, e.g. * "classpath:/META-INF/public-web-resources/", allowing convenient packaging * and serving of resources such as .js, .css, and others in jar files. * @@ -106,7 +105,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator private final List locationValues = new ArrayList<>(4); - private final List locations = new ArrayList<>(4); + private final List locationResources = new ArrayList<>(4); + + private final List locationsToUse = new ArrayList<>(4); private final Map locationCharsets = new HashMap<>(4); @@ -149,40 +150,52 @@ public class ResourceHttpRequestHandler extends WebContentGenerator /** - * An alternative to {@link #setLocations(List)} that accepts a list of - * String-based location values, with support for {@link UrlResource}'s - * (e.g. files or HTTP URLs) with a special prefix to indicate the charset - * to use when appending relative paths. For example + * Configure String-based locations to serve resources from. + *

For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}} + * allows resources to be served both from the web application root and + * from any JAR on the classpath that contains a + * {@code /META-INF/public-web-resources/} directory, with resources in the + * web application root taking precedence. + *

For {@link org.springframework.core.io.UrlResource URL-based resources} + * (e.g. files, HTTP URLs, etc) this method supports a special prefix to + * indicate the charset associated with the URL so that relative paths + * appended to it can be encoded correctly, for example * {@code "[charset=Windows-31J]https://example.org/path"}. * @since 4.3.13 + * @see #setLocations(List) */ - public void setLocationValues(List locationValues) { - Assert.notNull(locationValues, "Location values list must not be null"); + public void setLocationValues(List locations) { + Assert.notNull(locations, "Locations list must not be null"); this.locationValues.clear(); - this.locationValues.addAll(locationValues); + this.locationValues.addAll(locations); } /** - * Set the {@code List} of {@code Resource} locations to use as sources - * for serving static resources. + * Configure locations to serve resources from as pre-resourced Resource's. * @see #setLocationValues(List) */ public void setLocations(List locations) { Assert.notNull(locations, "Locations list must not be null"); - this.locations.clear(); - this.locations.addAll(locations); + this.locationResources.clear(); + this.locationResources.addAll(locations); } /** - * Return the configured {@code List} of {@code Resource} locations. - *

Note that if {@link #setLocationValues(List) locationValues} are provided, - * instead of loaded Resource-based locations, this method will return - * empty until after initialization via {@link #afterPropertiesSet()}. + * Return the configured {@code List} of {@code Resource} locations including + * both String-based locations provided via + * {@link #setLocationValues(List) setLocationValues} and pre-resolved {@code Resource} + * locations provided via {@link #setLocations(List) setLocations}. + *

Note that the returned list is fully initialized only after + * initialization via {@link #afterPropertiesSet()}. * @see #setLocationValues * @see #setLocations */ public List getLocations() { - return this.locations; + if (this.locationsToUse.isEmpty()) { + // Possibly not yet initialized, return only what we have so far + return this.locationResources; + } + return this.locationsToUse; } /** @@ -374,7 +387,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator public void afterPropertiesSet() throws Exception { resolveResourceLocations(); - if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) { + if (logger.isWarnEnabled() && CollectionUtils.isEmpty(getLocations())) { logger.warn("Locations list is empty. No resources will be served unless a " + "custom ResourceResolver is configured as an alternative to PathResourceResolver."); } @@ -410,43 +423,38 @@ public class ResourceHttpRequestHandler extends WebContentGenerator } private void resolveResourceLocations() { - if (CollectionUtils.isEmpty(this.locationValues)) { - return; - } - else if (!CollectionUtils.isEmpty(this.locations)) { - throw new IllegalArgumentException("Please set either Resource-based \"locations\" or " + - "String-based \"locationValues\", but not both."); - } - - ApplicationContext applicationContext = obtainApplicationContext(); - for (String location : this.locationValues) { - if (this.embeddedValueResolver != null) { - String resolvedLocation = this.embeddedValueResolver.resolveStringValue(location); - if (resolvedLocation == null) { - throw new IllegalArgumentException("Location resolved to null: " + location); + if (!this.locationValues.isEmpty()) { + ApplicationContext applicationContext = obtainApplicationContext(); + for (String location : this.locationValues) { + if (this.embeddedValueResolver != null) { + String resolvedLocation = this.embeddedValueResolver.resolveStringValue(location); + if (resolvedLocation == null) { + throw new IllegalArgumentException("Location resolved to null: " + location); + } + location = resolvedLocation; } - location = resolvedLocation; - } - Charset charset = null; - location = location.trim(); - if (location.startsWith(URL_RESOURCE_CHARSET_PREFIX)) { - int endIndex = location.indexOf(']', URL_RESOURCE_CHARSET_PREFIX.length()); - if (endIndex == -1) { - throw new IllegalArgumentException("Invalid charset syntax in location: " + location); + Charset charset = null; + location = location.trim(); + if (location.startsWith(URL_RESOURCE_CHARSET_PREFIX)) { + int endIndex = location.indexOf(']', URL_RESOURCE_CHARSET_PREFIX.length()); + if (endIndex == -1) { + throw new IllegalArgumentException("Invalid charset syntax in location: " + location); + } + String value = location.substring(URL_RESOURCE_CHARSET_PREFIX.length(), endIndex); + charset = Charset.forName(value); + location = location.substring(endIndex + 1); } - String value = location.substring(URL_RESOURCE_CHARSET_PREFIX.length(), endIndex); - charset = Charset.forName(value); - location = location.substring(endIndex + 1); - } - Resource resource = applicationContext.getResource(location); - this.locations.add(resource); - if (charset != null) { - if (!(resource instanceof UrlResource)) { - throw new IllegalArgumentException("Unexpected charset for non-UrlResource: " + resource); + Resource resource = applicationContext.getResource(location); + this.locationsToUse.add(resource); + if (charset != null) { + if (!(resource instanceof UrlResource)) { + throw new IllegalArgumentException("Unexpected charset for non-UrlResource: " + resource); + } + this.locationCharsets.put(resource, charset); } - this.locationCharsets.put(resource, charset); } } + this.locationsToUse.addAll(this.locationResources); } /** @@ -455,7 +463,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator * match the {@link #setLocations locations} configured on this class. */ protected void initAllowedLocations() { - if (CollectionUtils.isEmpty(this.locations)) { + if (CollectionUtils.isEmpty(getLocations())) { return; } for (int i = getResourceResolvers().size() - 1; i >= 0; i--) { @@ -785,17 +793,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator @Override public String toString() { - return "ResourceHttpRequestHandler " + formatLocations(); + return "ResourceHttpRequestHandler " + getLocations(); } - - private Object formatLocations() { - if (!this.locationValues.isEmpty()) { - return this.locationValues.stream().collect(Collectors.joining("\", \"", "[\"", "\"]")); - } - else if (!this.locations.isEmpty()) { - return this.locations; - } - return Collections.emptyList(); - } - }