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 2020/03/16 09:35:07 UTC
[camel] branch master updated: CAMEL-14717: camel-core - Binding
properties add support for optional keys
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
The following commit(s) were added to refs/heads/master by this push:
new 611b30c CAMEL-14717: camel-core - Binding properties add support for optional keys
611b30c is described below
commit 611b30c683926e9d69b32993c77815df802b95ac
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Mar 16 10:25:26 2020 +0100
CAMEL-14717: camel-core - Binding properties add support for optional keys
---
.../PropertyBindingSupportConfigurerTest.java | 242 +++++++++++++++++++++
.../camel/support/PropertyBindingSupportTest.java | 76 +++++++
.../apache/camel/support/IntrospectionSupport.java | 2 +-
.../camel/support/PropertyBindingSupport.java | 45 +++-
4 files changed, 359 insertions(+), 6 deletions(-)
diff --git a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportConfigurerTest.java b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportConfigurerTest.java
new file mode 100644
index 0000000..8edf23b
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportConfigurerTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.PropertyBindingException;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.junit.Test;
+
+/**
+ * Unit test for PropertyBindingSupport
+ */
+public class PropertyBindingSupportConfigurerTest extends ContextTestSupport {
+
+ private final MyConfigurer myConfigurer = new MyConfigurer();
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+
+ Company work = new Company();
+ work.setId(456);
+ work.setName("Acme");
+ context.getRegistry().bind("myWork", work);
+
+ Properties placeholders = new Properties();
+ placeholders.put("companyName", "Acme");
+ placeholders.put("committer", "rider");
+ context.getPropertiesComponent().setInitialProperties(placeholders);
+
+ return context;
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ Bar bar = new Bar();
+
+ Map<String, Object> prop = new HashMap<>();
+ prop.put("age", "33");
+ prop.put("{{committer}}", "true");
+ prop.put("gold-customer", "true");
+ prop.put("work.id", "123");
+ prop.put("work.name", "{{companyName}}");
+
+ myConfigurer.reset();
+ PropertyBindingSupport.build().withConfigurer(myConfigurer).withIgnoreCase(true).bind(context, bar, prop);
+ assertEquals(3, myConfigurer.getCounter());
+
+ assertEquals(33, bar.getAge());
+ assertTrue(bar.isRider());
+ assertTrue(bar.isGoldCustomer());
+ assertEquals(123, bar.getWork().getId());
+ assertEquals("Acme", bar.getWork().getName());
+
+ assertTrue("Should bind all properties", prop.isEmpty());
+ }
+
+ @Test
+ public void testPropertiesOptionallKey() throws Exception {
+ Bar bar = new Bar();
+
+ Map<String, Object> prop = new HashMap<>();
+ prop.put("?AGE", "33");
+ prop.put("{{committer}}", "true");
+ prop.put("gOLd-Customer", "true");
+ prop.put("?silver-Customer", "true");
+ prop.put("?work.ID", "123");
+ prop.put("?WORk.naME", "{{companyName}}");
+ prop.put("?work.addresss", "Some street");
+ prop.put("?work.addresss.zip", "1234");
+
+ myConfigurer.reset();
+ PropertyBindingSupport.build().withConfigurer(myConfigurer).withIgnoreCase(true).bind(context, bar, prop);
+ assertEquals(3, myConfigurer.getCounter());
+
+ assertEquals(33, bar.getAge());
+ assertTrue(bar.isRider());
+ assertTrue(bar.isGoldCustomer());
+ assertEquals(123, bar.getWork().getId());
+ assertEquals("Acme", bar.getWork().getName());
+
+ assertFalse("Should NOT bind all properties", prop.isEmpty());
+ assertEquals(3, prop.size());
+ assertTrue(prop.containsKey("?silver-Customer"));
+ assertTrue(prop.containsKey("?work.addresss"));
+ assertTrue(prop.containsKey("?work.addresss.zip"));
+ }
+
+ @Test
+ public void testPropertiesOptionallKeyMandatory() throws Exception {
+ Bar bar = new Bar();
+
+ Map<String, Object> prop = new HashMap<>();
+ prop.put("?AGE", "33");
+ prop.put("{{committer}}", "true");
+ prop.put("gOLd-Customer", "true");
+ prop.put("?silver-Customer", "true");
+ prop.put("?work.ID", "123");
+ prop.put("?WORk.naME", "{{companyName}}");
+ prop.put("?work.addresss", "Some street");
+ prop.put("?work.addresss.zip", "1234");
+
+ myConfigurer.reset();
+ PropertyBindingSupport.build().withConfigurer(myConfigurer).withIgnoreCase(true).withMandatory(true).bind(context, bar, prop);
+ assertEquals(3, myConfigurer.getCounter());
+
+ assertEquals(33, bar.getAge());
+ assertTrue(bar.isRider());
+ assertTrue(bar.isGoldCustomer());
+ assertEquals(123, bar.getWork().getId());
+ assertEquals("Acme", bar.getWork().getName());
+
+ assertFalse("Should NOT bind all properties", prop.isEmpty());
+ assertEquals(3, prop.size());
+ assertTrue(prop.containsKey("?silver-Customer"));
+ assertTrue(prop.containsKey("?work.addresss"));
+ assertTrue(prop.containsKey("?work.addresss.zip"));
+
+ // should not fail as we marked the option as optional
+ prop.put("?unknown", "123");
+ PropertyBindingSupport.build().withConfigurer(myConfigurer).withIgnoreCase(true).withMandatory(true).bind(context, bar, prop);
+ prop.remove("?unknown");
+
+ // should fail as its mandatory
+ prop.put("unknown", "123");
+ try {
+ PropertyBindingSupport.build().withConfigurer(myConfigurer).withIgnoreCase(true).withMandatory(true).bind(context, bar, prop);
+ fail("Should fail");
+ } catch (PropertyBindingException e) {
+ assertEquals("unknown", e.getPropertyName());
+ }
+ }
+
+ public static class Bar {
+ private int age;
+ private boolean rider;
+ private Company work; // has no default value but Camel can automatic
+ // create one if there is a setter
+ private boolean goldCustomer;
+
+ public int getAge() {
+ return age;
+ }
+
+ public boolean isRider() {
+ return rider;
+ }
+
+ public Company getWork() {
+ return work;
+ }
+
+ public boolean isGoldCustomer() {
+ return goldCustomer;
+ }
+
+ // this has no setter but only builders
+ // and mix the builders with both styles (with as prefix and no prefix
+ // at all)
+
+ public Bar withAge(int age) {
+ this.age = age;
+ return this;
+ }
+
+ public Bar withRider(boolean rider) {
+ this.rider = rider;
+ return this;
+ }
+
+ public Bar work(Company work) {
+ this.work = work;
+ return this;
+ }
+
+ public Bar goldCustomer(boolean goldCustomer) {
+ this.goldCustomer = goldCustomer;
+ return this;
+ }
+ }
+
+ private class MyConfigurer implements GeneratedPropertyConfigurer {
+
+ private int counter;
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object target, String name, Object value, boolean ignoreCase) {
+ name = name.toLowerCase(Locale.US);
+ name = name.replaceAll("-","");
+ if (target instanceof Bar) {
+ Bar bar = (Bar) target;
+ if ("age".equals(name)) {
+ bar.withAge(Integer.parseInt(value.toString()));
+ counter++;
+ return true;
+ } else if ("rider".equals(name)) {
+ bar.withRider(Boolean.parseBoolean(value.toString()));
+ counter++;
+ return true;
+ } else if ("work".equals(name)) {
+ bar.work((Company) value);
+ counter++;
+ return true;
+ } else if ("goldcustomer".equals(name)) {
+ bar.goldCustomer(Boolean.parseBoolean(value.toString()));
+ counter++;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int getCounter() {
+ return counter;
+ }
+
+ public void reset() {
+ counter = 0;
+ }
+ }
+
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java
index 7b86a3e..4d8fa36 100644
--- a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java
@@ -383,6 +383,82 @@ public class PropertyBindingSupportTest extends ContextTestSupport {
assertEquals(false, foo.getAnimal().isDangerous());
}
+ @Test
+ public void testPropertiesOptionallKey() throws Exception {
+ Foo foo = new Foo();
+
+ Map<String, Object> prop = new HashMap<>();
+ prop.put("name", "James");
+ prop.put("?bar.AGE", "33");
+ prop.put("BAR.{{committer}}", "true");
+ prop.put("bar.gOLd-Customer", "true");
+ prop.put("?bar.silver-Customer", "true");
+ prop.put("?bAr.work.ID", "123");
+ prop.put("?bar.WORk.naME", "{{companyName}}");
+ prop.put("?bar.work.addresss", "Some street");
+ prop.put("?bar.work.addresss.zip", "1234");
+
+ PropertyBindingSupport.build().withIgnoreCase(true).bind(context, foo, prop);
+
+ assertEquals("James", foo.getName());
+ assertEquals(33, foo.getBar().getAge());
+ assertTrue(foo.getBar().isRider());
+ assertTrue(foo.getBar().isGoldCustomer());
+ assertEquals(123, foo.getBar().getWork().getId());
+ assertEquals("Acme", foo.getBar().getWork().getName());
+
+ assertFalse("Should NOT bind all properties", prop.isEmpty());
+ assertEquals(3, prop.size());
+ assertTrue(prop.containsKey("?bar.silver-Customer"));
+ assertTrue(prop.containsKey("?bar.work.addresss"));
+ assertTrue(prop.containsKey("?bar.work.addresss.zip"));
+ }
+
+ @Test
+ public void testPropertiesOptionallKeyMandatory() throws Exception {
+ Foo foo = new Foo();
+
+ Map<String, Object> prop = new HashMap<>();
+ prop.put("name", "James");
+ prop.put("bar.AGE", "33");
+ prop.put("BAR.{{committer}}", "true");
+ prop.put("bar.gOLd-Customer", "true");
+ prop.put("?bar.silver-Customer", "true");
+ prop.put("?bAr.work.ID", "123");
+ prop.put("?bar.WORk.naME", "{{companyName}}");
+ prop.put("?bar.work.addresss", "Some street");
+ prop.put("?bar.work.addresss.zip", "1234");
+
+ PropertyBindingSupport.build().withIgnoreCase(true).withMandatory(true).bind(context, foo, prop);
+
+ assertEquals("James", foo.getName());
+ assertEquals(33, foo.getBar().getAge());
+ assertTrue(foo.getBar().isRider());
+ assertTrue(foo.getBar().isGoldCustomer());
+ assertEquals(123, foo.getBar().getWork().getId());
+ assertEquals("Acme", foo.getBar().getWork().getName());
+
+ assertFalse("Should NOT bind all properties", prop.isEmpty());
+ assertEquals(3, prop.size());
+ assertTrue(prop.containsKey("?bar.silver-Customer"));
+ assertTrue(prop.containsKey("?bar.work.addresss"));
+ assertTrue(prop.containsKey("?bar.work.addresss.zip"));
+
+ // should not fail as we marked the option as optional
+ prop.put("?bar.unknown", "123");
+ PropertyBindingSupport.build().withIgnoreCase(true).withMandatory(true).bind(context, foo, prop);
+ prop.remove("?bar.unknown");
+
+ // should fail as its mandatory
+ prop.put("bar.unknown", "123");
+ try {
+ PropertyBindingSupport.build().withIgnoreCase(true).withMandatory(true).bind(context, foo, prop);
+ fail("Should fail");
+ } catch (PropertyBindingException e) {
+ assertEquals("bar.unknown", e.getPropertyName());
+ }
+ }
+
public static class Foo {
private String name;
private Bar bar = new Bar();
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/IntrospectionSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/IntrospectionSupport.java
index db8b473..178e193 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/IntrospectionSupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/IntrospectionSupport.java
@@ -783,7 +783,7 @@ public final class IntrospectionSupport {
}
static boolean isPropertyPlaceholder(CamelContext context, Object value) {
- if (context != null) {
+ if (context != null && value != null) {
String text = value.toString();
return text.contains(PropertiesComponent.PREFIX_TOKEN) && text.contains(PropertiesComponent.SUFFIX_TOKEN);
}
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 9ed54cc..5c60834 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
@@ -57,6 +57,13 @@ import static org.apache.camel.util.ObjectHelper.isNotEmpty;
* #class:com.foo.MyClass('Hello World', 5, true)</li>.
* <li>ignore case - Whether to ignore case for property keys<li>
* </ul>
+ *
+ * Keys can be marked as optional if the key name starts with a question mark, such as:
+ * <pre>
+ * foo=123
+ * ?bar=false
+ * </pre>
+ * Where foo is mandatory, and bar is optional.
*/
public final class PropertyBindingSupport {
@@ -464,12 +471,18 @@ public final class PropertyBindingSupport {
final String uOptionPrefix = ignoreCase && isNotEmpty(optionPrefix) ? optionPrefix.toUpperCase(Locale.US) : "";
final int size = properties.size();
+ // use configuer first to set the options it can do
if (configurer != null) {
for (Iterator<Map.Entry<String, Object>> iter = properties.entrySet().iterator(); iter.hasNext();) {
Map.Entry<String, Object> entry = iter.next();
String key = entry.getKey();
Object value = entry.getValue();
+ final boolean optional = key.startsWith("?");
+ if (optional) {
+ key = key.substring(1);
+ }
+
// property configurer does not support nested names so skip if the name has a dot
if (key.indexOf('.') == -1) {
try {
@@ -491,15 +504,28 @@ public final class PropertyBindingSupport {
}
}
- // must set reference parameters first before the other bindings
+ // then we must set reference parameters before the other bindings
setReferenceProperties(camelContext, target, properties);
- // sort the keys by nesting level
+ // sort the keys by nesting level and set the remainder
properties.keySet().stream()
.sorted(Comparator.comparingInt(s -> StringHelper.countChar(s, '.')))
.forEach(key -> {
+ Object text = properties.get(key);
final String propertyKey = key;
- final Object value = properties.get(key);
+ final boolean optional = key.startsWith("?");
+ if (optional) {
+ key = key.substring(1);
+ }
+ if (placeholder) {
+ // resolve property placeholders
+ key = camelContext.resolvePropertyPlaceholders(key);
+ if (text instanceof String) {
+ // resolve property placeholders
+ text = camelContext.resolvePropertyPlaceholders(text.toString());
+ }
+ }
+ final Object value = text;
if (isNotEmpty(optionPrefix)) {
boolean match = key.startsWith(optionPrefix) || ignoreCase && key.toUpperCase(Locale.US).startsWith(uOptionPrefix);
@@ -512,7 +538,11 @@ public final class PropertyBindingSupport {
boolean bound = false;
if (configurer != null) {
// attempt configurer first
- bound = configurer.configure(camelContext, target, key, value, ignoreCase);
+ try {
+ bound = configurer.configure(camelContext, target, key, value, ignoreCase);
+ } catch (Exception e) {
+ throw new PropertyBindingException(target, key, value, e);
+ }
}
if (!bound) {
bound = bindProperty(camelContext, target, key, value, ignoreCase, nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder);
@@ -520,7 +550,7 @@ public final class PropertyBindingSupport {
if (bound && removeParameter) {
properties.remove(propertyKey);
}
- if (mandatory && !bound) {
+ if (mandatory && !optional && !bound) {
throw new PropertyBindingException(target, propertyKey, value);
}
}
@@ -730,6 +760,11 @@ public final class PropertyBindingSupport {
Map.Entry<String, Object> entry = it.next();
String name = entry.getKey();
+ final boolean optional = name.startsWith("?");
+ if (optional) {
+ name = name.substring(1);
+ }
+
// we only support basic keys
if (name.contains(".") || name.contains("[") || name.contains("]")) {
continue;