001/* 002 * Copyright 2012-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.autoconfigure.web; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.List; 022 023import org.springframework.boot.context.properties.ConfigurationProperties; 024import org.springframework.boot.context.properties.NestedConfigurationProperty; 025import org.springframework.context.ResourceLoaderAware; 026import org.springframework.core.io.ClassPathResource; 027import org.springframework.core.io.Resource; 028import org.springframework.core.io.ResourceLoader; 029 030/** 031 * Properties used to configure resource handling. 032 * 033 * @author Phillip Webb 034 * @author Brian Clozel 035 * @author Dave Syer 036 * @author Venil Noronha 037 * @since 1.1.0 038 */ 039@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false) 040public class ResourceProperties implements ResourceLoaderAware { 041 042 private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" }; 043 044 private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 045 "classpath:/META-INF/resources/", "classpath:/resources/", 046 "classpath:/static/", "classpath:/public/" }; 047 048 private static final String[] RESOURCE_LOCATIONS; 049 050 static { 051 RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length 052 + SERVLET_RESOURCE_LOCATIONS.length]; 053 System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0, 054 SERVLET_RESOURCE_LOCATIONS.length); 055 System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 056 SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length); 057 } 058 059 /** 060 * Locations of static resources. Defaults to classpath:[/META-INF/resources/, 061 * /resources/, /static/, /public/] plus context:/ (the root of the servlet context). 062 */ 063 private String[] staticLocations = RESOURCE_LOCATIONS; 064 065 /** 066 * Cache period for the resources served by the resource handler, in seconds. 067 */ 068 private Integer cachePeriod; 069 070 /** 071 * Enable default resource handling. 072 */ 073 private boolean addMappings = true; 074 075 private final Chain chain = new Chain(); 076 077 private ResourceLoader resourceLoader; 078 079 @Override 080 public void setResourceLoader(ResourceLoader resourceLoader) { 081 this.resourceLoader = resourceLoader; 082 } 083 084 public String[] getStaticLocations() { 085 return this.staticLocations; 086 } 087 088 public void setStaticLocations(String[] staticLocations) { 089 this.staticLocations = appendSlashIfNecessary(staticLocations); 090 } 091 092 private String[] appendSlashIfNecessary(String[] staticLocations) { 093 String[] normalized = new String[staticLocations.length]; 094 for (int i = 0; i < staticLocations.length; i++) { 095 String location = staticLocations[i]; 096 normalized[i] = (location.endsWith("/") ? location : location + "/"); 097 } 098 return normalized; 099 } 100 101 public Resource getWelcomePage() { 102 for (String location : getStaticWelcomePageLocations()) { 103 Resource resource = this.resourceLoader.getResource(location); 104 try { 105 if (resource.exists()) { 106 resource.getURL(); 107 return resource; 108 } 109 } 110 catch (Exception ex) { 111 // Ignore 112 } 113 } 114 return null; 115 } 116 117 private String[] getStaticWelcomePageLocations() { 118 String[] result = new String[this.staticLocations.length]; 119 for (int i = 0; i < result.length; i++) { 120 String location = this.staticLocations[i]; 121 if (!location.endsWith("/")) { 122 location = location + "/"; 123 } 124 result[i] = location + "index.html"; 125 } 126 return result; 127 } 128 129 List<Resource> getFaviconLocations() { 130 List<Resource> locations = new ArrayList<Resource>( 131 this.staticLocations.length + 1); 132 if (this.resourceLoader != null) { 133 for (String location : this.staticLocations) { 134 locations.add(this.resourceLoader.getResource(location)); 135 } 136 } 137 locations.add(new ClassPathResource("/")); 138 return Collections.unmodifiableList(locations); 139 } 140 141 public Integer getCachePeriod() { 142 return this.cachePeriod; 143 } 144 145 public void setCachePeriod(Integer cachePeriod) { 146 this.cachePeriod = cachePeriod; 147 } 148 149 public boolean isAddMappings() { 150 return this.addMappings; 151 } 152 153 public void setAddMappings(boolean addMappings) { 154 this.addMappings = addMappings; 155 } 156 157 public Chain getChain() { 158 return this.chain; 159 } 160 161 /** 162 * Configuration for the Spring Resource Handling chain. 163 */ 164 public static class Chain { 165 166 /** 167 * Enable the Spring Resource Handling chain. Disabled by default unless at least 168 * one strategy has been enabled. 169 */ 170 private Boolean enabled; 171 172 /** 173 * Enable caching in the Resource chain. 174 */ 175 private boolean cache = true; 176 177 /** 178 * Enable HTML5 application cache manifest rewriting. 179 */ 180 private boolean htmlApplicationCache = false; 181 182 /** 183 * Enable resolution of already gzipped resources. Checks for a resource name 184 * variant with the "*.gz" extension. 185 */ 186 private boolean gzipped = false; 187 188 @NestedConfigurationProperty 189 private final Strategy strategy = new Strategy(); 190 191 /** 192 * Return whether the resource chain is enabled. Return {@code null} if no 193 * specific settings are present. 194 * @return whether the resource chain is enabled or {@code null} if no specified 195 * settings are present. 196 */ 197 public Boolean getEnabled() { 198 return getEnabled(getStrategy().getFixed().isEnabled(), 199 getStrategy().getContent().isEnabled(), this.enabled); 200 } 201 202 public void setEnabled(boolean enabled) { 203 this.enabled = enabled; 204 } 205 206 public boolean isCache() { 207 return this.cache; 208 } 209 210 public void setCache(boolean cache) { 211 this.cache = cache; 212 } 213 214 public Strategy getStrategy() { 215 return this.strategy; 216 } 217 218 public boolean isHtmlApplicationCache() { 219 return this.htmlApplicationCache; 220 } 221 222 public void setHtmlApplicationCache(boolean htmlApplicationCache) { 223 this.htmlApplicationCache = htmlApplicationCache; 224 } 225 226 public boolean isGzipped() { 227 return this.gzipped; 228 } 229 230 public void setGzipped(boolean gzipped) { 231 this.gzipped = gzipped; 232 } 233 234 static Boolean getEnabled(boolean fixedEnabled, boolean contentEnabled, 235 Boolean chainEnabled) { 236 return (fixedEnabled || contentEnabled ? Boolean.TRUE : chainEnabled); 237 } 238 239 } 240 241 /** 242 * Strategies for extracting and embedding a resource version in its URL path. 243 */ 244 public static class Strategy { 245 246 @NestedConfigurationProperty 247 private final Fixed fixed = new Fixed(); 248 249 @NestedConfigurationProperty 250 private final Content content = new Content(); 251 252 public Fixed getFixed() { 253 return this.fixed; 254 } 255 256 public Content getContent() { 257 return this.content; 258 } 259 260 } 261 262 /** 263 * Version Strategy based on content hashing. 264 */ 265 public static class Content { 266 267 /** 268 * Enable the content Version Strategy. 269 */ 270 private boolean enabled; 271 272 /** 273 * Comma-separated list of patterns to apply to the Version Strategy. 274 */ 275 private String[] paths = new String[] { "/**" }; 276 277 public boolean isEnabled() { 278 return this.enabled; 279 } 280 281 public void setEnabled(boolean enabled) { 282 this.enabled = enabled; 283 } 284 285 public String[] getPaths() { 286 return this.paths; 287 } 288 289 public void setPaths(String[] paths) { 290 this.paths = paths; 291 } 292 293 } 294 295 /** 296 * Version Strategy based on a fixed version string. 297 */ 298 public static class Fixed { 299 300 /** 301 * Enable the fixed Version Strategy. 302 */ 303 private boolean enabled; 304 305 /** 306 * Comma-separated list of patterns to apply to the Version Strategy. 307 */ 308 private String[] paths = new String[] { "/**" }; 309 310 /** 311 * Version string to use for the Version Strategy. 312 */ 313 private String version; 314 315 public boolean isEnabled() { 316 return this.enabled; 317 } 318 319 public void setEnabled(boolean enabled) { 320 this.enabled = enabled; 321 } 322 323 public String[] getPaths() { 324 return this.paths; 325 } 326 327 public void setPaths(String[] paths) { 328 this.paths = paths; 329 } 330 331 public String getVersion() { 332 return this.version; 333 } 334 335 public void setVersion(String version) { 336 this.version = version; 337 } 338 339 } 340 341}