| /* |
| * 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.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.camel.CamelContext; |
| import org.apache.camel.ExtendedCamelContext; |
| import org.apache.camel.PropertyBindingException; |
| import org.apache.camel.spi.GeneratedPropertyConfigurer; |
| import org.apache.camel.spi.PropertyConfigurer; |
| import org.apache.camel.util.StringHelper; |
| import org.apache.camel.util.StringQuoteHelper; |
| |
| import static org.apache.camel.util.ObjectHelper.isNotEmpty; |
| |
| /** |
| * A convenient support class for binding String valued properties to an instance which |
| * uses a set of conventions: |
| * <ul> |
| * <li>property placeholders - Keys and values using Camels property placeholder will be resolved</li> |
| * <li>nested - Properties can be nested using the dot syntax (OGNL and builder pattern using with as prefix), eg foo.bar=123</li> |
| * <li>map</li> - Properties can lookup in Map's using map syntax, eg foo[bar] where foo is the name of the property that is a Map instance, and bar is the name of the key.</li> |
| * <li>list</li> - Properties can refer or add to in List's using list syntax, eg foo[0] where foo is the name of the property that is a |
| * List instance, and 0 is the index. To refer to the last element, then use last as key.</li> |
| * <li>reference by bean id - Values can refer to other beans in the registry by prefixing with with # or #bean: eg #myBean or #bean:myBean</li> |
| * <li>reference by type - Values can refer to singleton beans by their type in the registry by prefixing with #type: syntax, eg #type:com.foo.MyClassType</li> |
| * <li>autowire by type - Values can refer to singleton beans by auto wiring by setting the value to #autowired</li> |
| * <li>reference new class - Values can refer to creating new beans by their class name by prefixing with #class, eg #class:com.foo.MyClassType. |
| * The class is created using a default no-arg constructor, however if you need to create the instance via a factory method |
| * then you specify the method as shown: #class:com.foo.MyClassType#myFactoryMethod. |
| * Or if you need to create the instance via constructor parameters then you can specify the parameters as shown: |
| * #class:com.foo.MyClass('Hello World', 5, true)</li>. |
| * <li>ignore case - Whether to ignore case for property keys<li> |
| * </ul> |
| */ |
| 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 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 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 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; |
| } |
| |
| /** |
| * 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"); |
| org.apache.camel.util.ObjectHelper.notNull(properties, "properties"); |
| |
| return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, removeParameters, mandatory, |
| nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, 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; |
| |
| // mandatory parameters |
| org.apache.camel.util.ObjectHelper.notNull(context, "camelContext"); |
| org.apache.camel.util.ObjectHelper.notNull(obj, "target"); |
| org.apache.camel.util.ObjectHelper.notNull(prop, "properties"); |
| |
| return doBindProperties(context, obj, prop, optionPrefix, ignoreCase, removeParameters, mandatory, |
| nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, 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) { |
| org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext"); |
| org.apache.camel.util.ObjectHelper.notNull(target, "target"); |
| org.apache.camel.util.ObjectHelper.notNull(key, "key"); |
| org.apache.camel.util.ObjectHelper.notNull(value, "value"); |
| |
| Map<String, Object> properties = new HashMap<>(1); |
| properties.put(key, value); |
| |
| return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, removeParameters, mandatory, |
| nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, configurer); |
| } |
| |
| } |
| |
| private PropertyBindingSupport() { |
| } |
| |
| public static Builder build() { |
| 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 convention over configuration to automatic configure resources such as DataSource, Amazon Logins and |
| * so on. |
| * |
| * @param camelContext the camel context |
| * @param target the target object |
| * @return true if one ore more properties was auto wired |
| */ |
| public static boolean autowireSingletonPropertiesFromRegistry(CamelContext camelContext, Object target) { |
| return autowireSingletonPropertiesFromRegistry(camelContext, target, false, false, null); |
| } |
| |
| /** |
| * This will discover all the properties on the target, and automatic bind the properties 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 convention over configuration to automatic configure resources such as DataSource, Amazon Logins and |
| * so on. |
| * |
| * @param camelContext the camel context |
| * @param target the target object |
| * @param bindNullOnly whether to only autowire if the property has no default value or has not been configured explicit |
| * @param deepNesting whether to attempt to walk as deep down the object graph by creating new empty objects on the way if needed (Camel can only create |
| * new empty objects if they have a default no-arg constructor, also mind that this may lead to creating many empty objects, even |
| * if they will not have any objects autowired from the registry, so use this with caution) |
| * @param callback optional callback when a property was auto wired |
| * @return true if one ore more properties was auto wired |
| */ |
| public static boolean autowireSingletonPropertiesFromRegistry(CamelContext camelContext, Object target, |
| boolean bindNullOnly, boolean deepNesting, OnAutowiring callback) { |
| try { |
| if (target != null) { |
| Set<Object> parents = new HashSet<>(); |
| return doAutowireSingletonPropertiesFromRegistry(camelContext, target, parents, bindNullOnly, deepNesting, callback); |
| } |
| } catch (Exception e) { |
| throw new PropertyBindingException(target, e); |
| } |
| |
| return false; |
| } |
| |
| private static boolean doAutowireSingletonPropertiesFromRegistry(CamelContext camelContext, Object target, Set<Object> parents, |
| boolean bindNullOnly, boolean deepNesting, OnAutowiring callback) throws Exception { |
| |
| Map<String, Object> properties = new LinkedHashMap<>(); |
| camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().getProperties(target, properties, null); |
| |
| boolean hit = false; |
| |
| for (Map.Entry<String, Object> entry : properties.entrySet()) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| |
| // skip based on some known names |
| if ("basicPropertyBinding".equals(key)) { |
| continue; |
| } |
| |
| boolean skip = parents.contains(value) || value instanceof CamelContext; |
| if (skip) { |
| // we have already covered this as parent of parents so dont walk down this as we want to avoid |
| // circular dependencies when walking the OGNL graph, also we dont want to walk down CamelContext |
| continue; |
| } |
| |
| Class<?> type = getGetterType(camelContext, target, key, false); |
| if (type != null && CamelContext.class.isAssignableFrom(type)) { |
| // the camel context is usually bound by other means so don't bind it to the target object |
| // and most important do not walk it down and re-configure it. |
| // |
| // In some cases, such as Camel Quarkus, the Registry and the Context itself are added to |
| // the IoC Container and an attempt to auto re-wire the Context may ends up in a circular |
| // reference and a subsequent stack overflow. |
| continue; |
| } |
| |
| if (isComplexUserType(type)) { |
| // if the property has not been set and its a complex type (not simple or string etc) |
| if (!bindNullOnly || value == null) { |
| Set lookup = camelContext.getRegistry().findByType(type); |
| if (lookup.size() == 1) { |
| value = lookup.iterator().next(); |
| if (value != null) { |
| hit |= camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(camelContext, target, key, value); |
| if (hit && callback != null) { |
| callback.onAutowire(target, key, type, value); |
| } |
| } |
| } |
| } |
| |
| // attempt to create new instances to walk down the tree if its null (deepNesting option) |
| if (value == null && deepNesting) { |
| // okay is there a setter so we can create a new instance and set it automatic |
| Method method = findBestSetterMethod(camelContext, target.getClass(), key, true, true, false); |
| if (method != null) { |
| Class<?> parameterType = method.getParameterTypes()[0]; |
| if (parameterType != null && org.apache.camel.util.ObjectHelper.hasDefaultPublicNoArgConstructor(parameterType)) { |
| Object instance = camelContext.getInjector().newInstance(parameterType); |
| if (instance != null) { |
| org.apache.camel.support.ObjectHelper.invokeMethod(method, target, instance); |
| target = instance; |
| // remember this as parent and also autowire nested properties |
| // do not walk down if it point to our-selves (circular reference) |
| parents.add(target); |
| value = instance; |
| hit |= doAutowireSingletonPropertiesFromRegistry(camelContext, value, parents, bindNullOnly, deepNesting, callback); |
| } |
| } |
| } |
| } else if (value != null) { |
| // remember this as parent and also autowire nested properties |
| // do not walk down if it point to our-selves (circular reference) |
| parents.add(target); |
| hit |= doAutowireSingletonPropertiesFromRegistry(camelContext, value, parents, bindNullOnly, deepNesting, callback); |
| } |
| } |
| } |
| |
| return hit; |
| } |
| |
| /** |
| * Binds the properties to the target object, and removes the property that was bound from properties. |
| * <p/> |
| * This method uses the default settings, and if you need to configure any setting then use |
| * the fluent builder {@link #build()} where each option can be customized, such as whether parameter |
| * should be removed, or whether options are mandatory etc. |
| * |
| * @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 |
| * @see #build() |
| */ |
| public static boolean bindProperties(CamelContext camelContext, Object target, Map<String, Object> properties) { |
| // mandatory parameters |
| org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext"); |
| org.apache.camel.util.ObjectHelper.notNull(target, "target"); |
| org.apache.camel.util.ObjectHelper.notNull(properties, "properties"); |
| |
| return PropertyBindingSupport.build().bind(camelContext, target, properties); |
| } |
| |
| /** |
| * 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. |
| * |
| * @param camelContext the camel context |
| * @param target the target object |
| * @param properties the properties where the bound properties will be removed from |
| * @param optionPrefix the prefix used to filter properties |
| * @param ignoreCase whether to ignore case for property keys |
| * @param removeParameter whether to remove bound parameters |
| * @param mandatory whether all parameters must be bound |
| * @param nesting whether nesting is in use |
| * @param deepNesting 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. |
| * @param fluentBuilder whether fluent builder is allowed as a valid getter/setter |
| * @param allowPrivateSetter whether autowiring components allows to use private setter method when setting the value |
| * @param reference whether reference parameter (syntax starts with #) is in use |
| * @param placeholder whether to use Camels property placeholder to resolve placeholders on keys and values |
| * @param configurer to use an optional {@link org.apache.camel.spi.PropertyConfigurer} to configure the properties |
| * @return true if one or more properties was bound |
| */ |
| private static boolean doBindProperties(CamelContext camelContext, Object target, Map<String, Object> properties, |
| String optionPrefix, boolean ignoreCase, boolean removeParameter, boolean mandatory, |
| boolean nesting, boolean deepNesting, boolean fluentBuilder, boolean allowPrivateSetter, |
| boolean reference, boolean placeholder, |
| PropertyConfigurer configurer) { |
| org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext"); |
| org.apache.camel.util.ObjectHelper.notNull(target, "target"); |
| org.apache.camel.util.ObjectHelper.notNull(properties, "properties"); |
| |
| final String uOptionPrefix = ignoreCase && isNotEmpty(optionPrefix) ? optionPrefix.toUpperCase(Locale.US) : ""; |
| final int size = properties.size(); |
| |
| if (configurer instanceof GeneratedPropertyConfigurer) { |
| GeneratedPropertyConfigurer gen = (GeneratedPropertyConfigurer) configurer; |
| |
| for (Iterator<Map.Entry<String, Object>> iter = properties.entrySet().iterator(); iter.hasNext();) { |
| Map.Entry<String, Object> entry = iter.next(); |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| |
| // property configurer does not support nested names so skip if the name has a dot |
| if (key.indexOf('.') == -1) { |
| try { |
| // GeneratedPropertyConfigurer works by invoking the methods directly but it does |
| // not resolve property placeholders eventually defined in the value before invoking |
| // the setter. |
| if (value instanceof String) { |
| value = camelContext.resolvePropertyPlaceholders((String) value); |
| } |
| value = resolveValue(camelContext, target, key, value, ignoreCase, fluentBuilder, allowPrivateSetter); |
| boolean hit = gen.configure(camelContext, target, key, value, ignoreCase); |
| if (removeParameter && hit) { |
| iter.remove(); |
| } |
| } catch (Exception e) { |
| throw new PropertyBindingException(target, key, value, e); |
| } |
| } |
| } |
| } |
| |
| // must set reference parameters first before the other bindings |
| setReferenceProperties(camelContext, target, properties); |
| |
| // sort the keys by nesting level |
| properties.keySet().stream() |
| .sorted(Comparator.comparingInt(s -> StringHelper.countChar(s, '.'))) |
| .forEach(key -> { |
| final String propertyKey = key; |
| final Object value = properties.get(key); |
| |
| if (isNotEmpty(optionPrefix)) { |
| boolean match = key.startsWith(optionPrefix) || ignoreCase && key.toUpperCase(Locale.US).startsWith(uOptionPrefix); |
| if (!match) { |
| return; |
| } |
| key = key.substring(optionPrefix.length()); |
| } |
| |
| boolean bound = bindProperty(camelContext, target, key, value, ignoreCase, nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder); |
| if (bound && removeParameter) { |
| properties.remove(propertyKey); |
| } |
| if (mandatory && !bound) { |
| throw new PropertyBindingException(target, propertyKey, value); |
| } |
| } |
| ); |
| |
| return properties.size() != size; |
| } |
| |
| private static boolean bindProperty(CamelContext camelContext, Object target, String name, Object value, |
| boolean ignoreCase, boolean nesting, boolean deepNesting, boolean fluentBuilder, |
| boolean allowPrivateSetter, boolean reference, boolean placeholder) { |
| try { |
| if (target != null && name != null) { |
| return setProperty(camelContext, target, name, value, false, ignoreCase, nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder); |
| } |
| } catch (Exception e) { |
| throw new PropertyBindingException(target, name, value, e); |
| } |
| |
| return false; |
| } |
| |
| private static Object resolveValue(CamelContext context, Object target, String name, Object value, |
| boolean ignoreCase, boolean fluentBuilder, boolean allowPrivateSetter) throws Exception { |
| if (value instanceof String) { |
| if (value.toString().startsWith("#class:")) { |
| // its a new class to be created |
| String className = value.toString().substring(7); |
| String factoryMethod = null; |
| String parameters = null; |
| if (className.endsWith(")") && className.indexOf('(') != -1) { |
| parameters = StringHelper.after(className, "("); |
| parameters = parameters.substring(0, parameters.length() - 1); // clip last ) |
| className = StringHelper.before(className, "("); |
| } |
| if (className != null && className.indexOf('#') != -1) { |
| factoryMethod = StringHelper.after(className, "#"); |
| className = StringHelper.before(className, "#"); |
| } |
| Class<?> type = context.getClassResolver().resolveMandatoryClass(className); |
| if (factoryMethod != null) { |
| value = context.getInjector().newInstance(type, factoryMethod); |
| } else if (parameters != null) { |
| // special to support constructor parameters |
| value = newInstanceConstructorParameters(context, type, parameters); |
| } else { |
| value = context.getInjector().newInstance(type); |
| } |
| if (value == null) { |
| throw new IllegalStateException("Cannot create instance of class: " + className); |
| } |
| } else if (value.toString().startsWith("#type:")) { |
| // its reference by type, so lookup the actual value and use it if there is only one instance in the registry |
| String typeName = value.toString().substring(6); |
| Class<?> type = context.getClassResolver().resolveMandatoryClass(typeName); |
| Set<?> types = context.getRegistry().findByType(type); |
| if (types.size() == 1) { |
| value = types.iterator().next(); |
| } else if (types.size() > 1) { |
| throw new IllegalStateException("Cannot select single type: " + typeName + " as there are " + types.size() + " beans in the registry with this type"); |
| } else { |
| throw new IllegalStateException("Cannot select single type: " + typeName + " as there are no beans in the registry with this type"); |
| } |
| } else if (value.toString().equals("#autowired")) { |
| // we should get the type from the setter |
| Method method = findBestSetterMethod(context, target.getClass(), name, fluentBuilder, allowPrivateSetter, ignoreCase); |
| if (method != null) { |
| Class<?> parameterType = method.getParameterTypes()[0]; |
| Set<?> types = context.getRegistry().findByType(parameterType); |
| if (types.size() == 1) { |
| value = types.iterator().next(); |
| } else if (types.size() > 1) { |
| throw new IllegalStateException("Cannot select single type: " + parameterType + " as there are " + types.size() + " beans in the registry with this type"); |
| } else { |
| throw new IllegalStateException("Cannot select single type: " + parameterType + " as there are no beans in the registry with this type"); |
| } |
| } else { |
| throw new IllegalStateException("Cannot find setter method with name: " + name + " on class: " + target.getClass().getName() + " to use for autowiring"); |
| } |
| } else if (value.toString().startsWith("#bean:")) { |
| String key = value.toString().substring(6); |
| value = context.getRegistry().lookupByName(key); |
| } |
| } |
| return value; |
| } |
| |
| private static boolean setProperty(CamelContext context, Object target, String name, Object value, boolean mandatory, |
| boolean ignoreCase, boolean nesting, boolean deepNesting, boolean fluentBuilder, |
| boolean allowPrivateSetter, boolean reference, boolean placeholder) throws Exception { |
| String refName = null; |
| |
| if (placeholder) { |
| // resolve property placeholders |
| name = context.resolvePropertyPlaceholders(name); |
| if (value instanceof String) { |
| // resolve property placeholders |
| value = context.resolvePropertyPlaceholders(value.toString()); |
| } |
| } |
| |
| String ognlPath = name; |
| |
| // if name has dot then we need to OGNL walk it |
| if (nesting) { |
| if (name.indexOf('.') > 0) { |
| String[] parts = name.split("\\."); |
| Object newTarget = target; |
| Class<?> newClass = target.getClass(); |
| // we should only iterate until until 2nd last so we use -1 in the for loop |
| for (int i = 0; i < parts.length - 1; i++) { |
| String part = parts[i]; |
| Object prop = getOrElseProperty(context, newTarget, part, null, ignoreCase); |
| if (prop == null) { |
| if (!deepNesting) { |
| // okay we cannot go further down |
| break; |
| } |
| // okay is there a setter so we can create a new instance and set it automatic |
| Method method = findBestSetterMethod(context, newClass, part, fluentBuilder, allowPrivateSetter, ignoreCase); |
| if (method != null) { |
| Class<?> parameterType = method.getParameterTypes()[0]; |
| Object instance = null; |
| if (parameterType != null && org.apache.camel.util.ObjectHelper.hasDefaultPublicNoArgConstructor(parameterType)) { |
| instance = context.getInjector().newInstance(parameterType); |
| } |
| if (instance != null) { |
| org.apache.camel.support.ObjectHelper.invokeMethod(method, newTarget, instance); |
| newTarget = instance; |
| newClass = newTarget.getClass(); |
| } |
| } else { |
| if (mandatory) { |
| // there is no getter with this given name, so lets report this as a problem |
| throw new IllegalArgumentException("Cannot find getter method: " + part + " on bean: " + newClass + " when binding property: " + ognlPath); |
| } |
| } |
| } else { |
| newTarget = prop; |
| newClass = newTarget.getClass(); |
| } |
| } |
| if (newTarget != target) { |
| // okay we found a nested property, then lets change to use that |
| target = newTarget; |
| name = parts[parts.length - 1]; |
| } |
| } |
| } |
| |
| if (reference && value instanceof String) { |
| if (value.toString().startsWith("#bean:")) { |
| // okay its a reference so swap to lookup this which is already supported in IntrospectionSupport |
| refName = "#" + ((String) value).substring(6); |
| value = null; |
| } else { |
| value = resolveValue(context, target, name, value, ignoreCase, fluentBuilder, allowPrivateSetter); |
| } |
| } |
| boolean hit = context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, context.getTypeConverter(), target, name, value, refName, fluentBuilder, allowPrivateSetter, ignoreCase); |
| if (!hit && mandatory) { |
| // there is no setter with this given name, so lets report this as a problem |
| throw new IllegalArgumentException("Cannot find setter method: " + name + " on bean: " + target + " of type: " + target.getClass().getName() + " when binding property: " + ognlPath); |
| } |
| return hit; |
| } |
| |
| private static Object getOrElseProperty(CamelContext context, Object target, String property, Object defaultValue, boolean ignoreCase) { |
| String key = property; |
| String lookupKey = null; |
| |
| // support maps in keys |
| if (property.contains("[") && property.endsWith("]")) { |
| int pos = property.indexOf('['); |
| lookupKey = property.substring(pos + 1, property.length() - 1); |
| key = property.substring(0, pos); |
| } |
| |
| Object answer = context.adapt(ExtendedCamelContext.class).getBeanIntrospection().getOrElseProperty(target, key, defaultValue, ignoreCase); |
| if (answer instanceof Map && lookupKey != null) { |
| Map map = (Map) answer; |
| answer = map.getOrDefault(lookupKey, defaultValue); |
| } else if (answer instanceof List) { |
| List list = (List) answer; |
| if (isNotEmpty(lookupKey)) { |
| int idx = Integer.valueOf(lookupKey); |
| answer = list.get(idx); |
| } else { |
| if (list.isEmpty()) { |
| answer = null; |
| } else { |
| answer = list.get(list.size() - 1); |
| } |
| } |
| } |
| |
| return answer != null ? answer : defaultValue; |
| } |
| |
| private static Method findBestSetterMethod(CamelContext context, Class clazz, String name, |
| boolean fluentBuilder, boolean allowPrivateSetter, boolean ignoreCase) { |
| // is there a direct setter? |
| Set<Method> candidates = context.adapt(ExtendedCamelContext.class).getBeanIntrospection().findSetterMethods(clazz, name, false, allowPrivateSetter, ignoreCase); |
| if (candidates.size() == 1) { |
| return candidates.iterator().next(); |
| } |
| |
| // okay now try with builder pattern |
| if (fluentBuilder) { |
| candidates = context.adapt(ExtendedCamelContext.class).getBeanIntrospection().findSetterMethods(clazz, name, fluentBuilder, allowPrivateSetter, ignoreCase); |
| if (candidates.size() == 1) { |
| return candidates.iterator().next(); |
| } |
| } |
| |
| return null; |
| } |
| |
| private static Class getGetterType(CamelContext context, Object target, String name, boolean ignoreCase) { |
| try { |
| if (ignoreCase) { |
| Method getter = context.adapt(ExtendedCamelContext.class).getBeanIntrospection().getPropertyGetter(target.getClass(), name, true); |
| if (getter != null) { |
| return getter.getReturnType(); |
| } |
| } else { |
| Method getter = context.adapt(ExtendedCamelContext.class).getBeanIntrospection().getPropertyGetter(target.getClass(), name, false); |
| if (getter != null) { |
| return getter.getReturnType(); |
| } |
| } |
| } catch (NoSuchMethodException e) { |
| // ignore |
| } |
| return null; |
| } |
| |
| private static boolean isComplexUserType(Class type) { |
| // lets consider all non java, as complex types |
| return type != null && !type.isPrimitive() && !type.getName().startsWith("java."); |
| } |
| |
| private static void setReferenceProperties(CamelContext context, Object target, Map<String, Object> parameters) { |
| Iterator<Map.Entry<String, Object>> it = parameters.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry<String, Object> entry = it.next(); |
| String name = entry.getKey(); |
| |
| // we only support basic keys |
| if (name.contains(".") || name.contains("[") || name.contains("]")) { |
| continue; |
| } |
| |
| Object v = entry.getValue(); |
| String value = v != null ? v.toString() : null; |
| if (isReferenceParameter(value)) { |
| try { |
| boolean hit = context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, context.getTypeConverter(), target, name, null, value, true, false, false); |
| if (hit) { |
| // must remove as its a valid option and we could configure it |
| it.remove(); |
| } |
| } catch (Exception e) { |
| throw new PropertyBindingException(target, e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Is the given parameter a reference parameter (starting with a # char) |
| * |
| * @param parameter the parameter |
| * @return <tt>true</tt> if its a reference parameter |
| */ |
| private static boolean isReferenceParameter(String parameter) { |
| return parameter != null && parameter.trim().startsWith("#"); |
| } |
| |
| private static Object newInstanceConstructorParameters(CamelContext camelContext, Class<?> type, String parameters) throws Exception { |
| String[] params = StringQuoteHelper.splitSafeQuote(parameters, ','); |
| Constructor found = findMatchingConstructor(type.getConstructors(), params); |
| if (found != null) { |
| Object[] arr = new Object[found.getParameterCount()]; |
| for (int i = 0; i < found.getParameterCount(); i++) { |
| Class<?> paramType = found.getParameterTypes()[i]; |
| Object param = params[i]; |
| Object val = camelContext.getTypeConverter().convertTo(paramType, param); |
| // unquote text |
| if (val instanceof String) { |
| val = StringHelper.removeLeadingAndEndingQuotes((String) val); |
| } |
| arr[i] = val; |
| } |
| return found.newInstance(arr); |
| } |
| return null; |
| } |
| |
| /** |
| * Finds the best matching constructor for the given parameters. |
| * <p/> |
| * This implementation is similar to the logic in camel-bean. |
| * |
| * @param constructors the constructors |
| * @param params the parameters |
| * @return the constructor, or null if no matching constructor can be found |
| */ |
| private static Constructor findMatchingConstructor(Constructor<?>[] constructors, String[] params) { |
| List<Constructor> candidates = new ArrayList<>(); |
| Constructor fallbackCandidate = null; |
| |
| for (Constructor ctr : constructors) { |
| if (ctr.getParameterCount() != params.length) { |
| continue; |
| } |
| |
| boolean matches = true; |
| for (int i = 0; i < ctr.getParameterCount(); i++) { |
| String parameter = params[i]; |
| if (parameter != null) { |
| // must trim |
| parameter = parameter.trim(); |
| } |
| |
| Class<?> parameterType = getValidParameterType(parameter); |
| Class<?> expectedType = ctr.getParameterTypes()[i]; |
| |
| if (parameterType != null && expectedType != null) { |
| // skip java.lang.Object type, when we have multiple possible methods we want to avoid it if possible |
| if (Object.class.equals(expectedType)) { |
| fallbackCandidate = ctr; |
| matches = false; |
| break; |
| } |
| |
| boolean matchingTypes = isParameterMatchingType(parameterType, expectedType); |
| if (!matchingTypes) { |
| matches = false; |
| break; |
| } |
| } |
| } |
| |
| if (matches) { |
| candidates.add(ctr); |
| } |
| } |
| |
| return candidates.size() == 1 ? candidates.get(0) : fallbackCandidate; |
| } |
| |
| /** |
| * Determines and maps the given value is valid according to the supported |
| * values by the bean component. |
| * <p/> |
| * This implementation is similar to the logic in camel-bean. |
| * |
| * @param value the value |
| * @return the parameter type the given value is being mapped as, or <tt>null</tt> if not valid. |
| */ |
| private static Class<?> getValidParameterType(String value) { |
| if (org.apache.camel.util.ObjectHelper.isEmpty(value)) { |
| return null; |
| } |
| |
| // trim value |
| value = value.trim(); |
| |
| // single quoted is valid |
| if (value.startsWith("'") && value.endsWith("'")) { |
| return String.class; |
| } |
| |
| // double quoted is valid |
| if (value.startsWith("\"") && value.endsWith("\"")) { |
| return String.class; |
| } |
| |
| // true or false is valid (boolean) |
| if (value.equals("true") || value.equals("false")) { |
| return Boolean.class; |
| } |
| |
| // null is valid (to force a null value) |
| if (value.equals("null")) { |
| return Object.class; |
| } |
| |
| // simple language tokens is valid |
| if (StringHelper.hasStartToken(value, "simple")) { |
| return Object.class; |
| } |
| |
| // numeric is valid |
| boolean numeric = true; |
| for (char ch : value.toCharArray()) { |
| if (!Character.isDigit(ch)) { |
| numeric = false; |
| break; |
| } |
| } |
| if (numeric) { |
| return Number.class; |
| } |
| |
| // not valid |
| return null; |
| } |
| |
| private static boolean isParameterMatchingType(Class<?> parameterType, Class<?> expectedType) { |
| if (Number.class.equals(parameterType)) { |
| // number should match long/int/etc. |
| if (Integer.class.isAssignableFrom(expectedType) || Long.class.isAssignableFrom(expectedType) |
| || int.class.isAssignableFrom(expectedType) || long.class.isAssignableFrom(expectedType)) { |
| return true; |
| } |
| } |
| if (Boolean.class.equals(parameterType)) { |
| // boolean should match both Boolean and boolean |
| if (Boolean.class.isAssignableFrom(expectedType) || boolean.class.isAssignableFrom(expectedType)) { |
| return true; |
| } |
| } |
| return parameterType.isAssignableFrom(expectedType); |
| } |
| |
| } |