Skip to content

Commit

Permalink
CAMEL-16127: Revisit PackageScanResourceResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
lburgazzoli committed Feb 2, 2021
1 parent fc8a667 commit 3b4fb1f
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 91 deletions.
Expand Up @@ -17,7 +17,12 @@
package org.apache.camel.spi;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.apache.camel.StaticService;

Expand Down Expand Up @@ -64,13 +69,11 @@ public interface PackageScanResourceResolver extends StaticService {
* Notice when using wildcards, then there is additional overhead as the classpath is scanned, where as if you
* specific the exact name for each XML file is faster as no classpath scanning is needed.
*
* <b>Important:</b> Make sure to close the returned input streams after use.
*
* @param location the location (support ANT style patterns, eg routes/camel-*.xml)
* @return the found resources, or an empty set if no resources found
* @return the found resources, or an empty collection if no resources found
* @throws Exception can be thrown during scanning for resources.
*/
Set<InputStream> findResources(String location) throws Exception;
Collection<Resource> findResources(String location) throws Exception;

/**
* Finds the resource names from the given location.
Expand All @@ -83,10 +86,47 @@ public interface PackageScanResourceResolver extends StaticService {
* Notice when using wildcards, then there is additional overhead as the classpath is scanned, where as if you
* specific the exact name for each XML file is faster as no classpath scanning is needed.
*
* @param location the location (support ANT style patterns, eg routes/camel-*.xml)
* @return the found resource names, or an empty set if no resources found
* @throws Exception can be thrown during scanning for resources.
* @param location the location (support ANT style patterns, eg routes/camel-*.xml)
* @return the found resource names, or an empty collection if no resources found
* @throws Exception can be thrown during scanning for resources.
* @deprecated use {@link #findResources(String)}
*/
Set<String> findResourceNames(String location) throws Exception;
@Deprecated
default Collection<String> findResourceNames(String location) throws Exception {
return findResources(location)
.stream()
.map(Resource::getLocation)
.collect(Collectors.toCollection(TreeSet::new));
}

/**
* Finds the resources from the given location.
*
* The location can be prefixed with either file: or classpath: to look in either file system or classpath. By
* default classpath is assumed if no prefix is specified.
*
* Wildcards is supported using a ANT pattern style paths, such as classpath:&#42;&#42;/&#42;camel&#42;.xml
*
* Notice when using wildcards, then there is additional overhead as the classpath is scanned, where as if you
* specific the exact name for each XML file is faster as no classpath scanning is needed.
*
* <b>Important:</b> Make sure to close the returned input streams after use.
*
* @param location the location (support ANT style patterns, eg routes/camel-*.xml)
* @return the found resources, or an empty collection if no resources found
* @throws Exception can be thrown during scanning for resources.
* @deprecated use {@link #findResources(String)}
*/
@Deprecated
default Collection<InputStream> findResourceStreams(String location) throws Exception {
final Collection<Resource> values = findResources(location);
final List<InputStream> answer = new ArrayList<>(values.size());

for (Resource resource : values) {
answer.add(resource.getInputStream());
}

return answer;
}

}
35 changes: 35 additions & 0 deletions core/camel-api/src/main/java/org/apache/camel/spi/Resource.java
@@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.camel.spi;

import java.io.IOException;
import java.io.InputStream;

/**
* Describe a resource, such as a file or class path resource.
*/
public interface Resource {
/**
* The location of the resource.
*/
String getLocation();

/**
* Returns an input stream that reads from the underlying resource.
*/
InputStream getInputStream() throws IOException;
}
Expand Up @@ -18,29 +18,31 @@

import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.apache.camel.CamelContext;
import org.apache.camel.ExtendedCamelContext;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.spi.ComponentNameResolver;
import org.apache.camel.spi.Resource;

public class DefaultComponentNameResolver implements ComponentNameResolver {

public static final String RESOURCE_PATH = "META-INF/services/org/apache/camel/component/*";

@Override
public Set<String> resolveNames(CamelContext camelContext) {
// remove leading path to only keep name
Set<String> sorted = new TreeSet<>();

try {
Set<String> locations = camelContext.adapt(ExtendedCamelContext.class).getPackageScanResourceResolver()
.findResourceNames(RESOURCE_PATH);
locations.forEach(l -> sorted.add(l.substring(l.lastIndexOf('/') + 1)));
return camelContext.adapt(ExtendedCamelContext.class)
.getPackageScanResourceResolver()
.findResources(RESOURCE_PATH)
.stream()
.map(Resource::getLocation)
// remove leading path to only keep name
.map(l -> l.substring(l.lastIndexOf('/') + 1))
.collect(Collectors.toCollection(TreeSet::new));
} catch (Exception e) {
throw new RuntimeCamelException(e);
}

return sorted;
}
}
Expand Up @@ -27,23 +27,25 @@
import java.net.URLConnection;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.stream.Collectors;

import org.apache.camel.CamelContextAware;
import org.apache.camel.NonManagedService;
import org.apache.camel.spi.PackageScanResourceResolver;
import org.apache.camel.spi.Resource;
import org.apache.camel.support.ResourceHelper;
import org.apache.camel.util.AntPathMatcher;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.KeyValueHolder;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.function.ThrowingSupplier;

/**
* Default implement of {@link org.apache.camel.spi.PackageScanResourceResolver}
Expand All @@ -54,19 +56,15 @@ public class DefaultPackageScanResourceResolver extends BasePackageScanResolver
private static final AntPathMatcher PATH_MATCHER = AntPathMatcher.INSTANCE;

@Override
public Set<String> findResourceNames(String location) throws Exception {
Set<KeyValueHolder<String, InputStream>> answer = new LinkedHashSet<>();
public Collection<Resource> findResources(String location) throws Exception {
Set<Resource> answer = new HashSet<>();
doFindResources(location, answer);
return answer.stream().map(KeyValueHolder::getKey).collect(Collectors.toSet());
}

public Set<InputStream> findResources(String location) throws Exception {
Set<KeyValueHolder<String, InputStream>> answer = new LinkedHashSet<>();
doFindResources(location, answer);
return answer.stream().map(KeyValueHolder::getValue).collect(Collectors.toSet());
return answer;
}

protected void doFindResources(String location, Set<KeyValueHolder<String, InputStream>> resources) throws Exception {
protected void doFindResources(String location, Set<Resource> resources)
throws Exception {
// if its a pattern then we need to scan its root path and find
// all matching resources using the sub pattern
if (PATH_MATCHER.isPattern(location)) {
Expand All @@ -87,24 +85,41 @@ protected void doFindResources(String location, Set<KeyValueHolder<String, Input
}
} else {
// its a single resource so load it directly
InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(getCamelContext(), location);
resources.add(new KeyValueHolder<>(location, is));
resources.add(new DefaultResource(
location,
new ThrowingSupplier<InputStream, IOException>() {
@Override
public InputStream get() throws IOException {
return ResourceHelper.resolveMandatoryResourceAsInputStream(getCamelContext(), location);
}
}));
}
}

protected void findInFileSystem(File dir, Set<KeyValueHolder<String, InputStream>> resources, String subPattern)
protected void findInFileSystem(
File dir,
Set<Resource> resources,
String subPattern)
throws Exception {
ResourceHelper.findInFileSystem(dir.toPath(), subPattern).forEach(f -> {
try {
String location = f.toString();
resources.add(new KeyValueHolder<>(location, Files.newInputStream(f)));
} catch (IOException e) {
// ignore
}
});

for (Path path : ResourceHelper.findInFileSystem(dir.toPath(), subPattern)) {

resources.add(new DefaultResource(
path.toString(),
new ThrowingSupplier<InputStream, IOException>() {
@Override
public InputStream get() throws IOException {
return Files.newInputStream(path);
}
}));
}
}

protected void findInClasspath(String packageName, Set<KeyValueHolder<String, InputStream>> resources, String subPattern) {
protected void findInClasspath(
String packageName,
Set<Resource> resources,
String subPattern) {

packageName = packageName.replace('.', '/');
// If the URL is a jar, the URLClassloader.getResources() seems to require a trailing slash.
// The trailing slash is harmless for other URLs
Expand All @@ -120,8 +135,11 @@ protected void findInClasspath(String packageName, Set<KeyValueHolder<String, In
}

protected void doFind(
String packageName, ClassLoader classLoader, Set<KeyValueHolder<String, InputStream>> resources,
String packageName,
ClassLoader classLoader,
Set<Resource> resources,
String subPattern) {

Enumeration<URL> urls;
try {
urls = getResources(classLoader, packageName);
Expand Down Expand Up @@ -222,8 +240,12 @@ protected void doFind(
* @param urlPath the url of the jar file to be examined for classes
*/
private void loadImplementationsInJar(
String packageName, String subPattern, InputStream stream,
String urlPath, Set<KeyValueHolder<String, InputStream>> resources) {
String packageName,
String subPattern,
InputStream stream,
String urlPath,
Set<Resource> resources) {

List<String> entries = new ArrayList<>();

JarInputStream jarStream = null;
Expand Down Expand Up @@ -254,11 +276,14 @@ private void loadImplementationsInJar(
boolean match = PATH_MATCHER.match(subPattern, shortName);
log.debug("Found resource: {} matching pattern: {} -> {}", shortName, subPattern, match);
if (match) {
// use fqn name to load resource
InputStream is = getCamelContext().getClassResolver().loadResourceAsStream(name);
if (is != null) {
resources.add(new KeyValueHolder<>(name, is));
}
resources.add(new DefaultResource(
name,
new ThrowingSupplier<InputStream, IOException>() {
@Override
public InputStream get() throws IOException {
return getCamelContext().getClassResolver().loadResourceAsStream(name);
}
}));
}
}
}
Expand All @@ -275,7 +300,10 @@ private void loadImplementationsInJar(
* @param location a File object representing a directory
*/
private void loadImplementationsInDirectory(
String subPattern, String parent, File location, Set<KeyValueHolder<String, InputStream>> resources)
String subPattern,
String parent,
File location,
Set<Resource> resources)
throws FileNotFoundException {
File[] files = location.listFiles();
if (files == null || files.length == 0) {
Expand All @@ -296,8 +324,14 @@ private void loadImplementationsInDirectory(
boolean match = PATH_MATCHER.match(subPattern, name);
log.debug("Found resource: {} matching pattern: {} -> {}", name, subPattern, match);
if (match) {
InputStream is = new FileInputStream(file);
resources.add(new KeyValueHolder<>(name, is));
resources.add(new DefaultResource(
name,
new ThrowingSupplier<InputStream, IOException>() {
@Override
public InputStream get() throws IOException {
return new FileInputStream(file);
}
}));
}
}
}
Expand Down

0 comments on commit 3b4fb1f

Please sign in to comment.