This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch properties in repository https://gitbox.apache.org/repos/asf/camel.git
commit 605da5972c04395b691e00748696d4266e2d5336 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Jul 4 08:55:29 2019 +0200 CAMEL-13708: Properties component should have API to make it easier to lookup a property on-demand or from all pre-loaded properties. --- .../camel/blueprint/BlueprintPropertiesParser.java | 230 --------------------- .../blueprint/BlueprintPropertiesResolver.java | 72 ------- .../camel/blueprint/BlueprintPropertiesSource.java | 157 ++++++++++++++ .../camel/blueprint/CamelContextFactoryBean.java | 55 +++-- .../component/properties/PropertiesComponent.java | 3 - .../test/blueprint/BlueprintDefaultValuesTest.java | 1 - 6 files changed, 194 insertions(+), 324 deletions(-) diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesParser.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesParser.java deleted file mode 100644 index 14ba14d..0000000 --- a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesParser.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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.blueprint; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.aries.blueprint.ExtendedBeanMetadata; -import org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder; -import org.apache.aries.blueprint.ext.PropertyPlaceholder; -import org.apache.camel.component.properties.DefaultPropertiesParser; -import org.apache.camel.component.properties.PropertiesComponent; -import org.apache.camel.component.properties.PropertiesLookup; -import org.apache.camel.component.properties.PropertiesParser; -import org.apache.camel.support.ObjectHelper; -import org.osgi.service.blueprint.container.BlueprintContainer; -import org.osgi.service.blueprint.reflect.ComponentMetadata; - -/** - * Blueprint {@link PropertiesParser} which supports looking up - * property placeholders from the Blueprint Property Placeholder Service. - * <p/> - * This implementation will sit on top of any existing configured - * {@link PropertiesParser} and will delegate to those in case Blueprint could not - * resolve the property. - */ -public class BlueprintPropertiesParser extends DefaultPropertiesParser { - - private final PropertiesComponent propertiesComponent; - private final BlueprintContainer container; - private final PropertiesParser delegate; - private final Set<PropertyPlaceholderWrapper> placeholders = new LinkedHashSet<PropertyPlaceholderWrapper>(); - private Method method; - private Method oldMethod; - - public BlueprintPropertiesParser(PropertiesComponent propertiesComponent, BlueprintContainer container, PropertiesParser delegate) { - super(propertiesComponent); - this.propertiesComponent = propertiesComponent; - this.container = container; - this.delegate = delegate; - } - - /** - * Lookup the ids of the Blueprint property placeholder services in the - * Blueprint container. - * - * @return the ids, will be an empty array if none found. - */ - public String[] lookupPropertyPlaceholderIds() { - List<String> ids = new ArrayList<>(); - - for (Object componentId : container.getComponentIds()) { - String id = (String) componentId; - ComponentMetadata meta = container.getComponentMetadata(id); - if (meta instanceof ExtendedBeanMetadata) { - Class<?> clazz = ((ExtendedBeanMetadata) meta).getRuntimeClass(); - if (clazz != null && (AbstractPropertyPlaceholder.class.isAssignableFrom(clazz) - || newPlaceholderClass(clazz) != null)) { - ids.add(id); - } - } - } - - return ids.toArray(new String[ids.size()]); - } - - /** - * Obtains a {@link Class} instance for "org.apache.aries.blueprint.ext.AbstractPropertyPlaceholderExt" - */ - private Class<?> newPlaceholderClass(Class<?> clazz) { - Class<?> c = clazz; - while (c != null) { - if ("org.apache.aries.blueprint.ext.AbstractPropertyPlaceholderExt".equals(c.getName())) { - return c; - } - c = c.getSuperclass(); - } - return null; - } - - /** - * Adds the given Blueprint property placeholder service with the given id - * - * @param id id of the Blueprint property placeholder service to add. - */ - public void addPropertyPlaceholder(String id) { - Object component = container.getComponentInstance(id); - - // new API - if (component != null) { - Class<?> clazz = newPlaceholderClass(component.getClass()); - if (clazz != null) { - log.debug("Adding Blueprint PropertyPlaceholder: {}", id); - - if (method == null) { - try { - method = clazz.getDeclaredMethod("retrieveValue", String.class); - method.setAccessible(true); - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Cannot add blueprint property placeholder: " + id - + " as the method retrieveValue is not accessible", e); - } - } - placeholders.add(new PropertyPlaceholderWrapper(component, method)); - } - } - - // old, deprecated API - if (component instanceof AbstractPropertyPlaceholder) { - AbstractPropertyPlaceholder placeholder = (AbstractPropertyPlaceholder) component; - - log.debug("Adding Blueprint PropertyPlaceholder: {}", id); - - if (oldMethod == null) { - try { - oldMethod = AbstractPropertyPlaceholder.class.getDeclaredMethod("retrieveValue", String.class); - oldMethod.setAccessible(true); - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Cannot add blueprint property placeholder: " + id - + " as the method retrieveValue is not accessible", e); - } - } - placeholders.add(new PropertyPlaceholderWrapper(placeholder, oldMethod)); - } - } - - @Override - public String parseProperty(String key, String value, PropertiesLookup properties) { - log.trace("Parsing property key: {} with value: {}", key, value); - - String answer = null; - - // prefer any override properties - // this logic is special for BlueprintPropertiesParser as we otherwise prefer - // to use the AbstractPropertyPlaceholder from OSGi blueprint config admins - // service to lookup otherwise - if (key != null && propertiesComponent.getOverrideProperties() != null) { - answer = (String) propertiesComponent.getOverrideProperties().get(key); - } - - // lookup key in blueprint and return its value - if (answer == null && key != null) { - for (PropertyPlaceholderWrapper placeholder : placeholders) { - boolean isDefault = false; - if (placeholders.size() > 1) { - // okay we have multiple placeholders and we want to return the answer that - // is not the default placeholder if there is multiple keys - Map map = placeholder.getDefaultProperties(); - isDefault = map != null && map.containsKey(key); - log.trace("Blueprint property key: {} is part of default properties: {}", key, isDefault); - } - - try { - String candidate = placeholder.retrieveValue(key); - - if (candidate != null) { - if (answer == null || !isDefault) { - log.trace("Blueprint parsed candidate property key: {} as value: {}", key, answer); - answer = candidate; - } - } - } catch (Exception ex) { - // Here we just catch the exception and try to use other candidate - } - } - log.debug("Blueprint parsed property key: {} as value: {}", key, answer); - } - - // if there is a delegate then let it parse the current answer as it may be jasypt which - // need to decrypt values - if (delegate != null) { - String delegateAnswer = delegate.parseProperty(key, answer != null ? answer : value, properties); - if (delegateAnswer != null) { - answer = delegateAnswer; - log.debug("Delegate property parser parsed the property key: {} as value: {}", key, answer); - } - } - - log.trace("Returning parsed property key: {} as value: {}", key, answer); - return answer; - } - - private class PropertyPlaceholderWrapper { - - private Object delegate; - private Method method; - - public PropertyPlaceholderWrapper(Object delegate, Method method) { - this.delegate = delegate; - this.method = method; - } - - public String retrieveValue(String key) { - Object v = ObjectHelper.invokeMethod(method, delegate, key); - return v == null ? null : v.toString(); - } - - public Map getDefaultProperties() { - if (delegate instanceof PropertyPlaceholder) { - return ((PropertyPlaceholder) delegate).getDefaultProperties(); - } - try { - Method getDefaultProperties = delegate.getClass().getMethod("getDefaultProperties"); - return getDefaultProperties == null ? null : (Map) getDefaultProperties.invoke(delegate); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - return null; - } - } - } - -} diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesResolver.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesResolver.java deleted file mode 100644 index 1e982e9..0000000 --- a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesResolver.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.blueprint; - -import java.util.Collections; -import java.util.List; -import java.util.Properties; - -import org.apache.camel.CamelContext; -import org.apache.camel.component.properties.PropertiesLocation; -import org.apache.camel.component.properties.PropertiesResolver; - -/** - * A {@link PropertiesResolver} which supports the <tt>blueprint</tt> scheme. - * <p/> - * This implementation will sit on top of any existing configured - * {@link org.apache.camel.component.properties.PropertiesResolver} and will delegate - * to any non <tt>blueprint</tt> schemes. - */ -public class BlueprintPropertiesResolver implements PropertiesResolver { - - private final PropertiesResolver delegate; - private final BlueprintPropertiesParser blueprint; - - public BlueprintPropertiesResolver(PropertiesResolver delegate, BlueprintPropertiesParser blueprint) { - this.delegate = delegate; - this.blueprint = blueprint; - } - - @Override - public Properties resolveProperties(CamelContext context, boolean ignoreMissingLocation, List<PropertiesLocation> locations) { - Properties answer = new Properties(); - - boolean explicit = false; - - for (PropertiesLocation location : locations) { - if ("blueprint".equals(location.getResolver())) { - blueprint.addPropertyPlaceholder(location.getPath()); - // indicate an explicit blueprint id was configured - explicit = true; - } else { - // delegate the url - answer.putAll(delegate.resolveProperties(context, ignoreMissingLocation, Collections.singletonList(location))); - } - } - - if (!explicit) { - // auto lookup blueprint property placeholders to use if none explicit was configured - // this is convention over configuration - for (String id : blueprint.lookupPropertyPlaceholderIds()) { - blueprint.addPropertyPlaceholder(id); - } - } - - return answer; - } - -} diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesSource.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesSource.java new file mode 100644 index 0000000..12b0fcd --- /dev/null +++ b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/BlueprintPropertiesSource.java @@ -0,0 +1,157 @@ +/* + * 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.blueprint; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.aries.blueprint.ext.PropertyPlaceholderExt; +import org.apache.camel.component.properties.PropertiesSource; +import org.apache.camel.support.ObjectHelper; +import org.apache.camel.support.service.ServiceSupport; +import org.apache.camel.util.ReflectionHelper; +import org.osgi.service.blueprint.container.BlueprintContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Blueprint {@link PropertiesSource} which supports looking up + * property placeholders from the Blueprint Property Placeholder Service. + */ +public class BlueprintPropertiesSource extends ServiceSupport implements PropertiesSource { + + private static final Logger LOG = LoggerFactory.getLogger(BlueprintPropertiesSource.class); + private final BlueprintContainer container; + private final List<String> ids; + private final Set<PropertyPlaceholderWrapper> placeholders = new LinkedHashSet<>(); + + public BlueprintPropertiesSource(BlueprintContainer container, List<String> ids) { + this.container = container; + this.ids = ids; + } + + @Override + public String getName() { + return "BlueprintPropertiesSource" + ids; + } + + @Override + public String getProperty(String name) { + String answer = null; + + for (PropertyPlaceholderWrapper placeholder : placeholders) { + boolean isDefault = false; + if (placeholders.size() > 1) { + // okay we have multiple placeholders and we want to return the answer that + // is not the default placeholder if there is multiple keys + Map map = placeholder.getDefaultProperties(); + isDefault = map != null && map.containsKey(name); + LOG.trace("Blueprint property key: {} is part of default properties: {}", name, isDefault); + } + + try { + String candidate = placeholder.retrieveValue(name); + if (candidate != null) { + if (answer == null || !isDefault) { + LOG.trace("Blueprint candidate property key: {} as value: {}", name, answer); + answer = candidate; + } + } + } catch (Exception ex) { + // Here we just catch the exception and try to use other candidate + } + } + LOG.debug("Blueprint getProperty: {}={}", name, answer); + + return answer; + } + + /** + * Adds the given Blueprint property placeholder service with the given id + * + * @param id id of the Blueprint property placeholder service to add. + */ + private void addPropertyPlaceholder(String id) { + Object component = container.getComponentInstance(id); + + if (component instanceof PropertyPlaceholderExt) { + Class<?> clazz = component.getClass(); + if (clazz != null) { + LOG.debug("Adding Blueprint PropertyPlaceholder: {}", id); + Method method = ReflectionHelper.findMethod(clazz, "retrieveValue", String.class); + Method defaultMethod = ReflectionHelper.findMethod(clazz, "getDefaultProperties"); + if (method != null) { + method.setAccessible(true); + if (defaultMethod != null) { + defaultMethod.setAccessible(true); + } + placeholders.add(new PropertyPlaceholderWrapper(component, method, defaultMethod)); + } else { + throw new IllegalStateException("Cannot add blueprint property placeholder: " + id + + " as the method retrieveValue is not found"); + } + } + } + } + + @Override + protected void doInit() throws Exception { + for (String id : ids) { + addPropertyPlaceholder(id); + } + } + + @Override + protected void doStart() throws Exception { + // noop + } + + @Override + protected void doStop() throws Exception { + // noop + } + + private class PropertyPlaceholderWrapper { + + private final Object delegate; + private final Method method; + private final Method defaultMethod; + + PropertyPlaceholderWrapper(Object delegate, Method method, Method defaultMethod) { + this.delegate = delegate; + this.method = method; + this.defaultMethod = defaultMethod; + } + + String retrieveValue(String key) { + Object v = ObjectHelper.invokeMethod(method, delegate, key); + return v == null ? null : v.toString(); + } + + Map getDefaultProperties() { + if (defaultMethod != null) { + return (Map) ObjectHelper.invokeMethod(defaultMethod, delegate); + } + return null; + } + } + +} diff --git a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java index 34dd156..7007bbf 100644 --- a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java +++ b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java @@ -20,6 +20,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Properties; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; @@ -28,6 +31,8 @@ import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; +import org.apache.aries.blueprint.ExtendedBeanMetadata; +import org.apache.aries.blueprint.ext.PropertyPlaceholderExt; import org.apache.camel.LoggingLevel; import org.apache.camel.RoutesBuilder; import org.apache.camel.ShutdownRoute; @@ -35,6 +40,7 @@ import org.apache.camel.ShutdownRunningTask; import org.apache.camel.TypeConverterExists; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.component.properties.PropertiesLocation; import org.apache.camel.core.osgi.OsgiCamelContextPublisher; import org.apache.camel.core.osgi.OsgiEventAdminNotifier; import org.apache.camel.core.osgi.utils.BundleDelegatingClassLoader; @@ -69,6 +75,7 @@ import org.apache.camel.spi.Registry; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.blueprint.container.BlueprintContainer; +import org.osgi.service.blueprint.reflect.ComponentMetadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -264,9 +271,6 @@ public class CamelContextFactoryBean extends AbstractCamelContextFactoryBean<Blu // lookup existing configured properties component PropertiesComponent pc = getContext().getComponent("properties", PropertiesComponent.class); - BlueprintPropertiesParser parser = new BlueprintPropertiesParser(pc, blueprintContainer, pc.getPropertiesParser()); - BlueprintPropertiesResolver resolver = new BlueprintPropertiesResolver(pc.getPropertiesResolver(), parser); - // any extra properties ServiceReference<?> ref = bundleContext.getServiceReference(PropertiesComponent.OVERRIDE_PROPERTIES); if (ref != null) { @@ -276,26 +280,41 @@ public class CamelContextFactoryBean extends AbstractCamelContextFactoryBean<Blu } } - // no locations has been set, so its a default component - if (pc.getLocations() == null) { - String[] ids = parser.lookupPropertyPlaceholderIds(); - for (int i = 0; i < ids.length; i++) { - if (!ids[i].startsWith("blueprint:")) { - ids[i] = "blueprint:" + ids[i]; - } - } - if (ids.length > 0) { - // location supports multiple separated by comma - pc.setLocations(ids); + List<String> ids = new ArrayList<>(); + for (PropertiesLocation bp : pc.getLocations()) { + if ("blueprint".equals(bp.getResolver())) { + ids.add(bp.getPath()); } } + if (ids.isEmpty()) { + // no blueprint locations has been set, so auto-detect the blueprint property placeholders to use (convention over configuration) + ids = lookupPropertyPlaceholderIds(); + } + pc.addPropertiesSource(new BlueprintPropertiesSource(blueprintContainer, ids)); + } + } - if (pc.getLocations() != null) { - // bridge camel properties with blueprint - pc.setPropertiesParser(parser); - pc.setPropertiesResolver(resolver); + /** + * Lookup the ids of the Blueprint property placeholder services in the + * Blueprint container. + * + * @return the ids, will be an empty if none found. + */ + private List<String> lookupPropertyPlaceholderIds() { + List<String> ids = new ArrayList<>(); + + for (Object componentId : blueprintContainer.getComponentIds()) { + String id = (String) componentId; + ComponentMetadata meta = blueprintContainer.getComponentMetadata(id); + if (meta instanceof ExtendedBeanMetadata) { + Class<?> clazz = ((ExtendedBeanMetadata) meta).getRuntimeClass(); + if (clazz != null && PropertyPlaceholderExt.class.isAssignableFrom(clazz)) { + ids.add(id); + } } } + + return ids; } @Override diff --git a/components/camel-properties/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java b/components/camel-properties/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java index af122fe..f706125 100644 --- a/components/camel-properties/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java +++ b/components/camel-properties/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java @@ -617,9 +617,6 @@ public class PropertiesComponent extends DefaultComponent implements org.apache. addPropertiesSource(new FilePropertiesSource(this, location)); } else if ("classpath".equals(location.getResolver())) { addPropertiesSource(new ClasspathPropertiesSource(this, location)); - } else { - // classpath is also default - addPropertiesSource(new ClasspathPropertiesSource(this, location)); } } private List<PropertiesLocation> parseLocations(List<PropertiesLocation> locations) { diff --git a/components/camel-test-blueprint/src/test/java/org/apache/camel/test/blueprint/BlueprintDefaultValuesTest.java b/components/camel-test-blueprint/src/test/java/org/apache/camel/test/blueprint/BlueprintDefaultValuesTest.java index f139918..87deaab 100644 --- a/components/camel-test-blueprint/src/test/java/org/apache/camel/test/blueprint/BlueprintDefaultValuesTest.java +++ b/components/camel-test-blueprint/src/test/java/org/apache/camel/test/blueprint/BlueprintDefaultValuesTest.java @@ -24,7 +24,6 @@ import org.junit.Test; * A test showing that Blueprint XML property placeholders work correctly with * placeholders containing colon in the key. */ - public class BlueprintDefaultValuesTest extends CamelBlueprintTestSupport { @Override