UberJar does not server WebJAR resources #944

This commit is contained in:
Yuriy Artamonov 2018-06-08 13:48:16 +04:00
parent 983bf9c1da
commit c274e3428c
5 changed files with 567 additions and 151 deletions

View File

@ -27,7 +27,6 @@ import org.eclipse.jetty.xml.XmlConfiguration;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
@ -128,7 +127,8 @@ public class CubaJettyServer {
protected Server createServer() throws Exception {
ClassLoader serverClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader sharedClassLoader = new URLClassLoader(pathsToURLs(serverClassLoader, SHARED_CLASS_PATH_IN_JAR), serverClassLoader);
ClassLoader sharedClassLoader = new UberJarURLClassLoader("Shared",
pathsToURLs(serverClassLoader, SHARED_CLASS_PATH_IN_JAR), serverClassLoader);
Server server;
if (jettyConfUrl != null) {
XmlConfiguration xmlConfiguration = new XmlConfiguration(jettyConfUrl);
@ -147,33 +147,33 @@ public class CubaJettyServer {
coreContextPath = contextPath + "-core";
}
}
handlers.add(createAppContext(serverClassLoader, sharedClassLoader, CORE_PATH_IN_JAR, coreContextPath));
handlers.add(createAppContext("Core", serverClassLoader, sharedClassLoader, CORE_PATH_IN_JAR, coreContextPath));
}
if (hasWebApp(serverClassLoader)) {
handlers.add(createAppContext(serverClassLoader, sharedClassLoader, WEB_PATH_IN_JAR, contextPath));
handlers.add(createAppContext("Web", serverClassLoader, sharedClassLoader, WEB_PATH_IN_JAR, contextPath));
}
if (hasPortalApp(serverClassLoader)) {
String portalContextPath = contextPath;
if (isSingleJar(serverClassLoader)) {
portalContextPath = this.portalContextPath;
}
handlers.add(createAppContext(serverClassLoader, sharedClassLoader, PORTAL_PATH_IN_JAR, portalContextPath));
handlers.add(createAppContext("Portal", serverClassLoader, sharedClassLoader, PORTAL_PATH_IN_JAR, portalContextPath));
}
if (hasFrontApp(serverClassLoader)) {
handlers.add(createFrontAppContext(serverClassLoader, sharedClassLoader));
}
HandlerCollection handlerCollection = new HandlerCollection();
handlerCollection.setHandlers(handlers.toArray(new Handler[handlers.size()]));
handlerCollection.setHandlers(handlers.toArray(new Handler[0]));
server.setHandler(handlerCollection);
return server;
}
protected WebAppContext createAppContext(ClassLoader serverClassLoader, ClassLoader sharedClassLoader,
protected WebAppContext createAppContext(String name, ClassLoader serverClassLoader, ClassLoader sharedClassLoader,
String appPathInJar, String contextPath) throws URISyntaxException {
ClassLoader appClassLoader = new URLClassLoader(pathsToURLs(serverClassLoader, getAppClassesPath(appPathInJar)), sharedClassLoader);
ClassLoader appClassLoader = new UberJarURLClassLoader(name,
pathsToURLs(serverClassLoader, getAppClassesPath(appPathInJar)), sharedClassLoader);
WebAppContext appContext = new WebAppContext();
appContext.setConfigurations(new Configuration[]{new WebXmlConfiguration(), createEnvConfiguration()});
@ -185,9 +185,9 @@ public class CubaJettyServer {
return appContext;
}
protected WebAppContext createFrontAppContext(ClassLoader serverClassLoader, ClassLoader sharedClassLoader) throws URISyntaxException {
ClassLoader frontClassLoader = new URLClassLoader(pathsToURLs(serverClassLoader, getAppClassesPath(FRONT_PATH_IN_JAR)), sharedClassLoader);
ClassLoader frontClassLoader = new UberJarURLClassLoader("Front",
pathsToURLs(serverClassLoader, getAppClassesPath(FRONT_PATH_IN_JAR)), sharedClassLoader);
WebAppContext frontContext = new WebAppContext();
frontContext.setConfigurations(new Configuration[]{new WebXmlConfiguration()});

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.uberjar;
import java.net.URL;
import java.net.URLClassLoader;
class UberJarURLClassLoader extends URLClassLoader {
private final String name;
public UberJarURLClassLoader(String name, URL[] urls, ClassLoader parent) {
super(urls, parent);
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name + " " + super.toString();
}
}

View File

@ -0,0 +1,389 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.web.sys;
import com.haulmont.cuba.core.sys.AppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.webjars.MultipleMatchesException;
import org.webjars.urlprotocols.UrlProtocolHandler;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
// CAUTION: copied from WebJarAssetLocator with changes
public class CubaWebJarAssetLocator {
private static final Logger log = LoggerFactory.getLogger(CubaWebJarAssetLocator.class);
public static final String STANDARD_WEBJARS_PATH_PREFIX = "META-INF/resources/webjars";
public static final String UBERJAR_WEBJARS_PATH_PREFIX = "LIB-INF/shared/META-INF/resources/webjars";
private static final Comparator<String> IGNORE_CASE_COMPARATOR = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
};
public static boolean isUberJar() {
return Boolean.parseBoolean(AppContext.getProperty("cuba.uberJar"));
}
private static String getAssetsPath() {
if (isUberJar()) {
return UBERJAR_WEBJARS_PATH_PREFIX;
}
return STANDARD_WEBJARS_PATH_PREFIX;
}
/**
* The path to where webjar resources live.
*/
public static final String WEBJARS_PATH_PREFIX = getAssetsPath();
private static Pattern WEBJAR_EXTRACTOR_PATTERN = Pattern.compile(WEBJARS_PATH_PREFIX + "/([^/]*)/([^/]*)/(.*)$");
private static void aggregateFile(final File file, final Set<String> aggregatedChildren, final Pattern filterExpr) {
final String path = file.getPath().replace('\\', '/');
final String relativePath = path.substring(path.indexOf(WEBJARS_PATH_PREFIX));
if (filterExpr.matcher(relativePath).matches()) {
aggregatedChildren.add(relativePath);
}
}
/*
* Return all {@link URL}s defining {@value WebJarAssetLocator#WEBJARS_PATH_PREFIX} directory, either identifying JAR files or plain directories.
*/
private static Set<URL> listParentURLsWithResource(final ClassLoader[] classLoaders, final String resource) {
final Set<URL> urls = new HashSet<>();
for (final ClassLoader classLoader : classLoaders) {
try {
final Enumeration<URL> enumeration = classLoader.getResources(resource);
while (enumeration.hasMoreElements()) {
urls.add(enumeration.nextElement());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return urls;
}
/*
* Return all of the resource paths filtered given an expression and a list
* of class loaders.
*/
private static Set<String> getAssetPaths(final Pattern filterExpr,
final ClassLoader... classLoaders) {
final Set<String> assetPaths = new HashSet<>();
final Set<URL> urls = listParentURLsWithResource(classLoaders, WEBJARS_PATH_PREFIX);
// Haulmont API
if (isUberJar()) {
UberJarUrlProtocolHandler urlProtocolHandler = new UberJarUrlProtocolHandler(WEBJARS_PATH_PREFIX);
for (final URL url : urls) {
Set<String> assetPathSet = urlProtocolHandler.getAssetPaths(url, filterExpr, classLoaders);
if (assetPathSet != null) {
assetPaths.addAll(assetPathSet);
break;
}
}
} else {
ServiceLoader<UrlProtocolHandler> urlProtocolHandlers = ServiceLoader.load(UrlProtocolHandler.class);
for (final URL url : urls) {
for (UrlProtocolHandler urlProtocolHandler : urlProtocolHandlers) {
if (urlProtocolHandler.accepts(url.getProtocol())) {
Set<String> assetPathSet = urlProtocolHandler.getAssetPaths(url, filterExpr, classLoaders);
if (assetPathSet != null) {
assetPaths.addAll(assetPathSet);
break;
}
}
}
}
}
return assetPaths;
}
/**
* Return a map that can be used to perform index lookups of partial file
* paths. This index constitutes a key that is the reverse form of the path
* it relates to. Thus if a partial lookup needs to be performed from the
* rightmost path components then the key to access can be expressed easily
* e.g. the path "a/b" would be the map tuple "b/a" -&gt; "a/b". If we need to
* look for an asset named "a" without knowing the full path then we can
* perform a partial lookup on the sorted map.
*
* @param filterExpr the regular expression to be used to filter resources that
* will be included in the index.
* @param classLoaders the class loaders to be considered for loading the resources
* from.
* @return the index.
*/
public static SortedMap<String, String> getFullPathIndex(
final Pattern filterExpr, final ClassLoader... classLoaders) {
final Set<String> assetPaths = getAssetPaths(filterExpr, classLoaders);
final SortedMap<String, String> assetPathIndex = new TreeMap<>();
for (final String assetPath : assetPaths) {
assetPathIndex.put(reversePath(assetPath), assetPath);
}
return assetPathIndex;
}
/*
* Make paths like aa/bb/cc = cc/bb/aa.
*/
private static String reversePath(String assetPath) {
final String[] assetPathComponents = assetPath.split("/");
final StringBuilder reversedAssetPath = new StringBuilder();
for (int i = assetPathComponents.length - 1; i >= 0; --i) {
reversedAssetPath.append(assetPathComponents[i]);
reversedAssetPath.append('/');
}
return reversedAssetPath.toString();
}
private final SortedMap<String, String> fullPathIndex;
private final ClassLoader resourceClassLoader;
/**
* Convenience constructor that will form a locator for all resources on the
* current class path.
*/
public CubaWebJarAssetLocator() {
this.resourceClassLoader = Thread.currentThread().getContextClassLoader();
this.fullPathIndex = getInitialIndex(resourceClassLoader);
}
// called during initialization
// Haulmont API
private static SortedMap<String, String> getInitialIndex(ClassLoader contextClassLoader) {
log.debug("Loading WebJAR index with class loader {} from {}", contextClassLoader, CubaWebJarAssetLocator.WEBJARS_PATH_PREFIX);
SortedMap<String, String> index = getFullPathIndex(Pattern.compile(".*"), contextClassLoader);
log.debug("Loaded {} WebJAR paths", index.size());
return index;
}
private void throwNotFoundException(final String partialPath) {
throw new IllegalArgumentException(
partialPath
+ " could not be found. Make sure you've added the corresponding WebJar and please check for typos."
);
}
/**
* Given a distinct path within the WebJar index passed in return the full
* path of the resource.
*
* @param partialPath the path to return e.g. "jquery.js" or "abc/someother.js".
* This must be a distinct path within the index passed in.
* @return a fully qualified path to the resource.
*/
public String getFullPath(final String partialPath) {
return getFullPath(fullPathIndex, partialPath);
}
/**
* Returns the full path of an asset within a specific WebJar
*
* @param webjar The id of the WebJar to search
* @param partialPath The partial path to look for
* @return a fully qualified path to the resource
*/
public String getFullPath(final String webjar, final String partialPath) {
return getFullPath(filterPathIndexByPrefix(fullPathIndex, WEBJARS_PATH_PREFIX + "/" + webjar + "/"), partialPath);
}
/**
* Returns the full path of an asset within a specific WebJar
*
* @param webjar The id of the WebJar to search
* @param exactPath The exact path of the file within the WebJar
* @return a fully qualified path to the resource
*/
public String getFullPathExact(final String webjar, final String exactPath) {
String maybeVersion = getWebJars().get(webjar);
if (maybeVersion != null) {
String fullPath = WEBJARS_PATH_PREFIX + "/" + webjar + "/" + maybeVersion + "/" + exactPath;
if (getFullPathIndex().values().contains(fullPath)) {
return fullPath;
}
}
return null;
}
public String getFullPath(SortedMap<String, String> pathIndex, String partialPath) {
if (partialPath.charAt(0) == '/') {
partialPath = partialPath.substring(1);
}
final String reversePartialPath = reversePath(partialPath);
final SortedMap<String, String> fullPathTail = pathIndex.tailMap(reversePartialPath);
if (fullPathTail.size() == 0) {
if (log.isTraceEnabled()) {
printNotFoundTraceInfo(pathIndex, partialPath);
}
throwNotFoundException(partialPath);
}
final Iterator<Map.Entry<String, String>> fullPathTailIter = fullPathTail
.entrySet().iterator();
final Map.Entry<String, String> fullPathEntry = fullPathTailIter.next();
if (!fullPathEntry.getKey().startsWith(reversePartialPath)) {
if (log.isTraceEnabled()) {
printNotFoundTraceInfo(pathIndex, partialPath);
}
throwNotFoundException(partialPath);
}
final String fullPath = fullPathEntry.getValue();
if (fullPathTailIter.hasNext()) {
List<String> matches = null;
while (fullPathTailIter.hasNext()) {
Map.Entry<String, String> next = fullPathTailIter.next();
if (next.getKey().startsWith(reversePartialPath)) {
if (matches == null) {
matches = new ArrayList<>();
}
matches.add(next.getValue());
} else {
break;
}
}
if (matches != null) {
matches.add(fullPath);
throw new MultipleMatchesException(
"Multiple matches found for "
+ partialPath
+ ". Please provide a more specific path, for example by including a version number.", matches);
}
}
return fullPath;
}
public SortedMap<String, String> filterPathIndexByPrefix(SortedMap<String, String> pathIndex, String prefix) {
SortedMap<String, String> filteredPathIndex = new TreeMap<>();
for (String key : pathIndex.keySet()) {
String value = pathIndex.get(key);
if (value.startsWith(prefix)) {
filteredPathIndex.put(key, value);
}
}
return filteredPathIndex;
}
public SortedMap<String, String> getFullPathIndex() {
return fullPathIndex;
}
public Set<String> listAssets() {
return listAssets("");
}
/**
* List assets within a folder.
*
* @param folderPath the root path to the folder.
* @return a set of folder paths that match.
*/
public Set<String> listAssets(final String folderPath) {
final Collection<String> allAssets = fullPathIndex.values();
final Set<String> assets = new TreeSet<String>(IGNORE_CASE_COMPARATOR);
final String prefix = WEBJARS_PATH_PREFIX + (!folderPath.startsWith("/") ? "/" : "") + folderPath;
for (final String asset : allAssets) {
if (asset.startsWith(folderPath) || asset.startsWith(prefix)) {
assets.add(asset);
}
}
return assets;
}
/**
* @return A map of the WebJars based on the files in the CLASSPATH where the key is the artifactId and the value is the version
*/
public Map<String, String> getWebJars() {
Map<String, String> webjars = new HashMap<>();
for (String webjarFile : fullPathIndex.values()) {
Map.Entry<String, String> webjar = getWebJar(webjarFile);
if ((webjar != null) && (!webjars.containsKey(webjar.getKey()))) {
webjars.put(webjar.getKey(), webjar.getValue());
}
}
return webjars;
}
/**
* @param path The full WebJar path
* @return A WebJar tuple (Entry) with key = id and value = version
*/
public static Map.Entry<String, String> getWebJar(String path) {
Matcher matcher = WEBJAR_EXTRACTOR_PATTERN.matcher(path);
if (matcher.find()) {
String id = matcher.group(1);
String version = matcher.group(2);
return new AbstractMap.SimpleEntry<>(id, version);
} else {
// not a legal WebJar file format
return null;
}
}
// Haulmont API
private void printNotFoundTraceInfo(SortedMap<String, String> pathIndex, String partialPath) {
String fullPathIndexLog = getFullPathIndex().entrySet().stream()
.map(p -> p.getKey() + " : " + p.getValue())
.collect(Collectors.joining("\n"));
String pathIndexLog = pathIndex.entrySet().stream()
.map(p -> p.getKey() + " : " + p.getValue())
.collect(Collectors.joining("\n"));
log.trace("Unable to find WebJar resource: {}\n " +
"ClassLoader: {}\n" +
"WebJar full path index:\n {}\n" +
"Path index: \n{}",
partialPath,
this.resourceClassLoader,
fullPathIndexLog,
pathIndexLog);
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.web.sys;
import org.apache.commons.io.IOUtils;
import org.webjars.urlprotocols.UrlProtocolHandler;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.regex.Pattern;
public class UberJarUrlProtocolHandler implements UrlProtocolHandler {
private String assetsPath;
public UberJarUrlProtocolHandler(String assetsPath) {
this.assetsPath = assetsPath;
}
@Override
public boolean accepts(String protocol) {
return "jar".equals(protocol);
}
@Override
public Set<String> getAssetPaths(URL url, Pattern filterExpr, ClassLoader... classLoaders) {
HashSet<String> assetPaths = new HashSet<>();
String[] segments = getSegments(url.getPath());
JarFile jarFile = null;
JarInputStream jarInputStream = null;
try {
for (int i = 0; i < segments.length - 1; i++) {
String segment = segments[i];
if (jarFile == null) {
File file = new File(URI.create(segment));
jarFile = new JarFile(file);
if (i == segments.length - 2) {
jarInputStream = new JarInputStream(new FileInputStream(file));
}
} else {
jarInputStream = new JarInputStream(jarFile.getInputStream(jarFile.getEntry(segment)));
}
}
if (jarInputStream == null) {
throw new RuntimeException("Unable to identify target Jar");
}
JarEntry jarEntry = jarInputStream.getNextJarEntry();
while (jarEntry != null) {
String assetPathCandidate = jarEntry.getName();
if (!jarEntry.isDirectory()
&& assetPathCandidate.startsWith(assetsPath)
&& filterExpr.matcher(assetPathCandidate).matches()) {
assetPaths.add(assetPathCandidate);
}
jarEntry = jarInputStream.getNextJarEntry();
}
} catch (IOException e) {
throw new RuntimeException("Unable to read resource", e);
} finally {
IOUtils.closeQuietly(jarFile);
IOUtils.closeQuietly(jarInputStream);
}
return assetPaths;
}
private String[] getSegments(String path) {
String [] parts = path.split("!/");
ArrayList<String> segments = new ArrayList<>(parts.length);
StringBuilder outer = new StringBuilder(parts[0]);
for (int i = 1; i < parts.length; ++i) {
if (segments.isEmpty()) {
if (isArchive(outer.toString())) {
segments.add(outer.toString());
segments.add(parts[i]);
} else {
outer.append("!/").append(parts[i]);
}
} else {
segments.add(parts[i]);
}
}
return segments.toArray(new String[0]);
}
private boolean isArchive(String path) {
Path candidate = Paths.get(URI.create(path));
return Files.isReadable(candidate) && Files.isRegularFile(candidate);
}
}

View File

@ -16,17 +16,9 @@
package com.haulmont.cuba.web.sys;
import com.haulmont.cuba.core.sys.AppContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.webjars.MultipleMatchesException;
import org.webjars.WebJarAssetLocator;
import javax.annotation.concurrent.ThreadSafe;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Utility class for WebJar resources management.
@ -34,16 +26,7 @@ import java.util.stream.Collectors;
@ThreadSafe
public final class WebJarResourceUtils {
private static final Logger log = LoggerFactory.getLogger(WebJarResourceUtils.class);
// Thread safe
public static final WebJarAssetLocator locator = new WebJarAssetLocator(
WebJarAssetLocator.getFullPathIndex(Pattern.compile(".*"),
WebJarResourceUtils.class.getClassLoader())
); // use web application class loader instead of shared
public static final String WEBJARS_PATH_PREFIX = "META-INF/resources/webjars";
public static final String UBERJAR_WEBJARS_PATH_PREFIX = "LIB-INF/shared/META-INF/resources/webjars";
private static final CubaWebJarAssetLocator locator = new CubaWebJarAssetLocator();
public static final String VAADIN_PREFIX = "VAADIN/";
public static final String CLASSPATH_WEBJAR_PREFIX = "META-INF/resources/";
@ -52,22 +35,17 @@ public final class WebJarResourceUtils {
private WebJarResourceUtils() {
}
public static CubaWebJarAssetLocator getLocator() {
return locator;
}
/**
* @param webjar The id of the WebJar to search
* @param partialPath The partial path to look for
* @return a fully qualified path to the resource
*/
public static String getWebJarPath(String webjar, String partialPath) {
SortedMap<String, String> index = locator.getFullPathIndex();
String searchPath;
if (isUberJar()) {
searchPath = UBERJAR_WEBJARS_PATH_PREFIX + "/" + webjar + "/";
} else {
searchPath = WEBJARS_PATH_PREFIX + "/" + webjar + "/";
}
return getFullPath(filterPathIndexByPrefix(index, searchPath), partialPath);
return getLocator().getFullPath(webjar, partialPath);
}
/**
@ -75,7 +53,7 @@ public final class WebJarResourceUtils {
* @return a fully qualified path to the resource.
*/
public static String getWebJarPath(String partialPath) {
return locator.getFullPath(partialPath);
return getLocator().getFullPath(partialPath);
}
/**
@ -83,7 +61,7 @@ public final class WebJarResourceUtils {
* @return path for web page with VAADIN subfolder
*/
public static String translateToWebPath(String fullWebJarPath) {
if (isUberJar()) {
if (CubaWebJarAssetLocator.isUberJar()) {
return StringUtils.replace(fullWebJarPath, UBERJAR_CLASSPATH_WEBJAR_PREFIX, VAADIN_PREFIX);
}
return StringUtils.replace(fullWebJarPath, CLASSPATH_WEBJAR_PREFIX, VAADIN_PREFIX);
@ -94,118 +72,9 @@ public final class WebJarResourceUtils {
* @return WebJar resource location in ClassPath
*/
public static String translateToClassPath(String fullVaadinPath) {
if (isUberJar()) {
if (CubaWebJarAssetLocator.isUberJar()) {
return StringUtils.replace(fullVaadinPath, VAADIN_PREFIX, UBERJAR_CLASSPATH_WEBJAR_PREFIX);
}
return StringUtils.replace(fullVaadinPath, VAADIN_PREFIX, CLASSPATH_WEBJAR_PREFIX);
}
private static boolean isUberJar() {
return Boolean.parseBoolean(AppContext.getProperty("cuba.uberJar"));
}
// CAUTION: copied from WebJarAssetLocator
private static SortedMap<String, String> filterPathIndexByPrefix(SortedMap<String, String> pathIndex, String prefix) {
SortedMap<String, String> filteredPathIndex = new TreeMap<>();
for (String key : pathIndex.keySet()) {
String value = pathIndex.get(key);
if (value.startsWith(prefix)) {
filteredPathIndex.put(key, value);
}
}
return filteredPathIndex;
}
// CAUTION: copied from WebJarAssetLocator
private static String getFullPath(SortedMap<String, String> pathIndex, String partialPath) {
if (partialPath.charAt(0) == '/') {
partialPath = partialPath.substring(1);
}
final String reversePartialPath = reversePath(partialPath);
final SortedMap<String, String> fullPathTail = pathIndex.tailMap(reversePartialPath);
if (fullPathTail.size() == 0) {
if (log.isTraceEnabled()) {
printNotFoundTraceInfo(pathIndex, partialPath);
}
throwNotFoundException(partialPath);
}
final Iterator<Map.Entry<String, String>> fullPathTailIter = fullPathTail
.entrySet().iterator();
final Map.Entry<String, String> fullPathEntry = fullPathTailIter.next();
if (!fullPathEntry.getKey().startsWith(reversePartialPath)) {
if (log.isTraceEnabled()) {
printNotFoundTraceInfo(pathIndex, partialPath);
}
throwNotFoundException(partialPath);
}
final String fullPath = fullPathEntry.getValue();
if (fullPathTailIter.hasNext()) {
List<String> matches = null;
while (fullPathTailIter.hasNext()) {
Map.Entry<String, String> next = fullPathTailIter.next();
if (next.getKey().startsWith(reversePartialPath)) {
if (matches == null) {
matches = new ArrayList<>();
}
matches.add(next.getValue());
} else {
break;
}
}
if (matches != null) {
matches.add(fullPath);
throw new MultipleMatchesException(
"Multiple matches found for "
+ partialPath
+ ". Please provide a more specific path, for example by including a version number.", matches);
}
}
return fullPath;
}
private static void printNotFoundTraceInfo(SortedMap<String, String> pathIndex, String partialPath) {
String fullPathIndexLog = locator.getFullPathIndex().entrySet().stream()
.map(p -> p.getKey() + " : " + p.getValue())
.collect(Collectors.joining("\n"));
String pathIndexLog = pathIndex.entrySet().stream()
.map(p -> p.getKey() + " : " + p.getValue())
.collect(Collectors.joining("\n"));
log.trace("Unable to find WebJar resource: {}\n " +
"ClassLoader: {}" +
"WebJar full path index:\n {}\n" +
"Path index: \n{}",
partialPath,
WebJarResourceUtils.class.getClassLoader(),
fullPathIndexLog,
pathIndexLog);
}
// CAUTION: copied from WebJarAssetLocator
private static void throwNotFoundException(final String partialPath) {
throw new IllegalArgumentException(
partialPath
+ " could not be found. Make sure you've added the corresponding WebJar and please check for typos."
);
}
// CAUTION: copied from WebJarAssetLocator
private static String reversePath(String assetPath) {
final String[] assetPathComponents = assetPath.split("/");
final StringBuilder reversedAssetPath = new StringBuilder();
for (int i = assetPathComponents.length - 1; i >= 0; --i) {
reversedAssetPath.append(assetPathComponents[i]);
reversedAssetPath.append('/');
}
return reversedAssetPath.toString();
}
}