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 2024/04/18 13:28:15 UTC
(camel) branch main updated: Pi array (#13848)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new d8496319212 Pi array (#13848)
d8496319212 is described below
commit d849631921216723bd2c068620f7b0e4834f1e2b
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu Apr 18 15:28:08 2024 +0200
Pi array (#13848)
* CAMEL-20688: Add support for array types for @PropertyInject using a separator
* CAMEL-20688: Add support for array types for @PropertyInject using a separator
---
.../main/java/org/apache/camel/PropertyInject.java | 6 +
.../impl/engine/CamelPostProcessorHelper.java | 82 +++++++++++-
.../impl/engine/DefaultCamelBeanPostProcessor.java | 23 ++--
.../impl/engine/CamelPostProcessorHelperTest.java | 143 ++++++++++++++++++++-
.../modules/ROOT/pages/bean-integration.adoc | 48 +++++++
5 files changed, 285 insertions(+), 17 deletions(-)
diff --git a/core/camel-api/src/main/java/org/apache/camel/PropertyInject.java b/core/camel-api/src/main/java/org/apache/camel/PropertyInject.java
index 387a42fcdf4..8bda8d34c85 100644
--- a/core/camel-api/src/main/java/org/apache/camel/PropertyInject.java
+++ b/core/camel-api/src/main/java/org/apache/camel/PropertyInject.java
@@ -41,4 +41,10 @@ public @interface PropertyInject {
*/
String defaultValue() default "";
+ /**
+ * Used for splitting the property value into an array/list of values. For example to use comma to separate the
+ * values.
+ */
+ String separator() default "";
+
}
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelPostProcessorHelper.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelPostProcessorHelper.java
index ebced29de92..30b2d3a353e 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelPostProcessorHelper.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelPostProcessorHelper.java
@@ -18,7 +18,11 @@ package org.apache.camel.impl.engine;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@@ -37,6 +41,7 @@ import org.apache.camel.FluentProducerTemplate;
import org.apache.camel.IsSingleton;
import org.apache.camel.MultipleConsumersSupport;
import org.apache.camel.NoSuchBeanTypeException;
+import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.PollingConsumer;
import org.apache.camel.Producer;
import org.apache.camel.ProducerTemplate;
@@ -54,6 +59,7 @@ import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.PropertyBindingSupport;
import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -271,7 +277,7 @@ public class CamelPostProcessorHelper implements CamelContextAware {
}
public Object getInjectionPropertyValue(
- Class<?> type, String propertyName, String propertyDefaultValue,
+ Class<?> type, Type genericType, String propertyName, String propertyDefaultValue, String separator,
String injectionPointName, Object bean, String beanName) {
try {
String key;
@@ -286,6 +292,10 @@ public class CamelPostProcessorHelper implements CamelContextAware {
}
String value = getCamelContext().resolvePropertyPlaceholders(key);
if (value != null) {
+ if (separator != null && !separator.isBlank()) {
+ Object values = convertValueUsingSeparator(camelContext, type, genericType, value, separator);
+ return getCamelContext().getTypeConverter().mandatoryConvertTo(type, values);
+ }
return getCamelContext().getTypeConverter().mandatoryConvertTo(type, value);
} else {
return null;
@@ -293,6 +303,11 @@ public class CamelPostProcessorHelper implements CamelContextAware {
} catch (Exception e) {
if (ObjectHelper.isNotEmpty(propertyDefaultValue)) {
try {
+ if (separator != null && !separator.isBlank()) {
+ Object values
+ = convertValueUsingSeparator(camelContext, type, genericType, propertyDefaultValue, separator);
+ return getCamelContext().getTypeConverter().mandatoryConvertTo(type, values);
+ }
return getCamelContext().getTypeConverter().mandatoryConvertTo(type, propertyDefaultValue);
} catch (Exception e2) {
throw RuntimeCamelException.wrapRuntimeCamelException(e2);
@@ -302,6 +317,65 @@ public class CamelPostProcessorHelper implements CamelContextAware {
}
}
+ private static Object convertValueUsingSeparator(
+ CamelContext camelContext, Class<?> type, Type genericType,
+ String value, String separator)
+ throws NoTypeConversionAvailableException {
+ String[] arr = value.split(separator);
+
+ if (type.isArray()) {
+ Object[] values = new Object[arr.length];
+ Class<?> ct = type.getComponentType();
+ for (int i = 0; i < arr.length; i++) {
+ String v = arr[i].trim(); // trim values as user may have whitespace noise
+ values[i] = camelContext.getTypeConverter().mandatoryConvertTo(ct, v);
+ }
+ return values;
+ } else if (Collection.class.isAssignableFrom(type)) {
+ Class<?> ct = Object.class;
+ if (genericType != null) {
+ String name = StringHelper.between(genericType.getTypeName(), "<", ">");
+ if (name != null) {
+ Class<?> clazz = camelContext.getClassResolver().resolveClass(name.trim());
+ if (clazz != null) {
+ ct = clazz;
+ }
+ }
+ }
+ boolean set = type.isAssignableFrom(Set.class);
+ Collection values = set ? new LinkedHashSet() : new ArrayList();
+ for (int i = 0; i < arr.length; i++) {
+ String v = arr[i].trim(); // trim values as user may have whitespace noise
+ values.add(camelContext.getTypeConverter().mandatoryConvertTo(ct, v));
+ }
+ return values;
+ } else if (Map.class.isAssignableFrom(type)) {
+ Class<?> ct = Object.class;
+ if (genericType != null) {
+ String name = StringHelper.between(genericType.getTypeName(), "<", ">");
+ name = StringHelper.afterLast(name, ",");
+ if (name != null) {
+ Class<?> clazz = camelContext.getClassResolver().resolveClass(name.trim());
+ if (clazz != null) {
+ ct = clazz;
+ }
+ }
+ }
+ Map<String, Object> values = new LinkedHashMap<>();
+ for (int i = 0; i < arr.length; i++) {
+ String v = arr[i].trim(); // trim values as user may have whitespace noise
+ if (v.contains("=")) {
+ String k = StringHelper.before(v, "=").trim();
+ String e = StringHelper.after(v, "=").trim();
+ values.put(k, camelContext.getTypeConverter().mandatoryConvertTo(ct, e));
+ }
+ }
+ return values;
+ }
+
+ return null;
+ }
+
public Object getInjectionBeanValue(Class<?> type, String name) {
if (ObjectHelper.isEmpty(name)) {
// is it camel context itself?
@@ -456,6 +530,7 @@ public class CamelPostProcessorHelper implements CamelContextAware {
Object[] parameters = new Object[method.getParameterCount()];
for (int i = 0; i < method.getParameterCount(); i++) {
Class<?> type = method.getParameterTypes()[i];
+ Type genericType = method.getGenericParameterTypes()[i];
if (type.isAssignableFrom(CamelContext.class)) {
parameters[i] = context;
} else if (type.isAssignableFrom(Registry.class)) {
@@ -470,8 +545,9 @@ public class CamelPostProcessorHelper implements CamelContextAware {
Annotation ann = anns[0];
if (ann.annotationType() == PropertyInject.class) {
PropertyInject pi = (PropertyInject) ann;
- Object result = getInjectionPropertyValue(type, pi.value(), pi.defaultValue(),
- null, null, null);
+ Object result
+ = getInjectionPropertyValue(type, genericType, pi.value(), pi.defaultValue(), pi.separator(),
+ null, null, null);
parameters[i] = result;
} else if (ann.annotationType() == BeanConfigInject.class) {
BeanConfigInject pi = (BeanConfigInject) ann;
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java
index fc0d5cf86d2..45d6e1e1011 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java
@@ -18,6 +18,7 @@ package org.apache.camel.impl.engine;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -268,7 +269,8 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca
PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
if (propertyInject != null) {
- injectFieldProperty(field, propertyInject.value(), propertyInject.defaultValue(), bean, beanName);
+ injectFieldProperty(field, propertyInject.value(), propertyInject.defaultValue(), propertyInject.separator(),
+ bean, beanName);
}
BeanInject beanInject = field.getAnnotation(BeanInject.class);
@@ -326,10 +328,12 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca
}
public void injectFieldProperty(
- Field field, String propertyName, String propertyDefaultValue, Object bean, String beanName) {
+ Field field, String propertyName, String propertyDefaultValue, String propertySeparator,
+ Object bean, String beanName) {
ReflectionHelper.setField(field, bean,
- getPostProcessorHelper().getInjectionPropertyValue(field.getType(), propertyName, propertyDefaultValue,
- field.getName(), bean, beanName));
+ getPostProcessorHelper().getInjectionPropertyValue(field.getType(), field.getGenericType(), propertyName,
+ propertyDefaultValue,
+ propertySeparator, field.getName(), bean, beanName));
}
protected void injectMethods(final Object bean, final String beanName, Function<Class<?>, Boolean> accept) {
@@ -424,7 +428,8 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca
protected void setterInjection(Method method, Object bean, String beanName) {
PropertyInject propertyInject = method.getAnnotation(PropertyInject.class);
if (propertyInject != null) {
- setterPropertyInjection(method, propertyInject.value(), propertyInject.defaultValue(), bean, beanName);
+ setterPropertyInjection(method, propertyInject.value(), propertyInject.defaultValue(), propertyInject.separator(),
+ bean, beanName);
}
BeanInject beanInject = method.getAnnotation(BeanInject.class);
@@ -461,15 +466,17 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca
}
public void setterPropertyInjection(
- Method method, String propertyValue, String propertyDefaultValue,
+ Method method, String propertyValue, String propertyDefaultValue, String propertySeparator,
Object bean, String beanName) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
LOG.warn("Ignoring badly annotated method for injection due to incorrect number of parameters: {}", method);
} else {
String propertyName = org.apache.camel.util.ObjectHelper.getPropertyName(method);
- Object value = getPostProcessorHelper().getInjectionPropertyValue(parameterTypes[0], propertyValue,
- propertyDefaultValue, propertyName, bean, beanName);
+ Class<?> type = parameterTypes[0];
+ Type genericType = method.getGenericParameterTypes()[0];
+ Object value = getPostProcessorHelper().getInjectionPropertyValue(type, genericType, propertyValue,
+ propertyDefaultValue, propertySeparator, propertyName, bean, beanName);
invokeMethod(method, bean, value);
}
}
diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/engine/CamelPostProcessorHelperTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/engine/CamelPostProcessorHelperTest.java
index 212a7e868f9..28f4c8fed29 100644
--- a/core/camel-core/src/test/java/org/apache/camel/impl/engine/CamelPostProcessorHelperTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/impl/engine/CamelPostProcessorHelperTest.java
@@ -18,7 +18,11 @@ package org.apache.camel.impl.engine;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.Properties;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.camel.BeanConfigInject;
@@ -374,13 +378,13 @@ public class CamelPostProcessorHelperTest extends ContextTestSupport {
Field field = bean.getClass().getField("timeout");
PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
Class<?> type = field.getType();
- Object value = helper.getInjectionPropertyValue(type, propertyInject.value(), "", "timeout", bean, "foo");
+ Object value = helper.getInjectionPropertyValue(type, null, propertyInject.value(), "", "", "timeout", bean, "foo");
assertEquals(Integer.valueOf(2000), (Object) Integer.valueOf(String.valueOf(value)));
field = bean.getClass().getField("greeting");
propertyInject = field.getAnnotation(PropertyInject.class);
type = field.getType();
- value = helper.getInjectionPropertyValue(type, propertyInject.value(), "", "greeting", bean, "foo");
+ value = helper.getInjectionPropertyValue(type, null, propertyInject.value(), "", "", "greeting", bean, "foo");
assertEquals("Hello Camel", value);
}
@@ -395,16 +399,106 @@ public class CamelPostProcessorHelperTest extends ContextTestSupport {
Field field = bean.getClass().getField("timeout");
PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
Class<?> type = field.getType();
- Object value = helper.getInjectionPropertyValue(type, propertyInject.value(), "5000", "timeout", bean, "foo");
+ Object value = helper.getInjectionPropertyValue(type, null, propertyInject.value(), "5000", "", "timeout", bean, "foo");
assertEquals(Integer.valueOf(5000), (Object) Integer.valueOf(String.valueOf(value)));
field = bean.getClass().getField("greeting");
propertyInject = field.getAnnotation(PropertyInject.class);
type = field.getType();
- value = helper.getInjectionPropertyValue(type, propertyInject.value(), "", "greeting", bean, "foo");
+ value = helper.getInjectionPropertyValue(type, null, propertyInject.value(), "", "", "greeting", bean, "foo");
assertEquals("Hello Camel", value);
}
+ @Test
+ public void testPropertyFieldSeparatorArrayInject() throws Exception {
+ myProp.put("serverPorts", "4444;5555"); // test with semicolon as separator
+ myProp.put("hosts", "serverA , serverB"); // test with whitespace noise
+
+ CamelPostProcessorHelper helper = new CamelPostProcessorHelper(context);
+
+ MyPropertyFieldSeparatorArrayBean bean = new MyPropertyFieldSeparatorArrayBean();
+
+ Field field = bean.getClass().getField("ports");
+ PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
+ Class<?> type = field.getType();
+ Object value
+ = helper.getInjectionPropertyValue(type, null, propertyInject.value(), "", propertyInject.separator(), "ports",
+ bean, "foo");
+ assertIsInstanceOf(int[].class, value);
+ int[] arr = (int[]) value;
+ assertEquals(2, arr.length);
+ assertEquals(4444, arr[0]);
+ assertEquals(5555, arr[1]);
+
+ field = bean.getClass().getField("hosts");
+ propertyInject = field.getAnnotation(PropertyInject.class);
+ type = field.getType();
+ value = helper.getInjectionPropertyValue(type, null, propertyInject.value(), "", propertyInject.separator(), "hosts",
+ bean,
+ "foo");
+ assertIsInstanceOf(String[].class, value);
+ String[] arr2 = (String[]) value;
+ assertEquals(2, arr2.length);
+ assertEquals("serverA", arr2[0]);
+ assertEquals("serverB", arr2[1]);
+ }
+
+ @Test
+ public void testPropertyFieldSeparatorListInject() throws Exception {
+ myProp.put("serverPorts", "4444;5555"); // test with semicolon as separator
+ myProp.put("hosts", "serverA , serverB"); // test with whitespace noise
+
+ CamelPostProcessorHelper helper = new CamelPostProcessorHelper(context);
+
+ MyPropertyFieldSeparatorListBean bean = new MyPropertyFieldSeparatorListBean();
+
+ Field field = bean.getClass().getField("ports");
+ PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
+ Class<?> type = field.getType();
+ Object value = helper.getInjectionPropertyValue(type, field.getGenericType(),
+ propertyInject.value(), "", propertyInject.separator(), "ports",
+ bean, "foo");
+ assertIsInstanceOf(List.class, value);
+ List arr = (List) value;
+ assertEquals(2, arr.size());
+ assertEquals(4444, arr.get(0));
+ assertEquals(5555, arr.get(1));
+
+ field = bean.getClass().getField("hosts");
+ propertyInject = field.getAnnotation(PropertyInject.class);
+ type = field.getType();
+ value = helper.getInjectionPropertyValue(type, field.getGenericType(),
+ propertyInject.value(), "", propertyInject.separator(), "hosts", bean,
+ "foo");
+ assertIsInstanceOf(Set.class, value);
+ Set arr2 = (Set) value;
+ assertEquals(2, arr.size());
+ Iterator it = arr2.iterator();
+ assertEquals("serverA", it.next());
+ assertEquals("serverB", it.next());
+ }
+
+ @Test
+ public void testPropertyFieldSeparatorMapInject() throws Exception {
+ myProp.put("servers", "serverA = 4444 ; serverB=5555"); // test with semicolon as separator and whitespace
+
+ CamelPostProcessorHelper helper = new CamelPostProcessorHelper(context);
+
+ MyPropertyFieldSeparatorMapBean bean = new MyPropertyFieldSeparatorMapBean();
+
+ Field field = bean.getClass().getField("servers");
+ PropertyInject propertyInject = field.getAnnotation(PropertyInject.class);
+ Class<?> type = field.getType();
+ Object value = helper.getInjectionPropertyValue(type, field.getGenericType(),
+ propertyInject.value(), "", propertyInject.separator(), "servers",
+ bean, "foo");
+ assertIsInstanceOf(Map.class, value);
+ Map arr = (Map) value;
+ assertEquals(2, arr.size());
+ assertEquals(4444, arr.get("serverA"));
+ assertEquals(5555, arr.get("serverB"));
+ }
+
@Test
public void testPropertyMethodInject() throws Exception {
myProp.put("myTimeout", "2000");
@@ -417,13 +511,13 @@ public class CamelPostProcessorHelperTest extends ContextTestSupport {
Method method = bean.getClass().getMethod("setTimeout", int.class);
PropertyInject propertyInject = method.getAnnotation(PropertyInject.class);
Class<?> type = method.getParameterTypes()[0];
- Object value = helper.getInjectionPropertyValue(type, propertyInject.value(), "", "timeout", bean, "foo");
+ Object value = helper.getInjectionPropertyValue(type, null, propertyInject.value(), "", "", "timeout", bean, "foo");
assertEquals(Integer.valueOf(2000), (Object) Integer.valueOf(String.valueOf(value)));
method = bean.getClass().getMethod("setGreeting", String.class);
propertyInject = method.getAnnotation(PropertyInject.class);
type = method.getParameterTypes()[0];
- value = helper.getInjectionPropertyValue(type, propertyInject.value(), "", "greeting", bean, "foo");
+ value = helper.getInjectionPropertyValue(type, null, propertyInject.value(), "", "", "greeting", bean, "foo");
assertEquals("Hello Camel", value);
}
@@ -750,6 +844,43 @@ public class CamelPostProcessorHelperTest extends ContextTestSupport {
}
}
+ public static class MyPropertyFieldSeparatorArrayBean {
+
+ @PropertyInject(value = "serverPorts", separator = ";")
+ public int[] ports;
+
+ @PropertyInject(value = "hosts", separator = ",")
+ public String[] hosts;
+
+ public String doSomething(String body) {
+ return String.format("%s:%d %s:%d with body: %s", hosts[0], ports[0], hosts[1], ports[1], body);
+ }
+ }
+
+ public static class MyPropertyFieldSeparatorListBean {
+
+ @PropertyInject(value = "serverPorts", separator = ";")
+ public List<Integer> ports;
+
+ @PropertyInject(value = "hosts", separator = ",")
+ public Set<String> hosts;
+
+ public String doSomething(String body) {
+ Iterator<String> it = hosts.iterator();
+ return String.format("%s:%d %s:%d with body: %s", it.next(), ports.get(0), it.next(), ports.get(1), body);
+ }
+ }
+
+ public static class MyPropertyFieldSeparatorMapBean {
+
+ @PropertyInject(value = "servers", separator = ";")
+ public Map<String, Integer> servers;
+
+ public String doSomething(String body) {
+ return null;
+ }
+ }
+
public static class MyPropertyMethodBean {
private int timeout;
diff --git a/docs/user-manual/modules/ROOT/pages/bean-integration.adoc b/docs/user-manual/modules/ROOT/pages/bean-integration.adoc
index 2a047836918..e3036f18278 100644
--- a/docs/user-manual/modules/ROOT/pages/bean-integration.adoc
+++ b/docs/user-manual/modules/ROOT/pages/bean-integration.adoc
@@ -92,6 +92,54 @@ You can also add a default value if the key does not exist, such as:
private int timeout;
----
+=== Using @PropertyInject with arrays, lists, sets or maps
+
+You can also use `@PropertyInject` to inject an array of values. For example, you may configure multiple hostnames
+in the configuration file, and need to inject this into an `String[]` or `List<String>` field.
+To do this you need to tell Camel that the property value should be split using a separator, as follows:
+
+[source,java]
+----
+@PropertyInject(value = "myHostnames", separator = ",")
+private String[] servers;
+----
+
+TIP: You can also use list/set types, such as `List<String>` or `Set<String>` instead of array.
+
+Then in the `application.properties` file you can define the servers:
+
+[source,properties]
+----
+myHostnames = serverA, serverB, serverC
+----
+
+TIP: This also works for fields that are not String based, such as `int[]` for numeric values.
+
+For `Map` types then the values is expected to be in key=value format, such as:
+
+[source,properties]
+----
+myServers = serverA=http://coolstore:4444,serverB=http://megastore:5555
+----
+
+You can then inject this into a `Map` as follows:
+
+[source,java]
+----
+@PropertyInject(value = "myServers", separator = ",")
+private Map servers;
+----
+
+You can use generic types in the Map such as the values should be `Integer` values:
+
+[source,java]
+----
+@PropertyInject(value = "ports", separator = ",")
+private Map<String, Integer> ports;
+----
+
+NOTE: The generic type can only be a single class type, and cannot be a nested complex type such as `Map<String,Map<Kind,Priority>>`.
+
== See Also
** xref:bean-injection.adoc[Bean Injection]