001/* 002 * Copyright 2002-2019 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 * https://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.web.reactive.resource; 018 019import java.util.List; 020 021import org.webjars.WebJarAssetLocator; 022import reactor.core.publisher.Mono; 023 024import org.springframework.core.io.Resource; 025import org.springframework.lang.Nullable; 026import org.springframework.web.server.ServerWebExchange; 027 028/** 029 * A {@code ResourceResolver} that delegates to the chain to locate a resource and then 030 * attempts to find a matching versioned resource contained in a WebJar JAR file. 031 * 032 * <p>This allows WebJars.org users to write version agnostic paths in their templates, 033 * like {@code <script src="/jquery/jquery.min.js"/>}. 034 * This path will be resolved to the unique version {@code <script src="/jquery/1.2.0/jquery.min.js"/>}, 035 * which is a better fit for HTTP caching and version management in applications. 036 * 037 * <p>This also resolves resources for version agnostic HTTP requests {@code "GET /jquery/jquery.min.js"}. 038 * 039 * <p>This resolver requires the {@code org.webjars:webjars-locator-core} library 040 * on the classpath and is automatically registered if that library is present. 041 * 042 * @author Rossen Stoyanchev 043 * @author Brian Clozel 044 * @since 5.0 045 * @see <a href="https://www.webjars.org">webjars.org</a> 046 */ 047public class WebJarsResourceResolver extends AbstractResourceResolver { 048 049 private static final String WEBJARS_LOCATION = "META-INF/resources/webjars/"; 050 051 private static final int WEBJARS_LOCATION_LENGTH = WEBJARS_LOCATION.length(); 052 053 054 private final WebJarAssetLocator webJarAssetLocator; 055 056 057 /** 058 * Create a {@code WebJarsResourceResolver} with a default {@code WebJarAssetLocator} instance. 059 */ 060 public WebJarsResourceResolver() { 061 this(new WebJarAssetLocator()); 062 } 063 064 /** 065 * Create a {@code WebJarsResourceResolver} with a custom {@code WebJarAssetLocator} instance, 066 * e.g. with a custom index. 067 */ 068 public WebJarsResourceResolver(WebJarAssetLocator webJarAssetLocator) { 069 this.webJarAssetLocator = webJarAssetLocator; 070 } 071 072 073 @Override 074 protected Mono<Resource> resolveResourceInternal(@Nullable ServerWebExchange exchange, 075 String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) { 076 077 return chain.resolveResource(exchange, requestPath, locations) 078 .switchIfEmpty(Mono.defer(() -> { 079 String webJarsResourcePath = findWebJarResourcePath(requestPath); 080 if (webJarsResourcePath != null) { 081 return chain.resolveResource(exchange, webJarsResourcePath, locations); 082 } 083 else { 084 return Mono.empty(); 085 } 086 })); 087 } 088 089 @Override 090 protected Mono<String> resolveUrlPathInternal(String resourceUrlPath, 091 List<? extends Resource> locations, ResourceResolverChain chain) { 092 093 return chain.resolveUrlPath(resourceUrlPath, locations) 094 .switchIfEmpty(Mono.defer(() -> { 095 String webJarResourcePath = findWebJarResourcePath(resourceUrlPath); 096 if (webJarResourcePath != null) { 097 return chain.resolveUrlPath(webJarResourcePath, locations); 098 } 099 else { 100 return Mono.empty(); 101 } 102 })); 103 } 104 105 @Nullable 106 protected String findWebJarResourcePath(String path) { 107 int startOffset = (path.startsWith("/") ? 1 : 0); 108 int endOffset = path.indexOf('/', 1); 109 if (endOffset != -1) { 110 String webjar = path.substring(startOffset, endOffset); 111 String partialPath = path.substring(endOffset + 1); 112 String webJarPath = this.webJarAssetLocator.getFullPathExact(webjar, partialPath); 113 if (webJarPath != null) { 114 return webJarPath.substring(WEBJARS_LOCATION_LENGTH); 115 } 116 } 117 return null; 118 } 119 120}