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 <claus.ib...@gmail.com> 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; + +@org.apache.camel.spi.annotations.PropertiesFunction("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] +---- +@org.apache.camel.spi.annotations.PropertiesFunction("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(); + +}