You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2020/09/04 15:03:59 UTC
[camel] 01/03: CAMEL-14672: Invoke customizers as part of services
initialization
This is an automated email from the ASF dual-hosted git repository.
lburgazzoli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 493e93d44ba82d201a3a75e095b43a547b86c271
Author: lburgazzoli <lb...@gmail.com>
AuthorDate: Tue Sep 1 19:40:57 2020 +0200
CAMEL-14672: Invoke customizers as part of services initialization
---
.../org/apache/camel/spi/ComponentCustomizer.java | 209 +++++-
.../org/apache/camel/spi/DataFormatCustomizer.java | 213 +++++-
.../org/apache/camel/spi/DataFormatResolver.java | 10 -
.../org/apache/camel/spi/LanguageCustomizer.java | 213 +++++-
.../org/apache/camel/spi/LifecycleStrategy.java | 18 +
.../camel/impl/engine/AbstractCamelContext.java | 76 +-
.../impl/engine/CustomizersLifecycleStrategy.java | 87 +++
.../impl/engine/DefaultDataFormatResolver.java | 14 -
.../camel/impl/engine/DefaultLanguageResolver.java | 7 -
.../org/apache/camel/support/CustomizersTest.java | 457 ++++++++++++
.../org/apache/camel/main/MainCustomizerTest.java | 99 +++
.../apache/camel/support/CustomizersSupport.java | 129 ++++
.../camel/support/PropertyBindingSupport.java | 787 ++++++++++-----------
.../camel/support/PropertyConfigurerHelper.java | 52 +-
.../org/apache/camel/util/CollectionHelper.java | 1 +
15 files changed, 1901 insertions(+), 471 deletions(-)
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ComponentCustomizer.java b/core/camel-api/src/main/java/org/apache/camel/spi/ComponentCustomizer.java
index 59dc6c7..2a2cf28 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/ComponentCustomizer.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ComponentCustomizer.java
@@ -16,14 +16,217 @@
*/
package org.apache.camel.spi;
+import java.util.function.BiPredicate;
+
import org.apache.camel.Component;
+import org.apache.camel.Ordered;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.ThrowingBiConsumer;
+import org.apache.camel.util.function.ThrowingConsumer;
+/**
+ * To apply custom configurations to {@link Component} instances.
+ */
@FunctionalInterface
-public interface ComponentCustomizer<T extends Component> {
+public interface ComponentCustomizer extends Ordered {
+ /**
+ * Create a generic {@link Builder}.
+ *
+ * @return the {@link Builder}
+ */
+ static Builder<Component> builder() {
+ return builder(Component.class);
+ }
+
+ /**
+ * Create a typed {@link Builder} that can process a concrete component type instance.
+ *
+ * @param type the concrete type of the {@link Component}
+ * @return the {@link Builder}
+ */
+ static <T extends Component> Builder<T> builder(Class<T> type) {
+ return new Builder<>(type);
+ }
+
+ /**
+ * Create a {@link ComponentCustomizer} that can process a concrete component type instance.
+ *
+ * @param type the concrete type of the {@link Component}
+ * @param consumer the {@link Component} configuration logic
+ * @return the {@link ComponentCustomizer}
+ */
+ static <T extends Component> ComponentCustomizer forType(Class<T> type, ThrowingConsumer<T, Exception> consumer) {
+ return builder(type).build(consumer);
+ }
+
/**
* Customize the specified {@link Component}.
*
- * @param component the component to customize
+ * @param name the unique name of the component
+ * @param target the component to configure
+ */
+ void configure(String name, Component target);
+
+ /**
+ * Checks whether this customizer should be applied to the given {@link Component}.
+ *
+ * @param name the unique name of the component
+ * @param target the component to configure
+ * @return <tt>true</tt> if the customizer should be applied
+ */
+ default boolean isEnabled(String name, Component target) {
+ return true;
+ }
+
+ @Override
+ default int getOrder() {
+ return 0;
+ }
+
+ // ***************************************
+ //
+ // Filter
+ //
+ // ***************************************
+
+ /**
+ * Used as additional filer mechanism to control if customizers need to be applied or not. Each of this filter is
+ * applied before any of the discovered {@link ComponentCustomizer} is invoked.
+ * </p>
+ * This interface is useful to implement enable/disable logic on a group of customizers.
+ */
+ @FunctionalInterface
+ interface Policy extends BiPredicate<String, Component> {
+ /**
+ * A simple deny-all policy.
+ *
+ * @return false
+ */
+ static Policy none() {
+ return new Policy() {
+ @Override
+ public boolean test(String s, Component target) {
+ return false;
+ }
+ };
+ }
+
+ /**
+ * A simple allow-all policy.
+ *
+ * @return true
+ */
+ static Policy any() {
+ return new Policy() {
+ @Override
+ public boolean test(String s, Component target) {
+ return true;
+ }
+ };
+ }
+ }
+
+ // ***************************************
+ //
+ // Builders
+ //
+ // ***************************************
+
+ /**
+ * A fluent builder to create a {@link ComponentCustomizer} instance.
+ *
+ * @param <T> the concrete type of the {@link Component}.
*/
- void customize(T component);
+ class Builder<T extends Component> {
+ private final Class<T> type;
+ private BiPredicate<String, Component> condition;
+ private int order;
+
+ public Builder(Class<T> type) {
+ this.type = type;
+ }
+
+ public Builder<T> withOrder(int order) {
+ this.order = order;
+
+ return this;
+ }
+
+ public Builder<T> withCondition(BiPredicate<String, Component> condition) {
+ this.condition = condition;
+
+ return this;
+ }
+
+ public ComponentCustomizer build(ThrowingConsumer<T, Exception> consumer) {
+ return build(new ThrowingBiConsumer<String, T, Exception>() {
+ @Override
+ public void accept(String name, T target) throws Exception {
+ consumer.accept(target);
+ }
+ });
+ }
+
+ public ComponentCustomizer build(ThrowingBiConsumer<String, T, Exception> consumer) {
+ final int order = this.order;
+ final BiPredicate<String, Component> condition = condition();
+
+ return new ComponentCustomizer() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public void configure(String name, Component target) {
+ ObjectHelper.notNull(name, "component name");
+ ObjectHelper.notNull(target, "component instance");
+
+ try {
+ consumer.accept(name, (T) target);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isEnabled(String name, Component target) {
+ ObjectHelper.notNull(name, "component name");
+ ObjectHelper.notNull(target, "component instance");
+
+ return condition.test(name, target);
+ }
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+ };
+ }
+
+ private BiPredicate<String, Component> condition() {
+ if (type.equals(Component.class)) {
+ return this.condition != null
+ ? this.condition
+ : new BiPredicate<String, Component>() {
+ @Override
+ public boolean test(String s, Component language) {
+ return true;
+ }
+ };
+ }
+
+ if (condition == null) {
+ return new BiPredicate<String, Component>() {
+ @Override
+ public boolean test(String name, Component target) {
+ return type.isAssignableFrom(target.getClass());
+ }
+ };
+ }
+
+ return new BiPredicate<String, Component>() {
+ @Override
+ public boolean test(String name, Component target) {
+ return type.isAssignableFrom(target.getClass()) && condition.test(name, target);
+ }
+ };
+ }
+ }
}
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatCustomizer.java b/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatCustomizer.java
index 7e128f6..7507f75 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatCustomizer.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatCustomizer.java
@@ -16,12 +16,217 @@
*/
package org.apache.camel.spi;
+import java.util.function.BiPredicate;
+
+import org.apache.camel.Component;
+import org.apache.camel.Ordered;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.ThrowingBiConsumer;
+import org.apache.camel.util.function.ThrowingConsumer;
+
+/**
+ * To apply custom configurations to {@link DataFormat} instances.
+ */
@FunctionalInterface
-public interface DataFormatCustomizer<T extends DataFormat> {
+public interface DataFormatCustomizer extends Ordered {
+ /**
+ * Create a generic {@link Builder}.
+ *
+ * @return the {@link Builder}
+ */
+ static Builder<DataFormat> builder() {
+ return builder(DataFormat.class);
+ }
+
+ /**
+ * Create a typed {@link Builder} that can process a concrete data format type instance.
+ *
+ * @param type the concrete type of the {@link Component}
+ * @return the {@link ComponentCustomizer.Builder}
+ */
+ static <T extends DataFormat> Builder<T> builder(Class<T> type) {
+ return new Builder<>(type);
+ }
+
/**
- * Customize the specified {@link DataFormat}
+ * Create a {@link DataFormatCustomizer} that can process a concrete data format type instance.
*
- * @param dataformat the dataformat to customize
+ * @param type the concrete type of the {@link DataFormat}
+ * @param consumer the {@link DataFormat} configuration logic
+ * @return the {@link DataFormatCustomizer}
*/
- void customize(T dataformat);
+ static <T extends DataFormat> DataFormatCustomizer forType(Class<T> type, ThrowingConsumer<T, Exception> consumer) {
+ return builder(type).build(consumer);
+ }
+
+ /**
+ * Customize the specified {@link DataFormat}.
+ *
+ * @param name the unique name of the data format
+ * @param target the data format to configure
+ */
+ void configure(String name, DataFormat target);
+
+ /**
+ * Checks whether this customizer should be applied to the given {@link DataFormat}.
+ *
+ * @param name the unique name of the data format
+ * @param target the data format to configure
+ * @return <tt>true</tt> if the customizer should be applied
+ */
+ default boolean isEnabled(String name, DataFormat target) {
+ return true;
+ }
+
+ @Override
+ default int getOrder() {
+ return 0;
+ }
+
+ // ***************************************
+ //
+ // Filter
+ //
+ // ***************************************
+
+ /**
+ * Used as additional filer mechanism to control if customizers need to be applied or not. Each of this filter is
+ * applied before any of the discovered {@link DataFormatCustomizer} is invoked.
+ * </p>
+ * This interface is useful to implement enable/disable logic on a group of customizers.
+ */
+ @FunctionalInterface
+ interface Policy extends BiPredicate<String, DataFormat> {
+ /**
+ * A simple deny-all policy.
+ *
+ * @return false
+ */
+ static Policy none() {
+ return new Policy() {
+ @Override
+ public boolean test(String s, DataFormat target) {
+ return false;
+ }
+ };
+ }
+
+ /**
+ * A simple allow-all policy.
+ *
+ * @return true
+ */
+ static Policy any() {
+ return new Policy() {
+ @Override
+ public boolean test(String s, DataFormat target) {
+ return true;
+ }
+ };
+ }
+ }
+
+ // ***************************************
+ //
+ // Builders
+ //
+ // ***************************************
+
+ /**
+ * A fluent builder to create a {@link DataFormatCustomizer} instance.
+ *
+ * @param <T> the concrete type of the {@link DataFormat}.
+ */
+ class Builder<T extends DataFormat> {
+ private final Class<T> type;
+ private BiPredicate<String, DataFormat> condition;
+ private int order;
+
+ public Builder(Class<T> type) {
+ this.type = type;
+ }
+
+ public Builder<T> withOrder(int order) {
+ this.order = order;
+
+ return this;
+ }
+
+ public Builder<T> withCondition(BiPredicate<String, DataFormat> condition) {
+ this.condition = condition;
+
+ return this;
+ }
+
+ public DataFormatCustomizer build(ThrowingConsumer<T, Exception> consumer) {
+ return build(new ThrowingBiConsumer<String, T, Exception>() {
+ @Override
+ public void accept(String name, T target) throws Exception {
+ consumer.accept(target);
+ }
+ });
+ }
+
+ public DataFormatCustomizer build(ThrowingBiConsumer<String, T, Exception> consumer) {
+ final int order = this.order;
+ final BiPredicate<String, DataFormat> condition = condition();
+
+ return new DataFormatCustomizer() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public void configure(String name, DataFormat target) {
+ ObjectHelper.notNull(name, "data format name");
+ ObjectHelper.notNull(target, "data format instance");
+
+ try {
+ consumer.accept(name, (T) target);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isEnabled(String name, DataFormat target) {
+ ObjectHelper.notNull(name, "data format name");
+ ObjectHelper.notNull(target, "data format instance");
+
+ return condition.test(name, target);
+ }
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+ };
+ }
+
+ private BiPredicate<String, DataFormat> condition() {
+ if (type.equals(DataFormat.class)) {
+ return this.condition != null
+ ? this.condition
+ : new BiPredicate<String, DataFormat>() {
+ @Override
+ public boolean test(String s, DataFormat language) {
+ return true;
+ }
+ };
+ }
+
+ if (condition == null) {
+ return new BiPredicate<String, DataFormat>() {
+ @Override
+ public boolean test(String name, DataFormat target) {
+ return type.isAssignableFrom(target.getClass());
+ }
+ };
+ }
+
+ return new BiPredicate<String, DataFormat>() {
+ @Override
+ public boolean test(String name, DataFormat target) {
+ return type.isAssignableFrom(target.getClass()) && condition.test(name, target);
+ }
+ };
+ }
+ }
}
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatResolver.java b/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatResolver.java
index 12b47ad..492f247 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatResolver.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/DataFormatResolver.java
@@ -22,16 +22,6 @@ import org.apache.camel.CamelContext;
* Represents a resolver of data formats.
*/
public interface DataFormatResolver {
-
- /**
- * Resolves the given data format given its name.
- *
- * @param name the name of the data format to lookup in {@link org.apache.camel.spi.Registry} or create
- * @param context the camel context
- * @return the data format or <tt>null</tt> if not possible to resolve
- */
- DataFormat resolveDataFormat(String name, CamelContext context);
-
/**
* Creates the given data format given its name.
*
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/LanguageCustomizer.java b/core/camel-api/src/main/java/org/apache/camel/spi/LanguageCustomizer.java
index 34b3218..76c2ea8 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/LanguageCustomizer.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/LanguageCustomizer.java
@@ -16,12 +16,217 @@
*/
package org.apache.camel.spi;
+import java.util.function.BiPredicate;
+
+import org.apache.camel.Component;
+import org.apache.camel.Ordered;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.ThrowingBiConsumer;
+import org.apache.camel.util.function.ThrowingConsumer;
+
+/**
+ * To apply custom configurations to {@link Language} instances.
+ */
@FunctionalInterface
-public interface LanguageCustomizer<T extends Language> {
+public interface LanguageCustomizer extends Ordered {
+ /**
+ * Create a generic {@link Builder}.
+ *
+ * @return the {@link Builder}
+ */
+ static Builder<Language> builder() {
+ return builder(Language.class);
+ }
+
+ /**
+ * Create a typed {@link Builder} that can process a concrete language type instance.
+ *
+ * @param type the concrete type of the {@link Component}
+ * @return the {@link ComponentCustomizer.Builder}
+ */
+ static <T extends Language> Builder<T> builder(Class<T> type) {
+ return new Builder<>(type);
+ }
+
/**
- * Customize the specified {@link Language}
+ * Create a {@link DataFormatCustomizer} that can process a concrete language type instance.
*
- * @param language the language to customize
+ * @param type the concrete type of the {@link Language}
+ * @param consumer the {@link Language} configuration logic
+ * @return the {@link LanguageCustomizer}
*/
- void customize(T language);
+ static <T extends Language> LanguageCustomizer forType(Class<T> type, ThrowingConsumer<T, Exception> consumer) {
+ return builder(type).build(consumer);
+ }
+
+ /**
+ * Customize the specified {@link Language}.
+ *
+ * @param name the unique name of the language
+ * @param target the language to configure
+ */
+ void configure(String name, Language target);
+
+ /**
+ * Checks whether this customizer should be applied to the given {@link Language}.
+ *
+ * @param name the unique name of the language
+ * @param target the language to configure
+ * @return <tt>true</tt> if the customizer should be applied
+ */
+ default boolean isEnabled(String name, Language target) {
+ return true;
+ }
+
+ @Override
+ default int getOrder() {
+ return 0;
+ }
+
+ // ***************************************
+ //
+ // Filter
+ //
+ // ***************************************
+
+ /**
+ * Used as additional filer mechanism to control if customizers need to be applied or not. Each of this filter is
+ * applied before any of the discovered {@link LanguageCustomizer} is invoked.
+ * </p>
+ * This interface is useful to implement enable/disable logic on a group of customizers.
+ */
+ @FunctionalInterface
+ interface Policy extends BiPredicate<String, Language> {
+ /**
+ * A simple deny-all policy.
+ *
+ * @return false
+ */
+ static Policy none() {
+ return new Policy() {
+ @Override
+ public boolean test(String s, Language target) {
+ return false;
+ }
+ };
+ }
+
+ /**
+ * A simple allow-all policy.
+ *
+ * @return true
+ */
+ static Policy any() {
+ return new Policy() {
+ @Override
+ public boolean test(String s, Language target) {
+ return true;
+ }
+ };
+ }
+ }
+
+ // ***************************************
+ //
+ // Builders
+ //
+ // ***************************************
+
+ /**
+ * A fluent builder to create a {@link LanguageCustomizer} instance.
+ *
+ * @param <T> the concrete type of the {@link Language}.
+ */
+ class Builder<T extends Language> {
+ private final Class<T> type;
+ private BiPredicate<String, Language> condition;
+ private int order;
+
+ public Builder(Class<T> type) {
+ this.type = type;
+ }
+
+ public Builder<T> withOrder(int order) {
+ this.order = order;
+
+ return this;
+ }
+
+ public Builder<T> withCondition(BiPredicate<String, Language> condition) {
+ this.condition = condition;
+
+ return this;
+ }
+
+ public LanguageCustomizer build(ThrowingConsumer<T, Exception> consumer) {
+ return build(new ThrowingBiConsumer<String, T, Exception>() {
+ @Override
+ public void accept(String name, T target) throws Exception {
+ consumer.accept(target);
+ }
+ });
+ }
+
+ public LanguageCustomizer build(ThrowingBiConsumer<String, T, Exception> consumer) {
+ final int order = this.order;
+ final BiPredicate<String, Language> condition = this.condition != null ? this.condition : (name, target) -> true;
+
+ return new LanguageCustomizer() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public void configure(String name, Language target) {
+ ObjectHelper.notNull(name, "language name");
+ ObjectHelper.notNull(target, "language instance");
+
+ try {
+ consumer.accept(name, (T) target);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isEnabled(String name, Language target) {
+ ObjectHelper.notNull(name, "language name");
+ ObjectHelper.notNull(target, "language instance");
+
+ return type.isAssignableFrom(target.getClass()) && condition.test(name, target);
+ }
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+ };
+ }
+
+ private BiPredicate<String, Language> condition() {
+ if (type.equals(Language.class)) {
+ return this.condition != null
+ ? this.condition
+ : new BiPredicate<String, Language>() {
+ @Override
+ public boolean test(String s, Language language) {
+ return true;
+ }
+ };
+ }
+
+ if (condition == null) {
+ return new BiPredicate<String, Language>() {
+ @Override
+ public boolean test(String name, Language target) {
+ return type.isAssignableFrom(target.getClass());
+ }
+ };
+ }
+
+ return new BiPredicate<String, Language>() {
+ @Override
+ public boolean test(String name, Language target) {
+ return type.isAssignableFrom(target.getClass()) && condition.test(name, target);
+ }
+ };
+ }
+ }
}
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/LifecycleStrategy.java b/core/camel-api/src/main/java/org/apache/camel/spi/LifecycleStrategy.java
index a31edec..f837cae 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/LifecycleStrategy.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/LifecycleStrategy.java
@@ -144,6 +144,24 @@ public interface LifecycleStrategy {
void onEndpointRemove(Endpoint endpoint);
/**
+ * Notification on {@link DataFormat} being resolved from the {@link Registry}
+ *
+ * @param name the unique name of the {@link DataFormat}
+ * @param dataFormat the resolved {@link DataFormat}
+ */
+ default void onDataFormatCreated(String name, DataFormat dataFormat) {
+ }
+
+ /**
+ * Notification on a {@link Language} instance being resolved.
+ *
+ * @param name the unique name of the {@link Language}
+ * @param language the created {@link Language}
+ */
+ default void onLanguageCreated(String name, Language language) {
+ }
+
+ /**
* Notification on adding a {@link Service}.
*
* @param context the camel context
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 f8a9252..5a6a30f 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
@@ -151,6 +151,7 @@ import org.apache.camel.support.LRUCacheFactory;
import org.apache.camel.support.NormalizedUri;
import org.apache.camel.support.OrderedComparator;
import org.apache.camel.support.ProcessorEndpoint;
+import org.apache.camel.support.ResolverHelper;
import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.camel.support.service.BaseService;
import org.apache.camel.support.service.ServiceHelper;
@@ -186,6 +187,7 @@ public abstract class AbstractCamelContext extends BaseService
private final List<StartupListener> startupListeners = new CopyOnWriteArrayList<>();
private final DeferServiceStartupListener deferStartupListener = new DeferServiceStartupListener();
private final Map<String, Language> languages = new ConcurrentHashMap<>();
+ private final Map<String, DataFormat> dataformats = new ConcurrentHashMap<>();
private final List<LifecycleStrategy> lifecycleStrategies = new CopyOnWriteArrayList<>();
private final ThreadLocal<Boolean> isStartingRoutes = new ThreadLocal<>();
private final ThreadLocal<Boolean> isSetupRoutes = new ThreadLocal<>();
@@ -329,6 +331,9 @@ public abstract class AbstractCamelContext extends BaseService
// add a default LifecycleStrategy that discover strategies on the registry and invoke them
this.lifecycleStrategies.add(new OnCamelContextLifecycleStrategy());
+ // add a default LifecycleStrategy to customize services using customizers from registry
+ this.lifecycleStrategies.add(new CustomizersLifecycleStrategy(this));
+
if (build) {
try {
build();
@@ -1730,22 +1735,38 @@ public abstract class AbstractCamelContext extends BaseService
public Language resolveLanguage(String language) {
Language answer;
synchronized (languages) {
- answer = languages.get(language);
+ // as first iteration, check if there is a language instance for the given name
+ // bound to the registry
+ answer = ResolverHelper.lookupLanguageInRegistryWithFallback(getCamelContextReference(), language);
+ if (answer != null) {
+ Language old = languages.put(language, answer);
+ // if the language has already been loaded, thus it is already registered
+ // in the local language cache, we can return it as it has already been
+ // initialized and configured
+ if (old == answer) {
+ return answer;
+ }
+ } else {
+ answer = languages.get(language);
- // check if the language is singleton, if so return the shared
- // instance
- if (IsSingleton.test(answer)) {
- return answer;
+ // check if the language is singleton, if so return the shared
+ // instance
+ if (IsSingleton.test(answer)) {
+ return answer;
+ } else {
+ answer = null;
+ }
}
- // language not known or not singleton, then use resolver
- answer = getLanguageResolver().resolveLanguage(language, getCamelContextReference());
+ if (answer == null) {
+ // language not known or not singleton, then use resolver
+ answer = getLanguageResolver().resolveLanguage(language, getCamelContextReference());
+ }
- // inject CamelContext if aware
if (answer != null) {
- if (answer instanceof CamelContextAware) {
- ((CamelContextAware) answer).setCamelContext(getCamelContextReference());
- }
+ // inject CamelContext if aware
+ CamelContextAware.trySetCamelContext(answer, getCamelContextReference());
+
if (answer instanceof Service) {
try {
startService((Service) answer);
@@ -1754,6 +1775,10 @@ public abstract class AbstractCamelContext extends BaseService
}
}
+ for (LifecycleStrategy strategy : lifecycleStrategies) {
+ strategy.onLanguageCreated(language, answer);
+ }
+
languages.put(language, answer);
}
}
@@ -3739,14 +3764,25 @@ public abstract class AbstractCamelContext extends BaseService
@Override
public DataFormat resolveDataFormat(String name) {
- DataFormat answer = getDataFormatResolver().resolveDataFormat(name, getCamelContextReference());
+ final DataFormat answer = dataformats.computeIfAbsent(name, new Function<String, DataFormat>() {
+ @Override
+ public DataFormat apply(String s) {
+ DataFormat df = ResolverHelper.lookupDataFormatInRegistryWithFallback(getCamelContextReference(), name);
- // inject CamelContext if aware
- if (answer instanceof CamelContextAware) {
- ((CamelContextAware) answer).setCamelContext(getCamelContextReference());
- }
+ if (df != null) {
+ // inject CamelContext if aware
+ CamelContextAware.trySetCamelContext(df, getCamelContextReference());
- return answer;
+ for (LifecycleStrategy strategy : lifecycleStrategies) {
+ strategy.onDataFormatCreated(name, df);
+ }
+ }
+
+ return df;
+ }
+ });
+
+ return answer != null ? answer : createDataFormat(name);
}
@Override
@@ -3754,8 +3790,10 @@ public abstract class AbstractCamelContext extends BaseService
DataFormat answer = getDataFormatResolver().createDataFormat(name, getCamelContextReference());
// inject CamelContext if aware
- if (answer instanceof CamelContextAware) {
- ((CamelContextAware) answer).setCamelContext(getCamelContextReference());
+ CamelContextAware.trySetCamelContext(answer, getCamelContextReference());
+
+ for (LifecycleStrategy strategy : lifecycleStrategies) {
+ strategy.onDataFormatCreated(name, answer);
}
return answer;
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/CustomizersLifecycleStrategy.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/CustomizersLifecycleStrategy.java
new file mode 100644
index 0000000..d2b5855
--- /dev/null
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/CustomizersLifecycleStrategy.java
@@ -0,0 +1,87 @@
+/*
+ * 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.Comparator;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
+import org.apache.camel.Ordered;
+import org.apache.camel.spi.ComponentCustomizer;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.DataFormatCustomizer;
+import org.apache.camel.spi.Language;
+import org.apache.camel.spi.LanguageCustomizer;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.support.LifecycleStrategySupport;
+
+class CustomizersLifecycleStrategy extends LifecycleStrategySupport {
+ private final CamelContext camelContext;
+
+ public CustomizersLifecycleStrategy(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ @Override
+ public void onComponentAdd(String name, Component component) {
+ Registry registry = this.camelContext.getRegistry();
+ if (registry == null) {
+ return;
+ }
+
+ Set<ComponentCustomizer.Policy> filters = registry.findByType(ComponentCustomizer.Policy.class);
+ if (filters.isEmpty() || filters.stream().allMatch(filter -> filter.test(name, component))) {
+ registry.findByType(ComponentCustomizer.class).stream()
+ .sorted(Comparator.comparingInt(Ordered::getOrder))
+ .filter(customizer -> customizer.isEnabled(name, component))
+ .forEach(customizer -> customizer.configure(name, component));
+ }
+ }
+
+ @Override
+ public void onDataFormatCreated(String name, DataFormat dataFormat) {
+ Registry registry = this.camelContext.getRegistry();
+ if (registry == null) {
+ return;
+ }
+
+ Set<DataFormatCustomizer.Policy> filters = registry.findByType(DataFormatCustomizer.Policy.class);
+ if (filters.isEmpty() || filters.stream().allMatch(filter -> filter.test(name, dataFormat))) {
+ registry.findByType(DataFormatCustomizer.class).stream()
+ .sorted(Comparator.comparingInt(Ordered::getOrder))
+ .filter(customizer -> customizer.isEnabled(name, dataFormat))
+ .forEach(customizer -> customizer.configure(name, dataFormat));
+ }
+ }
+
+ @Override
+ public void onLanguageCreated(String name, Language language) {
+ Registry registry = this.camelContext.getRegistry();
+ if (registry == null) {
+ return;
+ }
+
+ Set<LanguageCustomizer.Policy> filters = registry.findByType(LanguageCustomizer.Policy.class);
+ if (filters.isEmpty() || filters.stream().allMatch(filter -> filter.test(name, language))) {
+ registry.findByType(LanguageCustomizer.class).stream()
+ .sorted(Comparator.comparingInt(Ordered::getOrder))
+ .filter(customizer -> customizer.isEnabled(name, language))
+ .forEach(customizer -> customizer.configure(name, language));
+ }
+ }
+}
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultDataFormatResolver.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultDataFormatResolver.java
index 4d01e3e..525cba8 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultDataFormatResolver.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultDataFormatResolver.java
@@ -33,20 +33,6 @@ public class DefaultDataFormatResolver implements DataFormatResolver {
private FactoryFinder dataformatFactory;
@Override
- public DataFormat resolveDataFormat(String name, CamelContext context) {
- // lookup in registry first
- DataFormat dataFormat = ResolverHelper.lookupDataFormatInRegistryWithFallback(context, name);
-
- if (dataFormat == null) {
- // If not found in the registry, try to create a new instance using
- // a DataFormatFactory or from resources
- dataFormat = createDataFormat(name, context);
- }
-
- return dataFormat;
- }
-
- @Override
public DataFormat createDataFormat(String name, CamelContext context) {
DataFormat dataFormat = null;
diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultLanguageResolver.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultLanguageResolver.java
index 4043f9e..d596361 100644
--- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultLanguageResolver.java
+++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultLanguageResolver.java
@@ -23,7 +23,6 @@ import org.apache.camel.NoSuchLanguageException;
import org.apache.camel.spi.FactoryFinder;
import org.apache.camel.spi.Language;
import org.apache.camel.spi.LanguageResolver;
-import org.apache.camel.support.ResolverHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,12 +41,6 @@ public class DefaultLanguageResolver implements LanguageResolver {
@Override
public Language resolveLanguage(String name, CamelContext context) {
- // lookup in registry first
- Language languageReg = ResolverHelper.lookupLanguageInRegistryWithFallback(context, name);
- if (languageReg != null) {
- return languageReg;
- }
-
Class<?> type = null;
try {
type = findLanguage(name, context);
diff --git a/core/camel-core/src/test/java/org/apache/camel/support/CustomizersTest.java b/core/camel-core/src/test/java/org/apache/camel/support/CustomizersTest.java
new file mode 100644
index 0000000..151f67d
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/support/CustomizersTest.java
@@ -0,0 +1,457 @@
+/*
+ * 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.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.component.log.LogComponent;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.language.tokenizer.TokenizeLanguage;
+import org.apache.camel.spi.ComponentCustomizer;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.DataFormatCustomizer;
+import org.apache.camel.spi.DataFormatFactory;
+import org.apache.camel.spi.Language;
+import org.apache.camel.spi.LanguageCustomizer;
+import org.apache.camel.support.processor.DefaultExchangeFormatter;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.apache.camel.util.CollectionHelper.propertiesOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class CustomizersTest {
+
+ // *****************************
+ //
+ // Helpers
+ //
+ // *****************************
+
+ public static Stream<Arguments> disableLanguageCustomizationProperties() {
+ return Stream.of(
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true",
+ "camel.customizer.language.enabled", "true",
+ "camel.customizer.language.tokenize.enabled", "false")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true",
+ "camel.customizer.language.enabled", "false")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false")));
+ }
+
+ public static Stream<Arguments> enableLanguageCustomizationProperties() {
+ return Stream.of(
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false",
+ "camel.customizer.language.enabled", "false",
+ "camel.customizer.language.tokenize.enabled", "true")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false",
+ "camel.customizer.language.enabled", "true")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true")));
+ }
+
+ public static Stream<Arguments> disableDataFormatCustomizationProperties() {
+ return Stream.of(
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true",
+ "camel.customizer.dataformat.enabled", "true",
+ "camel.customizer.dataformat.my-df.enabled", "false")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true",
+ "camel.customizer.dataformat.enabled", "false")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false")));
+ }
+
+ public static Stream<Arguments> enableDataFormatCustomizationProperties() {
+ return Stream.of(
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false",
+ "camel.customizer.dataformat.enabled", "false",
+ "camel.customizer.dataformat.my-df.enabled", "true")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false",
+ "camel.customizer.dataformat.enabled", "true")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true")));
+ }
+
+ public static Stream<Arguments> disableComponentCustomizationProperties() {
+ return Stream.of(
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true",
+ "camel.customizer.component.enabled", "true",
+ "camel.customizer.component.log.enabled", "false")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true",
+ "camel.customizer.component.enabled", "false")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false")));
+ }
+
+ public static Stream<Arguments> enableComponentCustomizationProperties() {
+ return Stream.of(
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false",
+ "camel.customizer.component.enabled", "false",
+ "camel.customizer.component.log.enabled", "true")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "false",
+ "camel.customizer.component.enabled", "true")),
+ Arguments.of(propertiesOf(
+ "camel.customizer.enabled", "true")));
+ }
+
+ // *****************************
+ //
+ // Component
+ //
+ // *****************************
+
+ @Test
+ public void testComponentCustomization() {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind(
+ "log-customizer",
+ ComponentCustomizer.forType(
+ LogComponent.class,
+ target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+ assertTrue(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+ }
+
+ @Test
+ public void testComponentCustomizationWithFilter() {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind(
+ "customizer-filter",
+ ComponentCustomizer.Policy.none());
+ context.getRegistry().bind(
+ "log-customizer",
+ ComponentCustomizer.forType(
+ LogComponent.class,
+ target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+ assertFalse(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+ }
+
+ @Test
+ public void testComponentCustomizationWithFluentBuilder() {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind(
+ "log-customizer",
+ ComponentCustomizer.forType(
+ LogComponent.class,
+ target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+ assertTrue(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+ }
+
+ @ParameterizedTest
+ @MethodSource("disableComponentCustomizationProperties")
+ public void testComponentCustomizationDisabledByProperty(Properties properties) {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getPropertiesComponent().setInitialProperties(properties);
+
+ context.getRegistry().bind(
+ "customizer-filter",
+ new CustomizersSupport.ComponentCustomizationEnabledPolicy());
+ context.getRegistry().bind(
+ "log-customizer",
+ ComponentCustomizer.forType(
+ LogComponent.class,
+ target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+ assertFalse(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+ }
+
+ @ParameterizedTest
+ @MethodSource("enableComponentCustomizationProperties")
+ public void testComponentCustomizationEnabledByProperty(Properties properties) {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getPropertiesComponent().setInitialProperties(properties);
+
+ context.getRegistry().bind(
+ "customizer-filter",
+ new CustomizersSupport.ComponentCustomizationEnabledPolicy());
+ context.getRegistry().bind(
+ "log-customizer",
+ ComponentCustomizer.forType(
+ LogComponent.class,
+ target -> target.setExchangeFormatter(new MyExchangeFormatter())));
+
+ assertTrue(context.getComponent("log", LogComponent.class).getExchangeFormatter() instanceof MyExchangeFormatter);
+ }
+
+ // *****************************
+ //
+ // Data Format
+ //
+ // *****************************
+
+ @Test
+ public void testDataFormatCustomization() {
+ AtomicInteger counter = new AtomicInteger();
+
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind(
+ "my-df",
+ (DataFormatFactory) MyDataFormat::new);
+ context.getRegistry().bind(
+ "my-df-customizer",
+ DataFormatCustomizer.forType(MyDataFormat.class, target -> target.setId(counter.incrementAndGet())));
+
+ DataFormat df1 = context.resolveDataFormat("my-df");
+ DataFormat df2 = context.resolveDataFormat("my-df");
+
+ assertNotEquals(df1, df2);
+
+ assertTrue(df1 instanceof MyDataFormat);
+ assertEquals(1, ((MyDataFormat) df1).getId());
+ assertTrue(df2 instanceof MyDataFormat);
+ assertEquals(2, ((MyDataFormat) df2).getId());
+ }
+
+ @Test
+ public void testDataFormatCustomizationWithFilter() {
+ AtomicInteger counter = new AtomicInteger();
+
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind(
+ "customizer-filter",
+ DataFormatCustomizer.Policy.none());
+ context.getRegistry().bind(
+ "my-df",
+ (DataFormatFactory) MyDataFormat::new);
+ context.getRegistry().bind(
+ "my-df-customizer",
+ DataFormatCustomizer.forType(MyDataFormat.class, target -> target.setId(counter.incrementAndGet())));
+
+ DataFormat df1 = context.resolveDataFormat("my-df");
+ DataFormat df2 = context.resolveDataFormat("my-df");
+
+ assertNotEquals(df1, df2);
+
+ assertTrue(df1 instanceof MyDataFormat);
+ assertEquals(0, ((MyDataFormat) df1).getId());
+ assertTrue(df2 instanceof MyDataFormat);
+ assertEquals(0, ((MyDataFormat) df2).getId());
+ }
+
+ @ParameterizedTest
+ @MethodSource("disableDataFormatCustomizationProperties")
+ public void testDataFormatCustomizationDisabledByProperty(Properties properties) {
+ AtomicInteger counter = new AtomicInteger();
+
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getPropertiesComponent().setInitialProperties(properties);
+
+ context.getRegistry().bind(
+ "customizer-filter",
+ new CustomizersSupport.DataFormatCustomizationEnabledPolicy());
+ context.getRegistry().bind(
+ "my-df",
+ (DataFormatFactory) MyDataFormat::new);
+ context.getRegistry().bind(
+ "my-df-customizer",
+ DataFormatCustomizer.forType(MyDataFormat.class, target -> target.setId(counter.incrementAndGet())));
+
+ DataFormat df1 = context.resolveDataFormat("my-df");
+ assertEquals(0, ((MyDataFormat) df1).getId());
+ }
+
+ @ParameterizedTest
+ @MethodSource("enableDataFormatCustomizationProperties")
+ public void testDataFormatCustomizationEnabledByProperty(Properties properties) {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getPropertiesComponent().setInitialProperties(properties);
+
+ context.getRegistry().bind(
+ "customizer-filter",
+ new CustomizersSupport.DataFormatCustomizationEnabledPolicy());
+ context.getRegistry().bind(
+ "my-df",
+ (DataFormatFactory) MyDataFormat::new);
+ context.getRegistry().bind(
+ "my-df-customizer",
+ DataFormatCustomizer.forType(MyDataFormat.class, target -> target.setId(1)));
+
+ DataFormat df1 = context.resolveDataFormat("my-df");
+ assertEquals(1, ((MyDataFormat) df1).getId());
+ }
+
+ // *****************************
+ //
+ // Language
+ //
+ // *****************************
+
+ @Test
+ public void testLanguageCustomizationFromRegistry() {
+ AtomicInteger counter = new AtomicInteger();
+
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind(
+ "tokenize",
+ new TokenizeLanguage());
+ context.getRegistry().bind(
+ "tokenize-customizer",
+ LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("" + counter.incrementAndGet())));
+
+ Language l1 = context.resolveLanguage("tokenize");
+ assertTrue(l1 instanceof TokenizeLanguage);
+ assertEquals("1", ((TokenizeLanguage) l1).getGroup());
+
+ Language l2 = context.resolveLanguage("tokenize");
+ assertTrue(l2 instanceof TokenizeLanguage);
+ assertEquals("1", ((TokenizeLanguage) l2).getGroup());
+
+ // as the language is resolved via the registry, then the instance is the same
+ // even if it is not a singleton
+ assertSame(l1, l2);
+ }
+
+ @Test
+ public void testLanguageCustomizationFromResource() {
+ AtomicInteger counter = new AtomicInteger();
+
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind(
+ "tokenize-customizer",
+ LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("" + counter.incrementAndGet())));
+
+ Language l1 = context.resolveLanguage("tokenize");
+ assertTrue(l1 instanceof TokenizeLanguage);
+ assertEquals("1", ((TokenizeLanguage) l1).getGroup());
+
+ Language l2 = context.resolveLanguage("tokenize");
+ assertTrue(l2 instanceof TokenizeLanguage);
+ assertEquals("2", ((TokenizeLanguage) l2).getGroup());
+
+ assertNotSame(l1, l2);
+ }
+
+ @Test
+ public void testLanguageCustomizationFromResourceWithFilter() {
+ AtomicInteger counter = new AtomicInteger();
+
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getRegistry().bind(
+ "customizer-filter",
+ LanguageCustomizer.Policy.none());
+ context.getRegistry().bind(
+ "tokenize-customizer",
+ LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("" + counter.incrementAndGet())));
+
+ Language l1 = context.resolveLanguage("tokenize");
+ assertTrue(l1 instanceof TokenizeLanguage);
+ assertNull(((TokenizeLanguage) l1).getGroup());
+
+ Language l2 = context.resolveLanguage("tokenize");
+ assertTrue(l2 instanceof TokenizeLanguage);
+ assertNull(((TokenizeLanguage) l2).getGroup());
+
+ assertNotSame(l1, l2);
+ }
+
+ @ParameterizedTest
+ @MethodSource("disableLanguageCustomizationProperties")
+ public void testLanguageCustomizationDisabledByProperty(Properties initialProperties) {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getPropertiesComponent().setInitialProperties(initialProperties);
+
+ context.getRegistry().bind(
+ "customizer-filter",
+ new CustomizersSupport.LanguageCustomizationEnabledPolicy());
+ context.getRegistry().bind(
+ "tokenize-customizer",
+ LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("something")));
+
+ assertNotEquals("something", ((TokenizeLanguage) context.resolveLanguage("tokenize")).getGroup());
+ }
+
+ @ParameterizedTest
+ @MethodSource("enableLanguageCustomizationProperties")
+ public void testLanguageCustomizationEnabledByProperty(Properties initialProperties) {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.getPropertiesComponent().setInitialProperties(initialProperties);
+
+ context.getRegistry().bind(
+ "customizer-filter",
+ new CustomizersSupport.LanguageCustomizationEnabledPolicy());
+ context.getRegistry().bind(
+ "tokenize-customizer",
+ LanguageCustomizer.forType(TokenizeLanguage.class, target -> target.setGroup("something")));
+
+ assertEquals("something", ((TokenizeLanguage) context.resolveLanguage("tokenize")).getGroup());
+ }
+
+ // *****************************
+ //
+ // Model
+ //
+ // *****************************
+
+ public static class MyDataFormat implements DataFormat {
+ private int id;
+
+ @Override
+ public void marshal(Exchange exchange, Object graph, OutputStream stream) throws Exception {
+ }
+
+ @Override
+ public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
+ return null;
+ }
+
+ @Override
+ public void start() {
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+ }
+
+ public static class MyExchangeFormatter extends DefaultExchangeFormatter {
+ }
+}
diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java
new file mode 100644
index 0000000..c4c8031
--- /dev/null
+++ b/core/camel-main/src/test/java/org/apache/camel/main/MainCustomizerTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.main;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.component.log.LogComponent;
+import org.apache.camel.spi.ComponentCustomizer;
+import org.apache.camel.support.CustomizersSupport;
+import org.apache.camel.support.processor.DefaultExchangeFormatter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class MainCustomizerTest {
+ @Test
+ public void testComponentCustomizer() {
+ Main main = new Main();
+
+ try {
+ main.configure().addConfigurationClass(MyConfiguration.class);
+ main.start();
+
+ LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class);
+ assertTrue(component.getExchangeFormatter() instanceof MyFormatter);
+ } finally {
+ main.stop();
+ }
+ }
+
+ @Test
+ public void testComponentCustomizerDisabledWithPolicy() {
+ Main main = new Main();
+
+ try {
+ main.bind("my-filter", ComponentCustomizer.Policy.none());
+ main.configure().addConfigurationClass(MyConfiguration.class);
+ main.start();
+
+ LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class);
+ assertFalse(component.getExchangeFormatter() instanceof MyFormatter);
+ } finally {
+ main.stop();
+ }
+ }
+
+ @Test
+ public void testComponentCustomizerDisabledWithProperties() {
+ Main main = new Main();
+
+ try {
+ main.addInitialProperty("camel.customizer.component.log.enabled", "false");
+ main.bind("my-filter", ComponentCustomizer.Policy.any());
+ main.configure().addConfigurationClass(MyConfiguration.class);
+ main.start();
+
+ LogComponent component = main.getCamelContext().getComponent("log", LogComponent.class);
+ assertFalse(component.getExchangeFormatter() instanceof MyFormatter);
+ } finally {
+ main.stop();
+ }
+ }
+
+ // ****************************
+ //
+ // Helpers
+ //
+ // ****************************
+
+ public static class MyConfiguration {
+ @BindToRegistry
+ public ComponentCustomizer logCustomizer() {
+ return ComponentCustomizer.builder(LogComponent.class)
+ .build(component -> component.setExchangeFormatter(new MyFormatter()));
+ }
+
+ @BindToRegistry
+ public ComponentCustomizer.Policy componentCustomizationPolicy() {
+ return new CustomizersSupport.ComponentCustomizationEnabledPolicy();
+ }
+ }
+
+ public static class MyFormatter extends DefaultExchangeFormatter {
+ }
+}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/CustomizersSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/CustomizersSupport.java
new file mode 100644
index 0000000..6f28ad8
--- /dev/null
+++ b/core/camel-support/src/main/java/org/apache/camel/support/CustomizersSupport.java
@@ -0,0 +1,129 @@
+/*
+ * 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.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Component;
+import org.apache.camel.spi.ComponentCustomizer;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.DataFormatCustomizer;
+import org.apache.camel.spi.Language;
+import org.apache.camel.spi.LanguageCustomizer;
+
+public final class CustomizersSupport {
+ private CustomizersSupport() {
+ }
+
+ /**
+ * Determine the value of the "enabled" flag for a hierarchy of properties.
+ *
+ * @param camelContext the {@link CamelContext}
+ * @param prefixes an ordered list of prefixed (less restrictive to more restrictive)
+ * @return the value of the key `enabled` for most restrictive prefix
+ */
+ public static boolean isEnabled(CamelContext camelContext, String... prefixes) {
+ boolean answer = true;
+
+ // Loop over all the prefixes to find out the value of the key `enabled`
+ // for the most restrictive prefix.
+ for (String prefix : prefixes) {
+ String property = prefix.endsWith(".") ? prefix + "enabled" : prefix + ".enabled";
+
+ // evaluate the value of the current prefix using the parent one as
+ // default value so if the `enabled` property is not set, the parent
+ // one is used.
+ answer = camelContext.getPropertiesComponent()
+ .resolveProperty(property)
+ .map(Boolean::valueOf)
+ .orElse(answer);
+ }
+
+ return answer;
+ }
+
+ /**
+ * Base class for policies
+ */
+ private static class CamelContextAwarePolicy implements CamelContextAware {
+ private CamelContext camelContext;
+
+ @Override
+ public CamelContext getCamelContext() {
+ return this.camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+ }
+
+ /**
+ * A {@link ComponentCustomizer.Policy} that uses a hierarchical lists of properties to determine if customization
+ * is enabled for the given {@link org.apache.camel.Component}.
+ */
+ public static final class ComponentCustomizationEnabledPolicy
+ extends CamelContextAwarePolicy
+ implements ComponentCustomizer.Policy {
+
+ @Override
+ public boolean test(String name, Component target) {
+ return isEnabled(
+ getCamelContext(),
+ "camel.customizer",
+ "camel.customizer.component",
+ "camel.customizer.component." + name);
+ }
+ }
+
+ /**
+ * A {@link DataFormatCustomizer.Policy} that uses a hierarchical lists of properties to determine if customization
+ * is enabled for the given {@link org.apache.camel.spi.DataFormat}.
+ */
+ public static final class DataFormatCustomizationEnabledPolicy
+ extends CamelContextAwarePolicy
+ implements DataFormatCustomizer.Policy {
+
+ @Override
+ public boolean test(String name, DataFormat target) {
+ return isEnabled(
+ getCamelContext(),
+ "camel.customizer",
+ "camel.customizer.dataformat",
+ "camel.customizer.dataformat." + name);
+ }
+ }
+
+ /**
+ * A {@link LanguageCustomizer.Policy} that uses a hierarchical lists of properties to determine if customization is
+ * enabled for the given {@link org.apache.camel.spi.Language}.
+ */
+ public static final class LanguageCustomizationEnabledPolicy
+ extends CamelContextAwarePolicy
+ implements LanguageCustomizer.Policy {
+
+ @Override
+ public boolean test(String name, Language target) {
+ return isEnabled(
+ getCamelContext(),
+ "camel.customizer",
+ "camel.customizer.language",
+ "camel.customizer.language." + name);
+ }
+ }
+}
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 fc896bc..5fca48b 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
@@ -34,13 +34,11 @@ import java.util.Set;
import java.util.TreeMap;
import org.apache.camel.CamelContext;
-import org.apache.camel.Component;
import org.apache.camel.ExtendedCamelContext;
import org.apache.camel.PropertyBindingException;
import org.apache.camel.spi.BeanIntrospection;
import org.apache.camel.spi.PropertyConfigurer;
import org.apache.camel.spi.PropertyConfigurerGetter;
-import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.util.StringHelper;
import org.apache.camel.util.StringQuoteHelper;
@@ -88,233 +86,6 @@ import static org.apache.camel.util.StringHelper.startsWithIgnoreCase;
*/
public final class PropertyBindingSupport {
- /**
- * To use a fluent builder style to configure this property binding support.
- */
- public static class Builder {
-
- private CamelContext camelContext;
- private Object target;
- private Map<String, Object> properties;
- private boolean removeParameters = true;
- private boolean flattenProperties;
- private boolean mandatory;
- private boolean nesting = true;
- private boolean deepNesting = true;
- private boolean reference = true;
- private boolean placeholder = true;
- private boolean fluentBuilder = true;
- private boolean allowPrivateSetter = true;
- private boolean ignoreCase;
- private String optionPrefix;
- private boolean reflection = true;
- private PropertyConfigurer configurer;
-
- /**
- * CamelContext to be used
- */
- public Builder withCamelContext(CamelContext camelContext) {
- this.camelContext = camelContext;
- return this;
- }
-
- /**
- * Target object that should have parameters bound
- */
- public Builder withTarget(Object target) {
- this.target = target;
- return this;
- }
-
- /**
- * The properties to use for binding
- */
- public Builder withProperties(Map<String, Object> properties) {
- if (this.properties == null) {
- this.properties = properties;
- } else {
- // there may be existing options so add those if missing
- // we need to mutate existing as we are may be removing bound properties
- this.properties.forEach(properties::putIfAbsent);
- this.properties = properties;
- }
- return this;
- }
-
- /**
- * Adds property to use for binding
- */
- public Builder withProperty(String key, Object value) {
- if (this.properties == null) {
- this.properties = new LinkedHashMap<>();
- }
- this.properties.put(key, value);
- return this;
- }
-
- /**
- * Whether parameters should be removed when its bound
- */
- public Builder withRemoveParameters(boolean removeParameters) {
- this.removeParameters = removeParameters;
- return this;
- }
-
- /**
- * Whether properties should be flattened (when properties is a map of maps).
- */
- public Builder withFlattenProperties(boolean flattenProperties) {
- this.flattenProperties = flattenProperties;
- return this;
- }
-
- /**
- * Whether all parameters should be mandatory and successfully bound
- */
- public Builder withMandatory(boolean mandatory) {
- this.mandatory = mandatory;
- return this;
- }
-
- /**
- * Whether nesting is in use
- */
- public Builder withNesting(boolean nesting) {
- this.nesting = nesting;
- return this;
- }
-
- /**
- * Whether deep nesting is in use, where Camel will attempt to walk as deep as possible by creating new objects
- * in the OGNL graph if a property has a setter and the object can be created from a default no-arg constructor.
- */
- public Builder withDeepNesting(boolean deepNesting) {
- this.deepNesting = deepNesting;
- return this;
- }
-
- /**
- * Whether reference parameter (syntax starts with #) is in use
- */
- public Builder withReference(boolean reference) {
- this.reference = reference;
- return this;
- }
-
- /**
- * Whether to use Camels property placeholder to resolve placeholders on keys and values
- */
- public Builder withPlaceholder(boolean placeholder) {
- this.placeholder = placeholder;
- return this;
- }
-
- /**
- * Whether fluent builder is allowed as a valid getter/setter
- */
- public Builder withFluentBuilder(boolean fluentBuilder) {
- this.fluentBuilder = fluentBuilder;
- return this;
- }
-
- /**
- * Whether properties should be filtered by prefix. * Note that the prefix is removed from the key before the
- * property is bound.
- */
- public Builder withAllowPrivateSetter(boolean allowPrivateSetter) {
- this.allowPrivateSetter = allowPrivateSetter;
- return this;
- }
-
- /**
- * Whether to ignore case in the property names (keys).
- */
- public Builder withIgnoreCase(boolean ignoreCase) {
- this.ignoreCase = ignoreCase;
- return this;
- }
-
- /**
- * Whether properties should be filtered by prefix. Note that the prefix is removed from the key before the
- * property is bound.
- */
- public Builder withOptionPrefix(String optionPrefix) {
- this.optionPrefix = optionPrefix;
- return this;
- }
-
- /**
- * Whether to use the configurer to configure the properties.
- */
- public Builder withConfigurer(PropertyConfigurer configurer) {
- this.configurer = configurer;
- return this;
- }
-
- /**
- * Whether to allow using reflection (when there is no configurer available).
- */
- public Builder withReflection(boolean reflection) {
- this.reflection = reflection;
- return this;
- }
-
- /**
- * Binds the properties to the target object, and removes the property that was bound from properties.
- *
- * @return true if one or more properties was bound
- */
- public boolean bind() {
- // mandatory parameters
- org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext");
- org.apache.camel.util.ObjectHelper.notNull(target, "target");
-
- if (properties == null || properties.isEmpty()) {
- return false;
- }
-
- return doBindProperties(camelContext, target, removeParameters ? properties : new HashMap<>(properties),
- optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
- nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
- }
-
- /**
- * Binds the properties to the target object, and removes the property that was bound from properties.
- *
- * @param camelContext the camel context
- * @param target the target object
- * @param properties the properties where the bound properties will be removed from
- * @return true if one or more properties was bound
- */
- public boolean bind(CamelContext camelContext, Object target, Map<String, Object> properties) {
- CamelContext context = camelContext != null ? camelContext : this.camelContext;
- Object obj = target != null ? target : this.target;
- Map<String, Object> prop = properties != null ? properties : this.properties;
-
- return doBindProperties(context, obj, removeParameters ? prop : new HashMap<>(prop),
- optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
- nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
- }
-
- /**
- * Binds the property to the target object.
- *
- * @param camelContext the camel context
- * @param target the target object
- * @param key the property key
- * @param value the property value
- * @return true if the property was bound
- */
- public boolean bind(CamelContext camelContext, Object target, String key, Object value) {
- Map<String, Object> properties = new HashMap<>(1);
- properties.put(key, value);
-
- return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, true, false, mandatory,
- nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
- }
-
- }
-
private PropertyBindingSupport() {
}
@@ -322,21 +93,6 @@ public final class PropertyBindingSupport {
return new Builder();
}
- @FunctionalInterface
- public interface OnAutowiring {
-
- /**
- * Callback when a property was autowired on a bean
- *
- * @param target the targeted bean
- * @param propertyName the name of the property
- * @param propertyType the type of the property
- * @param value the property value
- */
- 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. This is used for
@@ -391,38 +147,18 @@ public final class PropertyBindingSupport {
Map<String, Object> properties = new LinkedHashMap<>();
// if there a configurer
- PropertyConfigurer configurer = null;
- PropertyConfigurerGetter getter;
- if (target instanceof Component) {
- // the component needs to be initialized to have the configurer ready
- ServiceHelper.initService(target);
- configurer = ((Component) target).getComponentPropertyConfigurer();
- }
- if (configurer == null) {
- String name = target.getClass().getName();
- if (target instanceof ExtendedCamelContext) {
- // special for camel context itself as we have an extended configurer
- name = ExtendedCamelContext.class.getName();
- }
-
- if (isNotEmpty(name)) {
- // see if there is a configurer for it
- configurer = camelContext.adapt(ExtendedCamelContext.class).getConfigurerResolver()
- .resolvePropertyConfigurer(name, camelContext);
- }
- }
+ PropertyConfigurer configurer = PropertyConfigurerHelper.resolvePropertyConfigurer(camelContext, target);
// use configurer to get all the current options and its values
Map<String, Object> getterAllOption = null;
if (configurer instanceof PropertyConfigurerGetter) {
- getter = (PropertyConfigurerGetter) configurer;
- final PropertyConfigurerGetter lambdaGetter = getter;
+ final PropertyConfigurerGetter getter = (PropertyConfigurerGetter) configurer;
final Object lambdaTarget = target;
getterAllOption = getter.getAllOptions(target);
getterAllOption.forEach((key, type) -> {
// we only need the complex types
if (isComplexUserType((Class) type)) {
- Object value = lambdaGetter.getOptionValue(lambdaTarget, key, true);
+ Object value = getter.getOptionValue(lambdaTarget, key, true);
properties.put(key, value);
}
});
@@ -570,139 +306,6 @@ public final class PropertyBindingSupport {
}
/**
- * Used for making it easier to support using option prefix in property binding and to remove the bound properties
- * from the input map.
- */
- private static class OptionPrefixMap extends LinkedHashMap<String, Object> {
-
- private final String optionPrefix;
- private final Map<String, Object> originalMap;
-
- public OptionPrefixMap(Map<String, Object> map, String optionPrefix) {
- this.originalMap = map;
- this.optionPrefix = optionPrefix;
- // copy from original map into our map without the option prefix
- map.forEach((k, v) -> {
- if (startsWithIgnoreCase(k, optionPrefix)) {
- put(k.substring(optionPrefix.length()), v);
- } else if (startsWithIgnoreCase(k, "?" + optionPrefix)) {
- put(k.substring(optionPrefix.length() + 1), v);
- }
- });
- }
-
- @Override
- public Object remove(Object key) {
- // we only need to care about the remove method,
- // so we can remove the corresponding key from the original map
- Set<String> toBeRemoved = new HashSet<>();
- originalMap.forEach((k, v) -> {
- if (startsWithIgnoreCase(k, optionPrefix)) {
- toBeRemoved.add(k);
- } else if (startsWithIgnoreCase(k, "?" + optionPrefix)) {
- toBeRemoved.add(k);
- }
- });
- toBeRemoved.forEach(originalMap::remove);
-
- return super.remove(key);
- }
-
- }
-
- /**
- * Used for flatten properties when they are a map of maps
- */
- private static class FlattenMap extends LinkedHashMap<String, Object> {
-
- private final Map<String, Object> originalMap;
-
- public FlattenMap(Map<String, Object> map) {
- this.originalMap = map;
- flatten("", originalMap);
- }
-
- @SuppressWarnings("unchecked")
- private void flatten(String prefix, Map<?, Object> map) {
- for (Map.Entry<?, Object> entry : map.entrySet()) {
- String key = entry.getKey().toString();
- boolean optional = key.startsWith("?");
- if (optional) {
- key = key.substring(1);
- }
- Object value = entry.getValue();
- String keyPrefix = (optional ? "?" : "") + (prefix.isEmpty() ? key : prefix + "." + key);
- if (value instanceof Map) {
- flatten(keyPrefix, (Map<?, Object>) value);
- } else {
- put(keyPrefix, value);
- }
- }
- }
-
- @Override
- public Object remove(Object key) {
- // we only need to care about the remove method,
- // so we can remove the corresponding key from the original map
-
- // walk key with dots to remove right node
- String[] parts = key.toString().split("\\.");
- Map map = originalMap;
- for (int i = 0; i < parts.length; i++) {
- String part = parts[i];
- Object obj = map.get(part);
- if (i == parts.length - 1) {
- map.remove(part);
- } else if (obj instanceof Map) {
- map = (Map) obj;
- }
- }
-
- // remove empty middle maps
- Object answer = super.remove(key);
- if (super.isEmpty()) {
- originalMap.clear();
- }
- return answer;
- }
-
- }
-
- /**
- * Used for sorting the property keys when doing property binding. We need to sort the keys in a specific order so
- * we process the binding in a way that allows us to walk down the OGNL object graph and build empty nodes on the
- * fly, and as well handle map/list and array types as well.
- */
- private static final class PropertyBindingKeyComparator implements Comparator<String> {
-
- private final Map<String, Object> map;
-
- private PropertyBindingKeyComparator(Map<String, Object> map) {
- this.map = map;
- }
-
- @Override
- public int compare(String o1, String o2) {
- // 1) sort by nested level (shortest OGNL graph first)
- int n1 = StringHelper.countChar(o1, '.');
- int n2 = StringHelper.countChar(o2, '.');
- if (n1 != n2) {
- return Integer.compare(n1, n2);
- }
- // 2) sort by reference (as it may refer to other beans in the OGNL graph)
- Object v1 = map.get(o1);
- Object v2 = map.get(o2);
- boolean ref1 = v1 instanceof String && ((String) v1).startsWith("#");
- boolean ref2 = v2 instanceof String && ((String) v2).startsWith("#");
- if (ref1 != ref2) {
- return Boolean.compare(ref1, ref2);
- }
- // 3) sort by name
- return o1.compareTo(o2);
- }
- }
-
- /**
* Binds the properties with the given prefix to the target object, and removes the property that was bound from
* properties. Note that the prefix is removed from the key before the property is bound.
*
@@ -798,8 +401,7 @@ public final class PropertyBindingSupport {
if (configurer == null) {
// do we have a configurer by any chance
- configurer = camelContext.adapt(ExtendedCamelContext.class).getConfigurerResolver()
- .resolvePropertyConfigurer(newClass.getName(), camelContext);
+ configurer = PropertyConfigurerHelper.resolvePropertyConfigurer(camelContext, newClass);
}
// we should only walk and create OGNL path for the middle graph
@@ -862,11 +464,9 @@ public final class PropertyBindingSupport {
Class<?> collectionType = (Class<?>) ((PropertyConfigurerGetter) configurer)
.getCollectionValueType(newTarget, undashKey(key), ignoreCase);
if (collectionType != null) {
- configurer = camelContext.adapt(ExtendedCamelContext.class).getConfigurerResolver()
- .resolvePropertyConfigurer(collectionType.getName(), camelContext);
+ configurer = PropertyConfigurerHelper.resolvePropertyConfigurer(camelContext, collectionType);
} else {
- configurer = camelContext.adapt(ExtendedCamelContext.class).getConfigurerResolver()
- .resolvePropertyConfigurer(prop.getClass().getName(), camelContext);
+ configurer = PropertyConfigurerHelper.resolvePropertyConfigurer(camelContext, prop.getClass());
}
}
// prepare for next iterator
@@ -2052,4 +1652,379 @@ public final class PropertyBindingSupport {
return key;
}
+ @FunctionalInterface
+ public interface OnAutowiring {
+
+ /**
+ * Callback when a property was autowired on a bean
+ *
+ * @param target the targeted bean
+ * @param propertyName the name of the property
+ * @param propertyType the type of the property
+ * @param value the property value
+ */
+ void onAutowire(Object target, String propertyName, Class propertyType, Object value);
+
+ }
+
+ /**
+ * To use a fluent builder style to configure this property binding support.
+ */
+ public static class Builder {
+
+ private CamelContext camelContext;
+ private Object target;
+ private Map<String, Object> properties;
+ private boolean removeParameters = true;
+ private boolean flattenProperties;
+ private boolean mandatory;
+ private boolean nesting = true;
+ private boolean deepNesting = true;
+ private boolean reference = true;
+ private boolean placeholder = true;
+ private boolean fluentBuilder = true;
+ private boolean allowPrivateSetter = true;
+ private boolean ignoreCase;
+ private String optionPrefix;
+ private boolean reflection = true;
+ private PropertyConfigurer configurer;
+
+ /**
+ * CamelContext to be used
+ */
+ public Builder withCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ return this;
+ }
+
+ /**
+ * Target object that should have parameters bound
+ */
+ public Builder withTarget(Object target) {
+ this.target = target;
+ return this;
+ }
+
+ /**
+ * The properties to use for binding
+ */
+ public Builder withProperties(Map<String, Object> properties) {
+ if (this.properties == null) {
+ this.properties = properties;
+ } else {
+ // there may be existing options so add those if missing
+ // we need to mutate existing as we are may be removing bound properties
+ this.properties.forEach(properties::putIfAbsent);
+ this.properties = properties;
+ }
+ return this;
+ }
+
+ /**
+ * Adds property to use for binding
+ */
+ public Builder withProperty(String key, Object value) {
+ if (this.properties == null) {
+ this.properties = new LinkedHashMap<>();
+ }
+ this.properties.put(key, value);
+ return this;
+ }
+
+ /**
+ * Whether parameters should be removed when its bound
+ */
+ public Builder withRemoveParameters(boolean removeParameters) {
+ this.removeParameters = removeParameters;
+ return this;
+ }
+
+ /**
+ * Whether properties should be flattened (when properties is a map of maps).
+ */
+ public Builder withFlattenProperties(boolean flattenProperties) {
+ this.flattenProperties = flattenProperties;
+ return this;
+ }
+
+ /**
+ * Whether all parameters should be mandatory and successfully bound
+ */
+ public Builder withMandatory(boolean mandatory) {
+ this.mandatory = mandatory;
+ return this;
+ }
+
+ /**
+ * Whether nesting is in use
+ */
+ public Builder withNesting(boolean nesting) {
+ this.nesting = nesting;
+ return this;
+ }
+
+ /**
+ * Whether deep nesting is in use, where Camel will attempt to walk as deep as possible by creating new objects
+ * in the OGNL graph if a property has a setter and the object can be created from a default no-arg constructor.
+ */
+ public Builder withDeepNesting(boolean deepNesting) {
+ this.deepNesting = deepNesting;
+ return this;
+ }
+
+ /**
+ * Whether reference parameter (syntax starts with #) is in use
+ */
+ public Builder withReference(boolean reference) {
+ this.reference = reference;
+ return this;
+ }
+
+ /**
+ * Whether to use Camels property placeholder to resolve placeholders on keys and values
+ */
+ public Builder withPlaceholder(boolean placeholder) {
+ this.placeholder = placeholder;
+ return this;
+ }
+
+ /**
+ * Whether fluent builder is allowed as a valid getter/setter
+ */
+ public Builder withFluentBuilder(boolean fluentBuilder) {
+ this.fluentBuilder = fluentBuilder;
+ return this;
+ }
+
+ /**
+ * Whether properties should be filtered by prefix. * Note that the prefix is removed from the key before the
+ * property is bound.
+ */
+ public Builder withAllowPrivateSetter(boolean allowPrivateSetter) {
+ this.allowPrivateSetter = allowPrivateSetter;
+ return this;
+ }
+
+ /**
+ * Whether to ignore case in the property names (keys).
+ */
+ public Builder withIgnoreCase(boolean ignoreCase) {
+ this.ignoreCase = ignoreCase;
+ return this;
+ }
+
+ /**
+ * Whether properties should be filtered by prefix. Note that the prefix is removed from the key before the
+ * property is bound.
+ */
+ public Builder withOptionPrefix(String optionPrefix) {
+ this.optionPrefix = optionPrefix;
+ return this;
+ }
+
+ /**
+ * Whether to use the configurer to configure the properties.
+ */
+ public Builder withConfigurer(PropertyConfigurer configurer) {
+ this.configurer = configurer;
+ return this;
+ }
+
+ /**
+ * Whether to allow using reflection (when there is no configurer available).
+ */
+ public Builder withReflection(boolean reflection) {
+ this.reflection = reflection;
+ return this;
+ }
+
+ /**
+ * Binds the properties to the target object, and removes the property that was bound from properties.
+ *
+ * @return true if one or more properties was bound
+ */
+ public boolean bind() {
+ // mandatory parameters
+ org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext");
+ org.apache.camel.util.ObjectHelper.notNull(target, "target");
+
+ if (properties == null || properties.isEmpty()) {
+ return false;
+ }
+
+ return doBindProperties(camelContext, target, removeParameters ? properties : new HashMap<>(properties),
+ optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
+ nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
+ }
+
+ /**
+ * Binds the properties to the target object, and removes the property that was bound from properties.
+ *
+ * @param camelContext the camel context
+ * @param target the target object
+ * @param properties the properties where the bound properties will be removed from
+ * @return true if one or more properties was bound
+ */
+ public boolean bind(CamelContext camelContext, Object target, Map<String, Object> properties) {
+ CamelContext context = camelContext != null ? camelContext : this.camelContext;
+ Object obj = target != null ? target : this.target;
+ Map<String, Object> prop = properties != null ? properties : this.properties;
+
+ return doBindProperties(context, obj, removeParameters ? prop : new HashMap<>(prop),
+ optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
+ nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
+ }
+
+ /**
+ * Binds the property to the target object.
+ *
+ * @param camelContext the camel context
+ * @param target the target object
+ * @param key the property key
+ * @param value the property value
+ * @return true if the property was bound
+ */
+ public boolean bind(CamelContext camelContext, Object target, String key, Object value) {
+ Map<String, Object> properties = new HashMap<>(1);
+ properties.put(key, value);
+
+ return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, true, false, mandatory,
+ nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
+ }
+
+ }
+
+ /**
+ * Used for making it easier to support using option prefix in property binding and to remove the bound properties
+ * from the input map.
+ */
+ private static class OptionPrefixMap extends LinkedHashMap<String, Object> {
+
+ private final String optionPrefix;
+ private final Map<String, Object> originalMap;
+
+ public OptionPrefixMap(Map<String, Object> map, String optionPrefix) {
+ this.originalMap = map;
+ this.optionPrefix = optionPrefix;
+ // copy from original map into our map without the option prefix
+ map.forEach((k, v) -> {
+ if (startsWithIgnoreCase(k, optionPrefix)) {
+ put(k.substring(optionPrefix.length()), v);
+ } else if (startsWithIgnoreCase(k, "?" + optionPrefix)) {
+ put(k.substring(optionPrefix.length() + 1), v);
+ }
+ });
+ }
+
+ @Override
+ public Object remove(Object key) {
+ // we only need to care about the remove method,
+ // so we can remove the corresponding key from the original map
+ Set<String> toBeRemoved = new HashSet<>();
+ originalMap.forEach((k, v) -> {
+ if (startsWithIgnoreCase(k, optionPrefix)) {
+ toBeRemoved.add(k);
+ } else if (startsWithIgnoreCase(k, "?" + optionPrefix)) {
+ toBeRemoved.add(k);
+ }
+ });
+ toBeRemoved.forEach(originalMap::remove);
+
+ return super.remove(key);
+ }
+
+ }
+
+ /**
+ * Used for flatten properties when they are a map of maps
+ */
+ private static class FlattenMap extends LinkedHashMap<String, Object> {
+
+ private final Map<String, Object> originalMap;
+
+ public FlattenMap(Map<String, Object> map) {
+ this.originalMap = map;
+ flatten("", originalMap);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void flatten(String prefix, Map<?, Object> map) {
+ for (Map.Entry<?, Object> entry : map.entrySet()) {
+ String key = entry.getKey().toString();
+ boolean optional = key.startsWith("?");
+ if (optional) {
+ key = key.substring(1);
+ }
+ Object value = entry.getValue();
+ String keyPrefix = (optional ? "?" : "") + (prefix.isEmpty() ? key : prefix + "." + key);
+ if (value instanceof Map) {
+ flatten(keyPrefix, (Map<?, Object>) value);
+ } else {
+ put(keyPrefix, value);
+ }
+ }
+ }
+
+ @Override
+ public Object remove(Object key) {
+ // we only need to care about the remove method,
+ // so we can remove the corresponding key from the original map
+
+ // walk key with dots to remove right node
+ String[] parts = key.toString().split("\\.");
+ Map map = originalMap;
+ for (int i = 0; i < parts.length; i++) {
+ String part = parts[i];
+ Object obj = map.get(part);
+ if (i == parts.length - 1) {
+ map.remove(part);
+ } else if (obj instanceof Map) {
+ map = (Map) obj;
+ }
+ }
+
+ // remove empty middle maps
+ Object answer = super.remove(key);
+ if (super.isEmpty()) {
+ originalMap.clear();
+ }
+ return answer;
+ }
+
+ }
+
+ /**
+ * Used for sorting the property keys when doing property binding. We need to sort the keys in a specific order so
+ * we process the binding in a way that allows us to walk down the OGNL object graph and build empty nodes on the
+ * fly, and as well handle map/list and array types as well.
+ */
+ private static final class PropertyBindingKeyComparator implements Comparator<String> {
+
+ private final Map<String, Object> map;
+
+ private PropertyBindingKeyComparator(Map<String, Object> map) {
+ this.map = map;
+ }
+
+ @Override
+ public int compare(String o1, String o2) {
+ // 1) sort by nested level (shortest OGNL graph first)
+ int n1 = StringHelper.countChar(o1, '.');
+ int n2 = StringHelper.countChar(o2, '.');
+ if (n1 != n2) {
+ return Integer.compare(n1, n2);
+ }
+ // 2) sort by reference (as it may refer to other beans in the OGNL graph)
+ Object v1 = map.get(o1);
+ Object v2 = map.get(o2);
+ boolean ref1 = v1 instanceof String && ((String) v1).startsWith("#");
+ boolean ref2 = v2 instanceof String && ((String) v2).startsWith("#");
+ if (ref1 != ref2) {
+ return Boolean.compare(ref1, ref2);
+ }
+ // 3) sort by name
+ return o1.compareTo(o2);
+ }
+ }
+
}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/PropertyConfigurerHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/PropertyConfigurerHelper.java
index 97b7ee2..b04397c 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/PropertyConfigurerHelper.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/PropertyConfigurerHelper.java
@@ -17,8 +17,10 @@
package org.apache.camel.support;
import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
import org.apache.camel.ExtendedCamelContext;
-import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.spi.PropertyConfigurer;
+import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.util.ObjectHelper;
/**
@@ -39,13 +41,55 @@ public final class PropertyConfigurerHelper {
* @param target the target object for which we need a {@link org.apache.camel.spi.PropertyConfigurer}
* @return the resolved configurer, or <tt>null</tt> if no configurer could be found
*/
- public static GeneratedPropertyConfigurer resolvePropertyConfigurer(CamelContext context, Object target) {
+ public static PropertyConfigurer resolvePropertyConfigurer(CamelContext context, Object target) {
ObjectHelper.notNull(target, "target");
ObjectHelper.notNull(context, "context");
+ PropertyConfigurer configurer = null;
+
+ if (target instanceof Component) {
+ // the component needs to be initialized to have the configurer ready
+ ServiceHelper.initService(target);
+ configurer = ((Component) target).getComponentPropertyConfigurer();
+ }
+
+ if (configurer == null) {
+ String name = target.getClass().getName();
+ if (target instanceof ExtendedCamelContext) {
+ // special for camel context itself as we have an extended configurer
+ name = ExtendedCamelContext.class.getName();
+ }
+
+ // see if there is a configurer for it
+ configurer = context.adapt(ExtendedCamelContext.class)
+ .getConfigurerResolver()
+ .resolvePropertyConfigurer(name, context);
+ }
+
+ return configurer;
+ }
+
+ /**
+ * Resolves the given configurer.
+ *
+ * @param context the camel context
+ * @param targetType the target object type for which we need a {@link org.apache.camel.spi.PropertyConfigurer}
+ * @return the resolved configurer, or <tt>null</tt> if no configurer could be found
+ */
+ public static PropertyConfigurer resolvePropertyConfigurer(CamelContext context, Class<?> targetType) {
+ ObjectHelper.notNull(targetType, "targetType");
+ ObjectHelper.notNull(context, "context");
+
+ String name = targetType.getName();
+ if (ExtendedCamelContext.class.isAssignableFrom(targetType)) {
+ // special for camel context itself as we have an extended configurer
+ name = ExtendedCamelContext.class.getName();
+ }
+
+ // see if there is a configurer for it
return context.adapt(ExtendedCamelContext.class)
.getConfigurerResolver()
- .resolvePropertyConfigurer(target.getClass().getName(), context);
+ .resolvePropertyConfigurer(name, context);
}
/**
@@ -60,7 +104,7 @@ public final class PropertyConfigurerHelper {
ObjectHelper.notNull(target, "target");
ObjectHelper.notNull(context, "context");
- GeneratedPropertyConfigurer configurer = resolvePropertyConfigurer(context, target);
+ PropertyConfigurer configurer = resolvePropertyConfigurer(context, target);
if (type.isInstance(configurer)) {
return type.cast(configurer);
}
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/CollectionHelper.java b/core/camel-util/src/main/java/org/apache/camel/util/CollectionHelper.java
index 39ccc3d..89dc186 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/CollectionHelper.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/CollectionHelper.java
@@ -164,6 +164,7 @@ public final class CollectionHelper {
/**
* Build a map from varargs.
*/
+ @SuppressWarnings("unchecked")
public static <K, V> Map<K, V> mapOf(Supplier<Map<K, V>> creator, K key, V value, Object... keyVals) {
Map<K, V> map = creator.get();
map.put(key, value);