mirror of
https://gitee.com/jmix/cuba.git
synced 2024-11-30 02:07:41 +08:00
UberJar does not server WebJAR resources #944
This commit is contained in:
parent
983bf9c1da
commit
c274e3428c
@ -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()});
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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" -> "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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user