Skip to content

Commit

Permalink
ResourceHandlerRegistration exposes List<Resource> locations
Browse files Browse the repository at this point in the history
  • Loading branch information
rstoyanchev committed Jan 11, 2021
1 parent 2d53570 commit e5ab67b
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -42,6 +42,8 @@ public class ResourceHandlerRegistration {

private final List<String> locationValues = new ArrayList<>();

private final List<Resource> locationsResources = new ArrayList<>();

@Nullable
private Integer cachePeriod;

Expand Down Expand Up @@ -82,8 +84,21 @@ public ResourceHandlerRegistration(String... pathPatterns) {
* @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;
}

Expand Down Expand Up @@ -181,6 +196,7 @@ protected ResourceHttpRequestHandler getRequestHandler() {
handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
}
handler.setLocationValues(this.locationValues);
handler.setLocations(this.locationsResources);
if (this.cacheControl != null) {
handler.setCacheControl(this.cacheControl);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -21,12 +21,10 @@
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;
Expand Down Expand Up @@ -69,9 +67,10 @@
* {@code HttpRequestHandler} that serves static resources in an optimized way
* according to the guidelines of Page Speed, YSlow, etc.
*
* <p>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.
* <p>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.
*
Expand Down Expand Up @@ -106,7 +105,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator

private final List<String> locationValues = new ArrayList<>(4);

private final List<Resource> locations = new ArrayList<>(4);
private final List<Resource> locationResources = new ArrayList<>(4);

private final List<Resource> locationsToUse = new ArrayList<>(4);

private final Map<Resource, Charset> locationCharsets = new HashMap<>(4);

Expand Down Expand Up @@ -149,40 +150,52 @@ public ResourceHttpRequestHandler() {


/**
* 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.
* <p>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.
* <p>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<String> locationValues) {
Assert.notNull(locationValues, "Location values list must not be null");
public void setLocationValues(List<String> 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<Resource> 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.
* <p>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}.
* <p>Note that the returned list is fully initialized only after
* initialization via {@link #afterPropertiesSet()}.
* @see #setLocationValues
* @see #setLocations
*/
public List<Resource> 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;
}

/**
Expand Down Expand Up @@ -374,7 +387,7 @@ public void setUseLastModified(boolean useLastModified) {
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.");
}
Expand Down Expand Up @@ -410,43 +423,38 @@ public void afterPropertiesSet() throws Exception {
}

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);
}

/**
Expand All @@ -455,7 +463,7 @@ else if (!CollectionUtils.isEmpty(this.locations)) {
* 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--) {
Expand Down Expand Up @@ -785,17 +793,6 @@ protected void setHeaders(HttpServletResponse response, Resource resource, @Null

@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();
}

}

0 comments on commit e5ab67b

Please sign in to comment.