This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 1689d84 CAMEL-17647 (#6945)
1689d84 is described below
commit 1689d8484e21cad2415a5a0a85cb0a88bff687a6
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Feb 15 11:48:56 2022 +0100
CAMEL-17647 (#6945)
CAMEL-17647: camel-core - Properties component should support pluggable
functions
---
.../org/apache/camel/properties-function/base64 | 2 +
.../base64/Base64PropertiesFunction.java | 43 +++++++
.../base64/Base64PropertiesFunctionTest.java | 47 ++++++++
.../camel/spi/annotations/PropertiesFunction.java | 33 ++++++
.../properties/DefaultPropertiesParser.java | 33 +++---
.../component/properties/PropertiesComponent.java | 24 +++-
.../properties/PropertiesFunctionResolver.java | 128 +++++++++++++++++++++
.../ROOT/pages/using-propertyplaceholder.adoc | 28 +++++
.../camel/maven/packaging/SpiGeneratorMojo.java | 9 +-
.../camel/spi/annotations/PropertiesFunction.java | 33 ++++++
10 files changed, 350 insertions(+), 30 deletions(-)
diff --git
a/components/camel-base64/src/generated/resources/META-INF/services/org/apache/camel/properties-function/base64
b/components/camel-base64/src/generated/resources/META-INF/services/org/apache/camel/properties-function/base64
new file mode 100644
index 0000000..bb73781
--- /dev/null
+++
b/components/camel-base64/src/generated/resources/META-INF/services/org/apache/camel/properties-function/base64
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.dataformat.base64.Base64PropertiesFunction
diff --git
a/components/camel-base64/src/main/java/org/apache/camel/dataformat/base64/Base64PropertiesFunction.java
b/components/camel-base64/src/main/java/org/apache/camel/dataformat/base64/Base64PropertiesFunction.java
new file mode 100644
index 0000000..4eedd37
--- /dev/null
+++
b/components/camel-base64/src/main/java/org/apache/camel/dataformat/base64/Base64PropertiesFunction.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.dataformat.base64;
+
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.commons.codec.binary.Base64;
+
[email protected]("base64")
+public class Base64PropertiesFunction implements PropertiesFunction {
+
+ private final int lineLength = Base64.MIME_CHUNK_SIZE;
+ private final byte[] lineSeparator = { '\r', '\n' };
+ private final Base64 codec;
+
+ public Base64PropertiesFunction() {
+ this.codec = new Base64(lineLength, lineSeparator, true);
+ }
+
+ @Override
+ public String getName() {
+ return "base64";
+ }
+
+ @Override
+ public String apply(String remainder) {
+ byte[] arr = codec.decode(remainder);
+ return new String(arr);
+ }
+}
diff --git
a/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
b/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
new file mode 100644
index 0000000..462c672
--- /dev/null
+++
b/components/camel-base64/src/test/java/org/apache/camel/dataformat/base64/Base64PropertiesFunctionTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.dataformat.base64;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class Base64PropertiesFunctionTest extends CamelTestSupport {
+
+ @Test
+ public void testBase64() throws Exception {
+ getMockEndpoint("mock:result").expectedBodiesReceived("Hello Camel");
+
+ template.sendBody("direct:start", "Hello");
+
+ assertMockEndpointsSatisfied();
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start")
+ // Q2FtZWw== is the word Camel
+ .setBody(simple("${body} {{base64:Q2FtZWw==}}"))
+ .to("mock:result");
+ }
+ };
+ }
+}
diff --git
a/core/camel-api/src/generated/java/org/apache/camel/spi/annotations/PropertiesFunction.java
b/core/camel-api/src/generated/java/org/apache/camel/spi/annotations/PropertiesFunction.java
new file mode 100644
index 0000000..7e9e966
--- /dev/null
+++
b/core/camel-api/src/generated/java/org/apache/camel/spi/annotations/PropertiesFunction.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.spi.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target({ ElementType.TYPE })
+@ServiceFactory("properties-function")
+public @interface PropertiesFunction {
+
+ String value();
+
+}
diff --git
a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
index 73870e1..2ad3775 100644
---
a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
+++
b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
@@ -237,24 +237,23 @@ public class DefaultPropertiesParser implements
PropertiesParser {
// the key may be a function, so lets check this first
if (propertiesComponent != null) {
- for (PropertiesFunction function :
propertiesComponent.getFunctions().values()) {
- String token = function.getName() + ":";
- if (key.startsWith(token)) {
- String remainder = key.substring(token.length());
- log.debug("Property with key [{}] is applied by
function [{}]", key, function.getName());
- String value = function.apply(remainder);
- if (value == null) {
- throw new IllegalArgumentException(
- "Property with key [" + key + "] using
function [" + function.getName() + "]"
- + " returned
null value which is not allowed, from input: "
- + input);
- } else {
- if (log.isDebugEnabled()) {
- log.debug("Property with key [{}] applied by
function [{}] -> {}", key, function.getName(),
- value);
- }
- return value;
+ String prefix = StringHelper.before(key, ":");
+ PropertiesFunction function =
propertiesComponent.getPropertiesFunction(prefix);
+ if (function != null) {
+ String remainder = StringHelper.after(key, ":");
+ log.debug("Property with key [{}] is applied by function
[{}]", key, function.getName());
+ String value = function.apply(remainder);
+ if (value == null) {
+ throw new IllegalArgumentException(
+ "Property with key [" + key + "] using
function [" + function.getName() + "]"
+ + " returned null
value which is not allowed, from input: "
+ + input);
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("Property with key [{}] applied by
function [{}] -> {}", key, function.getName(),
+ value);
}
+ return value;
}
}
}
diff --git
a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
index 79b31af..bc11a82 100644
---
a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
+++
b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
@@ -19,7 +19,6 @@ package org.apache.camel.component.properties;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -103,7 +102,7 @@ public class PropertiesComponent extends ServiceSupport
private static final String NEGATE_PREFIX = PREFIX_TOKEN + "!";
private CamelContext camelContext;
- private final Map<String, PropertiesFunction> functions = new
LinkedHashMap<>();
+ private final PropertiesFunctionResolver functionResolver = new
PropertiesFunctionResolver();
private PropertiesParser propertiesParser = new
DefaultPropertiesParser(this);
private final PropertiesLookup propertiesLookup = new
DefaultPropertiesLookup(this);
private final List<PropertiesSource> sources = new ArrayList<>();
@@ -508,22 +507,36 @@ public class PropertiesComponent extends ServiceSupport
/**
* Gets the functions registered in this properties component.
*/
+ @Deprecated
public Map<String, PropertiesFunction> getFunctions() {
- return functions;
+ return functionResolver.getFunctions();
+ }
+
+ /**
+ * Gets the function by the given name
+ *
+ * @param name the function name
+ * @return the function or null if no function exists
+ */
+ public PropertiesFunction getPropertiesFunction(String name) {
+ if (name == null) {
+ return null;
+ }
+ return functionResolver.resolvePropertiesFunction(name);
}
/**
* Registers the {@link PropertiesFunction} as a function to this
component.
*/
public void addPropertiesFunction(PropertiesFunction function) {
- this.functions.put(function.getName(), function);
+ functionResolver.addPropertiesFunction(function);
}
/**
* Is there a {@link PropertiesFunction} with the given name?
*/
public boolean hasFunction(String name) {
- return functions.containsKey(name);
+ return functionResolver.hasFunction(name);
}
@ManagedAttribute(description = "System properties mode")
@@ -627,6 +640,7 @@ public class PropertiesComponent extends ServiceSupport
super.doInit();
ObjectHelper.notNull(camelContext, "CamelContext", this);
+ CamelContextAware.trySetCamelContext(functionResolver, camelContext);
if (systemPropertiesMode != SYSTEM_PROPERTIES_MODE_NEVER
&& systemPropertiesMode != SYSTEM_PROPERTIES_MODE_FALLBACK
diff --git
a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesFunctionResolver.java
b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesFunctionResolver.java
new file mode 100644
index 0000000..ec1d733
--- /dev/null
+++
b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesFunctionResolver.java
@@ -0,0 +1,128 @@
+/*
+ * 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.component.properties;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.ExtendedCamelContext;
+import org.apache.camel.spi.FactoryFinder;
+import org.apache.camel.spi.PropertiesFunction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Resolver for built-in and custom {@link PropertiesFunction}.
+ */
+public final class PropertiesFunctionResolver implements CamelContextAware {
+
+ public static final String RESOURCE_PATH =
"META-INF/services/org/apache/camel/properties-function/";
+
+ private static final Logger LOG =
LoggerFactory.getLogger(PropertiesFunctionResolver.class);
+
+ private CamelContext camelContext;
+ private FactoryFinder factoryFinder;
+ private final Map<String, PropertiesFunction> functions = new
LinkedHashMap<>();
+
+ public PropertiesFunctionResolver() {
+ // include out of the box functions
+ addPropertiesFunction(new EnvPropertiesFunction());
+ addPropertiesFunction(new SysPropertiesFunction());
+ addPropertiesFunction(new ServicePropertiesFunction());
+ addPropertiesFunction(new ServiceHostPropertiesFunction());
+ addPropertiesFunction(new ServicePortPropertiesFunction());
+ // TODO: Move AWSSecretsManagerPropertiesFunction to
camel-aws-secrets-manager
+ addPropertiesFunction(new AWSSecretsManagerPropertiesFunction());
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ /**
+ * Registers the {@link PropertiesFunction} as a function to this
component.
+ */
+ public void addPropertiesFunction(PropertiesFunction function) {
+ this.functions.put(function.getName(), function);
+ }
+
+ /**
+ * Gets the functions registered in this properties component.
+ */
+ public Map<String, PropertiesFunction> getFunctions() {
+ return functions;
+ }
+
+ /**
+ * Is there a {@link PropertiesFunction} with the given name?
+ */
+ public boolean hasFunction(String name) {
+ return functions.containsKey(name);
+ }
+
+ public PropertiesFunction resolvePropertiesFunction(String name) {
+ PropertiesFunction answer = functions.get(name);
+ if (answer == null) {
+ answer = resolve(camelContext, name);
+ if (answer != null) {
+ functions.put(name, answer);
+ }
+ }
+ return answer;
+ }
+
+ private PropertiesFunction resolve(CamelContext context, String name) {
+ // use factory finder to find a custom implementations
+ Class<?> type = null;
+ try {
+ type = findFactory(name, context);
+ } catch (Exception e) {
+ // ignore
+ }
+
+ if (type != null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Found PropertiesFunction: {} via: {}{}",
type.getName(), factoryFinder.getResourcePath(), name);
+ }
+ if (PropertiesFunction.class.isAssignableFrom(type)) {
+ PropertiesFunction answer = (PropertiesFunction)
context.getInjector().newInstance(type, false);
+ CamelContextAware.trySetCamelContext(answer, camelContext);
+ return answer;
+ } else {
+ throw new IllegalArgumentException("Type is not a
PropertiesFunction implementation. Found: " + type.getName());
+ }
+ }
+
+ return null;
+ }
+
+ private Class<?> findFactory(String name, CamelContext context) {
+ if (factoryFinder == null) {
+ factoryFinder =
context.adapt(ExtendedCamelContext.class).getFactoryFinder(RESOURCE_PATH);
+ }
+ return factoryFinder.findClass(name).orElse(null);
+ }
+
+}
diff --git a/docs/user-manual/modules/ROOT/pages/using-propertyplaceholder.adoc
b/docs/user-manual/modules/ROOT/pages/using-propertyplaceholder.adoc
index 3f79a92..2ffee01 100644
--- a/docs/user-manual/modules/ROOT/pages/using-propertyplaceholder.adoc
+++ b/docs/user-manual/modules/ROOT/pages/using-propertyplaceholder.adoc
@@ -558,6 +558,34 @@ PropertiesComponent pc = context.getPropertiesComponent();
pc.addFunction(new MyBeerFunction());
----
+==== Pluggable custom properties functions
+
+If you want custom properties functions to be easier to use then we recommend
making them pluggable.
+For example the beer function from above, can be made pluggable simply by
adding the `PropertiesFunction` annotations as shown:
+
+[source,java]
+----
[email protected]("beer")
+public class MyBeerFunction implements PropertiesFunction {
+
+ @Override
+ public String getName() {
+ return "beer";
+ }
+
+ @Override
+ public String apply(String remainder) {
+ return "mock:" + remainder.toLowerCase();
+ }
+}
+----
+
+Then by having the `camel-component-maven-plugin` as part of building the
component will
+then ensure that this custom properties has necessary source code generated
that makes Camel
+able to automatically discover the custom function.
+
+TIP: For an example see the `camel-base64` component.
+
== Using third party property sources
The properties component allows to plugin 3rd party sources to load and lookup
properties via the `PropertySource`
diff --git
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/SpiGeneratorMojo.java
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/SpiGeneratorMojo.java
index 3e23588..80f7f19 100644
---
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/SpiGeneratorMojo.java
+++
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/SpiGeneratorMojo.java
@@ -112,14 +112,7 @@ public class SpiGeneratorMojo extends
AbstractGeneratorMojo {
}
//
- // @ServiceFactory
- // @SubServiceFactory
- //
- // @CloudServiceFactory
- // @Component
- // @Dataformat
- // @Language
- // @SendDynamic
+ // @ServiceFactory and children
//
for (AnnotationInstance sfa : index.getAnnotations(SERVICE_FACTORY)) {
if (sfa.target().kind() != Kind.CLASS ||
sfa.target().asClass().nestingType() != NestingType.TOP_LEVEL) {
diff --git
a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/annotations/PropertiesFunction.java
b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/annotations/PropertiesFunction.java
new file mode 100644
index 0000000..7e9e966
--- /dev/null
+++
b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/annotations/PropertiesFunction.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.spi.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target({ ElementType.TYPE })
+@ServiceFactory("properties-function")
+public @interface PropertiesFunction {
+
+ String value();
+
+}