2020import java .net .URLDecoder ;
2121import java .nio .charset .Charset ;
2222import java .util .ArrayList ;
23- import java .util .Collections ;
2423import java .util .HashMap ;
2524import java .util .List ;
2625import java .util .Map ;
3332import org .apache .commons .logging .LogFactory ;
3433
3534import org .springframework .beans .factory .InitializingBean ;
35+ import org .springframework .beans .factory .config .ConfigurableBeanFactory ;
36+ import org .springframework .context .ApplicationContext ;
3637import org .springframework .core .io .Resource ;
38+ import org .springframework .core .io .UrlResource ;
3739import org .springframework .core .io .support .ResourceRegion ;
3840import org .springframework .http .HttpHeaders ;
3941import org .springframework .http .HttpMethod ;
@@ -101,11 +103,15 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
101103
102104 private static final Log logger = LogFactory .getLog (ResourceHttpRequestHandler .class );
103105
106+ private static final String URL_RESOURCE_CHARSET_PREFIX = "[charset=" ;
107+
104108
105109 private final List <Resource > locations = new ArrayList <Resource >(4 );
106110
107111 private final Map <Resource , Charset > locationCharsets = new HashMap <Resource , Charset >(4 );
108112
113+ private final List <String > locationValues = new ArrayList <String >(4 );
114+
109115 private final List <ResourceResolver > resourceResolvers = new ArrayList <ResourceResolver >(4 );
110116
111117 private final List <ResourceTransformer > resourceTransformers = new ArrayList <ResourceTransformer >(4 );
@@ -129,8 +135,9 @@ public ResourceHttpRequestHandler() {
129135
130136
131137 /**
132- * Set the {@code List} of {@code Resource} paths to use as sources
138+ * Set the {@code List} of {@code Resource} locations to use as sources
133139 * for serving static resources.
140+ * @see #setLocationValues(List)
134141 */
135142 public void setLocations (List <Resource > locations ) {
136143 Assert .notNull (locations , "Locations list must not be null" );
@@ -139,35 +146,27 @@ public void setLocations(List<Resource> locations) {
139146 }
140147
141148 /**
142- * Return the {@code List} of {@code Resource} paths to use as sources
143- * for serving static resources.
149+ * Return the configured {@code List} of {@code Resource} locations.
150+ * Note that if {@link #setLocationValues(List) locationValues} are provided,
151+ * instead of loaded Resource-based locations, this method will return
152+ * empty until after initialization via {@link #afterPropertiesSet()}.
144153 */
145154 public List <Resource > getLocations () {
146155 return this .locations ;
147156 }
148157
149158 /**
150- * Specify charsets associated with the configured {@link #setLocations(List)
151- * locations}. This is supported for
152- * {@link org.springframework.core.io.UrlResource URL resources} such as a
153- * file or an HTTP URL location and is used in {@link PathResourceResolver}
154- * to correctly encode paths relative to the location.
155- * <p><strong>Note:</strong> The charset is used only if the
156- * {@link #setUrlPathHelper urlPathHelper} property is also configured and
157- * its {@code urlDecode} property is set to {@code true}.
158- * @since 4.3.13
159- */
160- public void setLocationCharsets (Map <Resource ,Charset > locationCharsets ) {
161- this .locationCharsets .clear ();
162- this .locationCharsets .putAll (locationCharsets );
163- }
164-
165- /**
166- * Return charsets associated with static resource locations.
159+ * An alternative to {@link #setLocations(List)} that accepts a list of
160+ * String-based location values, with support for {@link UrlResource}'s
161+ * (e.g. files or HTTP URLs) with a special prefix to indicate the charset
162+ * to use when appending relative paths. For example
163+ * {@code "[charset=Windows-31J]http://example.org/path"}.
167164 * @since 4.3.13
168165 */
169- public Map <Resource , Charset > getLocationCharsets () {
170- return Collections .unmodifiableMap (this .locationCharsets );
166+ public void setLocationValues (List <String > locationValues ) {
167+ Assert .notNull (locationValues , "Location values list must not be null" );
168+ this .locationValues .clear ();
169+ this .locationValues .addAll (locationValues );
171170 }
172171
173172 /**
@@ -297,6 +296,9 @@ public UrlPathHelper getUrlPathHelper() {
297296
298297 @ Override
299298 public void afterPropertiesSet () throws Exception {
299+
300+ loadResourceLocations ();
301+
300302 if (logger .isWarnEnabled () && CollectionUtils .isEmpty (this .locations )) {
301303 logger .warn ("Locations list is empty. No resources will be served unless a " +
302304 "custom ResourceResolver is configured as an alternative to PathResourceResolver." );
@@ -305,6 +307,7 @@ public void afterPropertiesSet() throws Exception {
305307 if (this .resourceResolvers .isEmpty ()) {
306308 this .resourceResolvers .add (new PathResourceResolver ());
307309 }
310+
308311 initAllowedLocations ();
309312
310313 if (this .resourceHttpMessageConverter == null ) {
@@ -317,6 +320,46 @@ public void afterPropertiesSet() throws Exception {
317320 this .contentNegotiationStrategy = initContentNegotiationStrategy ();
318321 }
319322
323+ private void loadResourceLocations () {
324+ if (!CollectionUtils .isEmpty (this .locations ) && !CollectionUtils .isEmpty (this .locationValues )) {
325+ throw new IllegalArgumentException ("Please set either Resource-based \" locations\" or " +
326+ "String-based \" locationValues\" , but not both." );
327+ }
328+ if (CollectionUtils .isEmpty (this .locationValues )) {
329+ return ;
330+ }
331+ ApplicationContext appContext = getApplicationContext ();
332+ ConfigurableBeanFactory beanFactory = null ;
333+ if (appContext .getAutowireCapableBeanFactory () instanceof ConfigurableBeanFactory ) {
334+ beanFactory = ((ConfigurableBeanFactory ) appContext .getAutowireCapableBeanFactory ());
335+ }
336+ for (String location : this .locationValues ) {
337+ if (beanFactory != null ) {
338+ location = beanFactory .resolveEmbeddedValue (location );
339+ Assert .notNull (location , "Null location" );
340+ }
341+ Charset charset = null ;
342+ location = location .trim ();
343+ if (location .startsWith (URL_RESOURCE_CHARSET_PREFIX )) {
344+ int endIndex = location .indexOf ("]" , URL_RESOURCE_CHARSET_PREFIX .length ());
345+ if (endIndex == -1 ) {
346+ throw new IllegalArgumentException ("Invalid charset syntax in location: " + location );
347+ }
348+ String value = location .substring (URL_RESOURCE_CHARSET_PREFIX .length (), endIndex );
349+ charset = Charset .forName (value );
350+ location = location .substring (endIndex + 1 );
351+ }
352+ Resource resource = appContext .getResource (location );
353+ this .locations .add (resource );
354+ if (charset != null ) {
355+ if (!(resource instanceof UrlResource )) {
356+ throw new IllegalArgumentException ("Unexpected charset for non-UrlResource: " + resource );
357+ }
358+ this .locationCharsets .put (resource , charset );
359+ }
360+ }
361+ }
362+
320363 /**
321364 * Look for a {@code PathResourceResolver} among the configured resource
322365 * resolvers and set its {@code allowedLocations} property (if empty) to
0 commit comments