This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
commit 2e57fd7f4eadc7f0d6269417ddba8f1bcbdb6a99 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Sat Mar 14 13:04:48 2020 +0100 CAMEL-14598: Add ComponentNameResolver to discover components on the classpath. --- .../org/apache/camel/ExtendedCamelContext.java | 11 +++++ .../apache/camel/spi/ComponentNameResolver.java | 35 ++++++++++++++ .../camel/spi/PackageScanResourceResolver.java | 17 +++++++ .../camel/impl/engine/AbstractCamelContext.java | 20 ++++++++ .../impl/engine/DefaultComponentNameResolver.java | 45 ++++++++++++++++++ .../engine/DefaultPackageScanResourceResolver.java | 40 ++++++++++------ .../camel/impl/engine/SimpleCamelContext.java | 6 +++ .../camel/impl/lw/ImmutableCamelContext.java | 11 +++++ .../impl/lw/RuntimeImmutableCamelContext.java | 13 ++++++ .../engine/DefaultComponentNameResolverTest.java | 54 ++++++++++++++++++++++ .../org/apache/camel/main/BaseMainSupport.java | 4 +- 11 files changed, 241 insertions(+), 15 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java index 55f92bb..8b53752 100644 --- a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java +++ b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java @@ -30,6 +30,7 @@ import org.apache.camel.spi.BeanIntrospection; import org.apache.camel.spi.BeanProcessorFactory; import org.apache.camel.spi.BeanProxyFactory; import org.apache.camel.spi.CamelBeanPostProcessor; +import org.apache.camel.spi.ComponentNameResolver; import org.apache.camel.spi.ComponentResolver; import org.apache.camel.spi.ConfigurerResolver; import org.apache.camel.spi.DataFormatResolver; @@ -262,6 +263,16 @@ public interface ExtendedCamelContext extends CamelContext { void setComponentResolver(ComponentResolver componentResolver); /** + * Gets the {@link ComponentNameResolver} to use. + */ + ComponentNameResolver getComponentNameResolver(); + + /** + * Sets a custom {@link ComponentNameResolver} to use. + */ + void setComponentNameResolver(ComponentNameResolver componentNameResolver); + + /** * Gets the {@link LanguageResolver} to use. */ LanguageResolver getLanguageResolver(); diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ComponentNameResolver.java b/core/camel-api/src/main/java/org/apache/camel/spi/ComponentNameResolver.java new file mode 100644 index 0000000..8454bdc --- /dev/null +++ b/core/camel-api/src/main/java/org/apache/camel/spi/ComponentNameResolver.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.util.Set; + +import org.apache.camel.CamelContext; + +/** + * Discovers which components are available on the classpath. + */ +public interface ComponentNameResolver { + + /** + * Discovers which components are available on the classpath. + * + * @param camelContext the camel context + * @return the component names on the classpath + */ + Set<String> resolveNames(CamelContext camelContext); +} diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanResourceResolver.java b/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanResourceResolver.java index 9052255..db0bc0e 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanResourceResolver.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanResourceResolver.java @@ -74,4 +74,21 @@ public interface PackageScanResourceResolver extends StaticService { */ Set<InputStream> findResources(String location) throws Exception; + /** + * Finds the resource names 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:**/*camel*.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. + * + * @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. + */ + Set<String> findResourceNames(String location) throws Exception; + } diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java index b32bbc6..c8acf1b 100644 --- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java +++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java @@ -91,6 +91,7 @@ import org.apache.camel.spi.CamelBeanPostProcessor; import org.apache.camel.spi.CamelContextNameStrategy; import org.apache.camel.spi.CamelContextTracker; import org.apache.camel.spi.ClassResolver; +import org.apache.camel.spi.ComponentNameResolver; import org.apache.camel.spi.ComponentResolver; import org.apache.camel.spi.ConfigurerResolver; import org.apache.camel.spi.DataFormat; @@ -246,6 +247,7 @@ public abstract class AbstractCamelContext extends BaseService private volatile Injector injector; private volatile CamelBeanPostProcessor beanPostProcessor; private volatile ComponentResolver componentResolver; + private volatile ComponentNameResolver componentNameResolver; private volatile LanguageResolver languageResolver; private volatile ConfigurerResolver configurerResolver; private volatile DataFormatResolver dataFormatResolver; @@ -1882,6 +1884,21 @@ public abstract class AbstractCamelContext extends BaseService this.componentResolver = doAddService(componentResolver); } + public ComponentNameResolver getComponentNameResolver() { + if (componentNameResolver == null) { + synchronized (lock) { + if (componentNameResolver == null) { + setComponentNameResolver(createComponentNameResolver()); + } + } + } + return componentNameResolver; + } + + public void setComponentNameResolver(ComponentNameResolver componentNameResolver) { + this.componentNameResolver = doAddService(componentNameResolver); + } + public LanguageResolver getLanguageResolver() { if (languageResolver == null) { synchronized (lock) { @@ -3497,6 +3514,7 @@ public abstract class AbstractCamelContext extends BaseService getFactoryFinderResolver(); getDefaultFactoryFinder(); getComponentResolver(); + getComponentNameResolver(); getDataFormatResolver(); getManagementStrategy(); getHeadersMapFactory(); @@ -4431,6 +4449,8 @@ public abstract class AbstractCamelContext extends BaseService protected abstract ComponentResolver createComponentResolver(); + protected abstract ComponentNameResolver createComponentNameResolver(); + protected abstract Registry createRegistry(); protected abstract UuidGenerator createUuidGenerator(); diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultComponentNameResolver.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultComponentNameResolver.java new file mode 100644 index 0000000..a3189de --- /dev/null +++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultComponentNameResolver.java @@ -0,0 +1,45 @@ +/* + * 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.impl.engine; + +import java.util.Set; +import java.util.TreeSet; + +import org.apache.camel.CamelContext; +import org.apache.camel.ExtendedCamelContext; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.ComponentNameResolver; + +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))); + } catch (Exception e) { + throw new RuntimeCamelException(e); + } + + return sorted; + } +} diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultPackageScanResourceResolver.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultPackageScanResourceResolver.java index a42355d..f814af6 100644 --- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultPackageScanResourceResolver.java +++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultPackageScanResourceResolver.java @@ -34,6 +34,7 @@ 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; @@ -41,6 +42,7 @@ import org.apache.camel.spi.PackageScanResourceResolver; 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; /** @@ -50,9 +52,20 @@ 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<>(); + doFindResources(location, answer); + return answer.stream().map(KeyValueHolder::getKey).collect(Collectors.toSet()); + } + public Set<InputStream> findResources(String location) throws Exception { - Set<InputStream> answer = new LinkedHashSet<>(); + Set<KeyValueHolder<String, InputStream>> answer = new LinkedHashSet<>(); + doFindResources(location, answer); + return answer.stream().map(KeyValueHolder::getValue).collect(Collectors.toSet()); + } + protected void doFindResources(String location, Set<KeyValueHolder<String, InputStream>> 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)) { @@ -63,34 +76,33 @@ public class DefaultPackageScanResourceResolver extends BasePackageScanResolver if ("file:".equals(scheme)) { // file based scanning root = root.substring(scheme.length()); - findInFileSystem(new File(root), answer, subPattern); + findInFileSystem(new File(root), resources, subPattern); } else { if ("classpath:".equals(scheme)) { root = root.substring(scheme.length()); } // assume classpath based scan from root path and find all resources - findInClasspath(root, answer, subPattern); + findInClasspath(root, resources, subPattern); } } else { // its a single resource so load it directly InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(getCamelContext(), location); - answer.add(is); + resources.add(new KeyValueHolder<>(location, is)); } - - return answer; } - protected void findInFileSystem(File dir, Set<InputStream> resources, String subPattern) throws Exception { + protected void findInFileSystem(File dir, Set<KeyValueHolder<String, InputStream>> resources, String subPattern) throws Exception { ResourceHelper.findInFileSystem(dir.toPath(), subPattern).forEach(f -> { try { - resources.add(Files.newInputStream(f)); + String location = f.toString(); + resources.add(new KeyValueHolder<>(location, Files.newInputStream(f))); } catch (IOException e) { // ignore } }); } - protected void findInClasspath(String packageName, Set<InputStream> resources, String subPattern) { + protected void findInClasspath(String packageName, Set<KeyValueHolder<String, InputStream>> 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 @@ -105,7 +117,7 @@ public class DefaultPackageScanResourceResolver extends BasePackageScanResolver } } - protected void doFind(String packageName, ClassLoader classLoader, Set<InputStream> resources, String subPattern) { + protected void doFind(String packageName, ClassLoader classLoader, Set<KeyValueHolder<String, InputStream>> resources, String subPattern) { Enumeration<URL> urls; try { urls = getResources(classLoader, packageName); @@ -207,7 +219,7 @@ public class DefaultPackageScanResourceResolver extends BasePackageScanResolver * @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<InputStream> resources) { + String urlPath, Set<KeyValueHolder<String, InputStream>> resources) { List<String> entries = new ArrayList<>(); JarInputStream jarStream = null; @@ -241,7 +253,7 @@ public class DefaultPackageScanResourceResolver extends BasePackageScanResolver // use fqn name to load resource InputStream is = getCamelContext().getClassResolver().loadResourceAsStream(name); if (is != null) { - resources.add(is); + resources.add(new KeyValueHolder<>(name, is)); } } } @@ -260,7 +272,7 @@ public class DefaultPackageScanResourceResolver extends BasePackageScanResolver * <i>parent</i> would be <i>org/apache</i> * @param location a File object representing a directory */ - private void loadImplementationsInDirectory(String subPattern, String parent, File location, Set<InputStream> resources) throws FileNotFoundException { + private void loadImplementationsInDirectory(String subPattern, String parent, File location, Set<KeyValueHolder<String, InputStream>> resources) throws FileNotFoundException { File[] files = location.listFiles(); if (files == null || files.length == 0) { return; @@ -281,7 +293,7 @@ public class DefaultPackageScanResourceResolver extends BasePackageScanResolver log.debug("Found resource: {} matching pattern: {} -> {}", name, subPattern, match); if (match) { InputStream is = new FileInputStream(file); - resources.add(is); + resources.add(new KeyValueHolder<>(name, is)); } } } diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java index db8eed0..e6cd59f 100644 --- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java +++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java @@ -39,6 +39,7 @@ import org.apache.camel.spi.BeanProxyFactory; import org.apache.camel.spi.CamelBeanPostProcessor; import org.apache.camel.spi.CamelContextNameStrategy; import org.apache.camel.spi.ClassResolver; +import org.apache.camel.spi.ComponentNameResolver; import org.apache.camel.spi.ComponentResolver; import org.apache.camel.spi.ConfigurerResolver; import org.apache.camel.spi.DataFormatResolver; @@ -155,6 +156,11 @@ public class SimpleCamelContext extends AbstractCamelContext { } @Override + protected ComponentNameResolver createComponentNameResolver() { + return new DefaultComponentNameResolver(); + } + + @Override protected Registry createRegistry() { return new DefaultRegistry(); } diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/ImmutableCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/ImmutableCamelContext.java index 71ffe85..5d8368e 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/ImmutableCamelContext.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/ImmutableCamelContext.java @@ -69,6 +69,7 @@ import org.apache.camel.spi.BeanRepository; import org.apache.camel.spi.CamelBeanPostProcessor; import org.apache.camel.spi.CamelContextNameStrategy; import org.apache.camel.spi.ClassResolver; +import org.apache.camel.spi.ComponentNameResolver; import org.apache.camel.spi.ComponentResolver; import org.apache.camel.spi.ConfigurerResolver; import org.apache.camel.spi.DataFormat; @@ -1166,6 +1167,16 @@ public class ImmutableCamelContext implements ExtendedCamelContext, CatalogCamel } @Override + public ComponentNameResolver getComponentNameResolver() { + return getExtendedCamelContext().getComponentNameResolver(); + } + + @Override + public void setComponentNameResolver(ComponentNameResolver componentNameResolver) { + getExtendedCamelContext().setComponentNameResolver(componentNameResolver); + } + + @Override public LanguageResolver getLanguageResolver() { return getExtendedCamelContext().getLanguageResolver(); } diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/RuntimeImmutableCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/RuntimeImmutableCamelContext.java index d2920b6..0470117 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/RuntimeImmutableCamelContext.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/RuntimeImmutableCamelContext.java @@ -72,6 +72,7 @@ import org.apache.camel.spi.BeanProxyFactory; import org.apache.camel.spi.CamelBeanPostProcessor; import org.apache.camel.spi.CamelContextNameStrategy; import org.apache.camel.spi.ClassResolver; +import org.apache.camel.spi.ComponentNameResolver; import org.apache.camel.spi.ComponentResolver; import org.apache.camel.spi.ConfigurerResolver; import org.apache.camel.spi.DataFormat; @@ -145,6 +146,7 @@ public class RuntimeImmutableCamelContext implements ExtendedCamelContext, Catal private final ModelJAXBContextFactory modelJAXBContextFactory; private final RuntimeCamelCatalog camelRuntimeCatalog; private final ComponentResolver componentResolver; + private final ComponentNameResolver componentNameResolver; private final LanguageResolver languageResolver; private final DataFormatResolver dataFormatResolver; private final UuidGenerator uuidGenerator; @@ -186,6 +188,7 @@ public class RuntimeImmutableCamelContext implements ExtendedCamelContext, Catal routes = Collections.unmodifiableList(context.getRoutes()); uuidGenerator = context.getUuidGenerator(); componentResolver = context.adapt(ExtendedCamelContext.class).getComponentResolver(); + componentNameResolver = context.adapt(ExtendedCamelContext.class).getComponentNameResolver(); languageResolver = context.adapt(ExtendedCamelContext.class).getLanguageResolver(); dataFormatResolver = context.adapt(ExtendedCamelContext.class).getDataFormatResolver(); endpoints = (EndpointRegistry) context.getEndpointRegistry(); @@ -416,6 +419,11 @@ public class RuntimeImmutableCamelContext implements ExtendedCamelContext, Catal } @Override + public ComponentNameResolver getComponentNameResolver() { + return componentNameResolver; + } + + @Override public LanguageResolver getLanguageResolver() { return languageResolver; } @@ -1414,6 +1422,11 @@ public class RuntimeImmutableCamelContext implements ExtendedCamelContext, Catal } @Override + public void setComponentNameResolver(ComponentNameResolver componentResolver) { + throw new UnsupportedOperationException(); + } + + @Override public void setLanguageResolver(LanguageResolver languageResolver) { throw new UnsupportedOperationException(); } diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultComponentNameResolverTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultComponentNameResolverTest.java new file mode 100644 index 0000000..c6d5216 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultComponentNameResolverTest.java @@ -0,0 +1,54 @@ +/* + * 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.impl.engine; + +import java.util.Set; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ExtendedCamelContext; +import org.apache.camel.spi.ComponentNameResolver; +import org.junit.Test; + +public class DefaultComponentNameResolverTest extends ContextTestSupport { + + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Test + public void testDefaultComponentNameResolver() throws Exception { + context.start(); + + ComponentNameResolver resolver = context.adapt(ExtendedCamelContext.class).getComponentNameResolver(); + assertNotNull(resolver); + + Set<String> names = resolver.resolveNames(context); + assertNotNull(names); + assertTrue(names.size() > 20); + + assertTrue(names.contains("bean")); + assertTrue(names.contains("direct")); + assertTrue(names.contains("file")); + assertTrue(names.contains("log")); + assertTrue(names.contains("mock")); + assertTrue(names.contains("vm")); + assertTrue(names.contains("xslt")); + + context.stop(); + } +} diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index b7782ee..b8114a7 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -1143,7 +1143,8 @@ public abstract class BaseMainSupport extends BaseService { return; } - Object target = supplier.apply(name); + + String prefix = dot == -1 ? "" : key.substring(0, dot + 1); String option = dot == -1 ? "" : key.substring(dot + 1); String value = prop.getProperty(key, ""); @@ -1155,6 +1156,7 @@ public abstract class BaseMainSupport extends BaseService { validateOptionAndValue(key, option, value); + Object target = supplier.apply(name); PropertyOptionKey pok = new PropertyOptionKey(target, prefix); Map<String, Object> values = properties.computeIfAbsent(pok, k -> new LinkedHashMap<>());