You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2019/06/17 12:04:36 UTC
[camel] 01/20: CAMEL-13647: Allow to do autowrire by classpath.
Quick and dirty prototype.
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 d473ed4e5940cc6d43a4ec51fa15f3c48e311e5f
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Jun 14 11:45:19 2019 +0200
CAMEL-13647: Allow to do autowrire by classpath. Quick and dirty prototype.
---
.../org/apache/camel/spi/PackageScanFilter.java | 1 +
.../impl/scan/AssignableToPackageScanFilter.java | 14 +-
.../apache/camel/support/MyBarImplementation.java} | 44 +++--
.../org/apache/camel/support/MyBarInterface.java} | 30 ++-
...ropertyBindingSupportAutowireClasspathTest.java | 64 ++++++
.../camel/support/PropertyBindingSupport.java | 219 ++++++++++++++++++++-
6 files changed, 337 insertions(+), 35 deletions(-)
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java b/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java
index d87f1be..6129faf 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java
@@ -19,6 +19,7 @@ package org.apache.camel.spi;
/**
* Filter that can be used with the {@link org.apache.camel.spi.PackageScanClassResolver} resolver.
*/
+@FunctionalInterface
public interface PackageScanFilter {
/**
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/scan/AssignableToPackageScanFilter.java b/core/camel-base/src/main/java/org/apache/camel/impl/scan/AssignableToPackageScanFilter.java
index 892b8fd..56be02b 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/scan/AssignableToPackageScanFilter.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/scan/AssignableToPackageScanFilter.java
@@ -26,6 +26,7 @@ import org.apache.camel.spi.PackageScanFilter;
*/
public class AssignableToPackageScanFilter implements PackageScanFilter {
private final Set<Class<?>> parents = new HashSet<>();
+ private boolean includeInterfaces;
public AssignableToPackageScanFilter() {
}
@@ -38,13 +39,24 @@ public class AssignableToPackageScanFilter implements PackageScanFilter {
this.parents.addAll(parents);
}
+ public boolean isIncludeInterfaces() {
+ return includeInterfaces;
+ }
+
+ public void setIncludeInterfaces(boolean includeInterfaces) {
+ this.includeInterfaces = includeInterfaces;
+ }
+
public void addParentType(Class<?> parentType) {
parents.add(parentType);
}
public boolean matches(Class<?> type) {
- if (parents != null && parents.size() > 0) {
+ if (parents.size() > 0) {
for (Class<?> parent : parents) {
+ if (!includeInterfaces && parent.isInterface()) {
+ continue;
+ }
if (parent.isAssignableFrom(type)) {
return true;
}
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java b/core/camel-core/src/test/java/org/apache/camel/support/MyBarImplementation.java
similarity index 59%
copy from core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java
copy to core/camel-core/src/test/java/org/apache/camel/support/MyBarImplementation.java
index d87f1be..2a91dec 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java
+++ b/core/camel-core/src/test/java/org/apache/camel/support/MyBarImplementation.java
@@ -1,31 +1,43 @@
-/*
+/**
* 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
* 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;
+package org.apache.camel.support;
-/**
- * Filter that can be used with the {@link org.apache.camel.spi.PackageScanClassResolver} resolver.
- */
-public interface PackageScanFilter {
+public class MyBarImplementation implements MyBarInterface {
+
+ private String name;
+ private String city;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getCity() {
+ return city;
+ }
- /**
- * Does the given class match
- *
- * @param type the class
- * @return true to include this class, false to skip it.
- */
- boolean matches(Class<?> type);
+ @Override
+ public void setCity(String city) {
+ this.city = city;
+ }
}
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java b/core/camel-core/src/test/java/org/apache/camel/support/MyBarInterface.java
similarity index 64%
copy from core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java
copy to core/camel-core/src/test/java/org/apache/camel/support/MyBarInterface.java
index d87f1be..5813eaa 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanFilter.java
+++ b/core/camel-core/src/test/java/org/apache/camel/support/MyBarInterface.java
@@ -1,31 +1,29 @@
-/*
+/**
* 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
* 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;
+package org.apache.camel.support;
-/**
- * Filter that can be used with the {@link org.apache.camel.spi.PackageScanClassResolver} resolver.
- */
-public interface PackageScanFilter {
+public interface MyBarInterface {
+
+ void setName(String name);
+
+ String getName();
+
+ void setCity(String city);
- /**
- * Does the given class match
- *
- * @param type the class
- * @return true to include this class, false to skip it.
- */
- boolean matches(Class<?> type);
+ String getCity();
+
}
diff --git a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportAutowireClasspathTest.java b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportAutowireClasspathTest.java
new file mode 100644
index 0000000..d803585
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportAutowireClasspathTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.support;
+
+import org.apache.camel.ContextTestSupport;
+import org.junit.Test;
+
+/**
+ * Unit test for PropertyBindingSupport
+ */
+public class PropertyBindingSupportAutowireClasspathTest extends ContextTestSupport {
+
+ @Test
+ public void testAutowireProperties() throws Exception {
+ Foo foo = new Foo();
+
+ PropertyBindingSupport.bindProperty(context, foo, "name", "James");
+ PropertyBindingSupport.autowireInterfacePropertiesFromClasspath(context, foo);
+ PropertyBindingSupport.bindProperty(context, foo, "my-bar.name", "Thirsty Bear");
+ PropertyBindingSupport.bindProperty(context, foo, "my-bar.city", "San Francisco");
+
+ assertEquals("James", foo.getName());
+ assertEquals("Thirsty Bear", foo.getMyBar().getName());
+ assertEquals("San Francisco", foo.getMyBar().getCity());
+ }
+
+ public static class Foo {
+ private String name;
+ private MyBarInterface myBar;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public MyBarInterface getMyBar() {
+ return myBar;
+ }
+
+ public void setMyBar(MyBarInterface myBar) {
+ this.myBar = myBar;
+ }
+ }
+
+
+}
+
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
index 081312f..9f84c43 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
@@ -16,16 +16,27 @@
*/
package org.apache.camel.support;
+import java.io.File;
+import java.io.IOException;
import java.lang.reflect.Method;
+import java.net.JarURLConnection;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
import org.apache.camel.CamelContext;
+import org.apache.camel.ExtendedCamelContext;
import org.apache.camel.PropertyBindingException;
+import org.apache.camel.spi.PackageScanClassResolver;
import static org.apache.camel.support.IntrospectionSupport.findSetterMethods;
import static org.apache.camel.util.ObjectHelper.isNotEmpty;
@@ -148,6 +159,7 @@ public final class PropertyBindingSupport {
void onAutowire(Object target, String propertyName, Class propertyType, Object value);
}
+
/**
* This will discover all the properties on the target, and automatic bind the properties that are null by
* looking up in the registry to see if there is a single instance of the same type as the property.
@@ -193,8 +205,7 @@ public final class PropertyBindingSupport {
private static boolean doAutowireSingletonPropertiesFromRegistry(CamelContext camelContext, Object target, Set<Object> parents,
boolean bindNullOnly, boolean deepNesting, OnAutowiring callback) throws Exception {
- // when adding a component then support auto-configuring complex types
- // by looking up from registry, such as DataSource etc
+
Map<String, Object> properties = new LinkedHashMap<>();
IntrospectionSupport.getProperties(target, properties, null);
@@ -259,6 +270,130 @@ public final class PropertyBindingSupport {
}
/**
+ * This will discover all the properties on the target which are interfaces, and automatic attempt to bind the properties that are null by
+ * doing classpath scanning to find if there is a just only one class that implements the interface, and then attempt to create a new instance
+ * of this class. This is used for convention over configuration to automatic configure resources such as DataSource, Amazon Logins and
+ * so on.
+ *
+ * @param camelContext the camel context
+ * @param target the target object
+ * @return true if one ore more properties was auto wired
+ */
+ public static boolean autowireInterfacePropertiesFromClasspath(CamelContext camelContext, Object target) {
+ return autowireInterfacePropertiesFromClasspath(camelContext, target, false, false, null);
+ }
+
+ /**
+ * This will discover all the properties on the target which are interfaces, and automatic attempt to bind the properties that are null by
+ * doing classpath scanning to find if there is a just only one class that implements the interface, and then attempt to create a new instance
+ * of this class. This is used for convention over configuration to automatic configure resources such as DataSource, Amazon Logins and
+ * so on.
+ *
+ * @param camelContext the camel context
+ * @param target the target object
+ * @param bindNullOnly whether to only autowire if the property has no default value or has not been configured explicit
+ * @param deepNesting whether to attempt to walk as deep down the object graph by creating new empty objects on the way if needed (Camel can only create
+ * new empty objects if they have a default no-arg constructor, also mind that this may lead to creating many empty objects, even
+ * if they will not have any objects autowired from the registry, so use this with caution)
+ * @param callback optional callback when a property was auto wired
+ * @return true if one ore more properties was auto wired
+ */
+ public static boolean autowireInterfacePropertiesFromClasspath(CamelContext camelContext, Object target,
+ boolean bindNullOnly, boolean deepNesting, OnAutowiring callback) {
+ try {
+ if (target != null) {
+ Set<Object> parents = new HashSet<>();
+ return doAutowireInterfacePropertiesFromClasspath(camelContext, target, parents, bindNullOnly, deepNesting, callback);
+ }
+ } catch (Exception e) {
+ throw new PropertyBindingException(target, e);
+ }
+
+ return false;
+ }
+
+ private static boolean doAutowireInterfacePropertiesFromClasspath(CamelContext camelContext, Object target, Set<Object> parents,
+ boolean bindNullOnly, boolean deepNesting, OnAutowiring callback) throws Exception {
+
+ Map<String, Object> properties = new LinkedHashMap<>();
+ IntrospectionSupport.getProperties(target, properties, null);
+
+ boolean hit = false;
+
+ for (Map.Entry<String, Object> entry : properties.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ Class<?> type = getGetterType(target, key);
+
+ boolean skip = parents.contains(value) || value instanceof CamelContext;
+ if (skip) {
+ // we have already covered this as parent of parents so dont walk down this as we want to avoid
+ // circular dependencies when walking the OGNL graph, also we dont want to walk down CamelContext
+ continue;
+ }
+
+ if (isComplexUserType(type) && isInterface(type)) {
+ // if the property has not been set and its a complex type (not simple or string etc)
+ if (!bindNullOnly || value == null) {
+ // do classpath scanning (TODO: do this only once)
+ Set<String> packageNames = findAllPackageNames(null);
+ if (!packageNames.isEmpty()) {
+ String[] packages = packageNames.toArray(new String[packageNames.size()]);
+
+ PackageScanClassResolver resolver = camelContext.adapt(ExtendedCamelContext.class).getPackageScanClassResolver();
+ Set<Class<?>> classes = resolver.findByFilter(
+ c -> !c.isInterface() && type.isAssignableFrom(c),
+ packages);
+
+ if (classes.size() == 1) {
+ Class<?> clazz = classes.iterator().next();
+ try {
+ value = camelContext.getInjector().newInstance(clazz);
+ } catch (Throwable e) {
+ // ignore
+ }
+ if (value != null) {
+ hit |= IntrospectionSupport.setProperty(camelContext, target, key, value);
+ if (hit && callback != null) {
+ callback.onAutowire(target, key, type, value);
+ }
+ }
+ }
+ }
+ }
+
+ // attempt to create new instances to walk down the tree if its null (deepNesting option)
+ if (value == null && deepNesting) {
+ // okay is there a setter so we can create a new instance and set it automatic
+ Method method = findBestSetterMethod(target.getClass(), key, true);
+ if (method != null) {
+ Class<?> parameterType = method.getParameterTypes()[0];
+ if (parameterType != null && org.apache.camel.util.ObjectHelper.hasDefaultPublicNoArgConstructor(parameterType)) {
+ Object instance = camelContext.getInjector().newInstance(parameterType);
+ if (instance != null) {
+ org.apache.camel.support.ObjectHelper.invokeMethod(method, target, instance);
+ target = instance;
+ // remember this as parent and also autowire nested properties
+ // do not walk down if it point to our-selves (circular reference)
+ parents.add(target);
+ value = instance;
+ hit |= doAutowireInterfacePropertiesFromClasspath(camelContext, value, parents, bindNullOnly, deepNesting, callback);
+ }
+ }
+ }
+ } else if (value != null) {
+ // remember this as parent and also autowire nested properties
+ // do not walk down if it point to our-selves (circular reference)
+ parents.add(target);
+ hit |= doAutowireInterfacePropertiesFromClasspath(camelContext, value, parents, bindNullOnly, deepNesting, callback);
+ }
+ }
+ }
+
+ return hit;
+ }
+
+ /**
* Binds the properties to the target object, and removes the property that was bound from properties.
*
* @param camelContext the camel context
@@ -572,6 +707,11 @@ public final class PropertyBindingSupport {
return type != null && !type.isPrimitive() && !type.getName().startsWith("java");
}
+ private static boolean isInterface(Class type) {
+ // lets consider all non java, as complex types
+ return type != null && type.isInterface();
+ }
+
private static void setReferenceProperties(CamelContext context, Object target, Map<String, Object> parameters) {
Iterator<Map.Entry<String, Object>> it = parameters.entrySet().iterator();
while (it.hasNext()) {
@@ -609,4 +749,79 @@ public final class PropertyBindingSupport {
return parameter != null && parameter.trim().startsWith("#");
}
+ // TODO: move this to some util class
+
+ public static void main(String[] args) throws IOException {
+ PropertyBindingSupport.findAllPackageNames(null);
+ }
+
+ public static Set<String> findAllPackageNames(ClassLoader loader) throws IOException {
+ Set<String> answer = new TreeSet<>();
+
+ // get all JARs on classpath
+ String cp = System.getProperty("java.class.path");
+// System.out.println(cp);
+ String[] parts = cp.split(":");
+ for (String p : parts) {
+ System.out.println(p);
+ if (p.endsWith(".jar")) {
+ JarFile jar = new JarFile(p);
+ jar.stream().forEach(e -> {
+ if (e.isDirectory()) {
+ String name = e.getName();
+ name = name.replace('/', '.');
+ name = name.replace('\\', '.');
+ if (name.endsWith(".")) {
+ name = name.substring(0, name.length() - 1);
+ }
+ if (validName(name)) {
+ answer.add(name);
+ }
+ }
+ });
+ } else {
+ // its a directory such as target, then traverse and find all directory
+ gatherAllDirectories(new File(p), "", answer);
+ }
+ }
+
+ System.out.println("There are " + answer.size() + " packages");
+ answer.forEach(System.out::println);
+ return answer;
+ }
+
+ public static boolean validName(String name) {
+ boolean invalid = name.startsWith("META-INF") || name.startsWith("java") || name.startsWith("jdk") || name.startsWith("netscape")
+ || name.startsWith("resources") || name.startsWith("toolbarButtonGraphics")
+ || name.startsWith("oracle") || name.startsWith("sun") || name.startsWith("com.sun") || name.startsWith("com.oracle");
+ return !invalid;
+ }
+
+ public static boolean validPackageForClassloader(String packageName, ClassLoader loader) throws IOException {
+ return loader.getResources(packageName) != null;
+ }
+
+ public static void gatherAllDirectories(File path, String root, Set<String> dirs) {
+ if (path == null) {
+ return;
+ }
+ File[] paths = path.listFiles(f -> f.isDirectory());
+ if (paths != null) {
+ for (File dir : paths) {
+ String name = root + (root.isEmpty() ? "" : ".") + dir.getName();
+ name = name.replace('/', '.');
+ name = name.replace('\\', '.');
+ if (name.endsWith(".")) {
+ name = name.substring(0, name.length() - 1);
+ }
+
+ if (validName(name)) {
+ dirs.add(name);
+ String subRoot = root + (root.isEmpty() ? "" : ".") + dir.getName();
+ gatherAllDirectories(dir, subRoot, dirs);
+ }
+ }
+ }
+ }
+
}