You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by dh...@apache.org on 2014/06/10 21:51:33 UTC

[05/35] git commit: Initial version of Component utilities

Initial version of Component utilities


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/10a0b615
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/10a0b615
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/10a0b615

Branch: refs/heads/master
Commit: 10a0b6156a1a2a7ad8d349d3e561b713b5e51377
Parents: c59a0ff
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Fri May 23 13:29:39 2014 -0700
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Tue Jun 10 12:48:29 2014 -0700

----------------------------------------------------------------------
 .../camel/util/component/ApiCollection.java     |  33 ++
 .../apache/camel/util/component/ApiMethod.java  |  57 +++
 .../camel/util/component/ApiMethodHelper.java   | 366 +++++++++++++++++++
 .../camel/util/component/ApiMethodImpl.java     | 123 +++++++
 .../camel/util/component/ApiMethodParser.java   | 288 +++++++++++++++
 .../component/ApiMethodPropertiesHelper.java    | 103 ++++++
 .../component/ArgumentSubstitutionParser.java   | 143 ++++++++
 .../util/component/ApiMethodHelperTest.java     | 169 +++++++++
 .../ApiMethodPropertiesHelperTest.java          | 122 +++++++
 .../ArgumentSubstitutionParserTest.java         |  52 +++
 .../apache/camel/util/component/TestProxy.java  |  64 ++++
 11 files changed, 1520 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/main/java/org/apache/camel/util/component/ApiCollection.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/component/ApiCollection.java b/camel-core/src/main/java/org/apache/camel/util/component/ApiCollection.java
new file mode 100644
index 0000000..585fbb2
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/component/ApiCollection.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.util.component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base class for a collection of ApiMethods. Meant to be extended by Components to create the api name map.
+ */
+public abstract class ApiCollection {
+
+    protected final Map<String, ApiMethodHelper> apis = new HashMap<String, ApiMethodHelper>();
+
+    public final ApiMethodHelper getHelper(String apiName) {
+        return apis.get(apiName);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/main/java/org/apache/camel/util/component/ApiMethod.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/component/ApiMethod.java b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethod.java
new file mode 100644
index 0000000..7ee7cb9
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethod.java
@@ -0,0 +1,57 @@
+/**
+ * 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.util.component;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * Interface for proxy methods.
+ */
+public interface ApiMethod {
+
+    /**
+     * Returns method name.
+     * @return
+     */
+    String getName();
+
+    /**
+     * Returns method result type.
+     * @return
+     */
+    Class<?> getResultType();
+
+    /**
+     * Returns method argument names.
+     * @return
+     */
+    List<String> getArgNames();
+
+    /**
+     * Return method argument types.
+     * @return
+     */
+    List<Class<?>> getArgTypes();
+
+    /**
+     * Returns {@link Method} in proxy type.
+     * @return
+     */
+    Method getMethod();
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodHelper.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodHelper.java b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodHelper.java
new file mode 100644
index 0000000..9f250f0
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodHelper.java
@@ -0,0 +1,366 @@
+/**
+ * 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.util.component;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.camel.RuntimeCamelException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper class for working with {@link ApiMethod}.
+ */
+public final class ApiMethodHelper<T extends Enum<T> & ApiMethod> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ApiMethodHelper.class);
+
+    // maps method name to ApiMethod
+    private final Map<String, List<T>> METHOD_MAP = new HashMap<String, List<T>>();
+
+    // maps method name to method arguments of the form Class type1, String name1, Class type2, String name2,...
+    private final Map<String, List<Object>> ARGUMENTS_MAP = new HashMap<String, List<Object>>();
+
+    // maps argument name to argument type
+    private final Map<String, Class<?>> VALID_ARGUMENTS = new HashMap<String, Class<?>>();
+
+    // maps aliases to actual method names
+    private final HashMap<String, String> ALIASES = new HashMap<String, String>();
+
+    /**
+     * Create a helper to work with a {@link ApiMethod}, using optional method aliases.
+     * @param apiMethodEnum {@link ApiMethod} enumeration class
+     * @param aliases Aliases mapped to actual method names
+     */
+    public ApiMethodHelper(Class<T> apiMethodEnum, Map<String, String> aliases) {
+
+        // validate ApiMethod Enum
+        if (apiMethodEnum == null) {
+            throw new IllegalArgumentException("ApiMethod enumeration cannot be null");
+        }
+
+        final Map<Pattern, String> aliasPatterns = new HashMap<Pattern, String>();
+        for (Map.Entry<String, String> alias : aliases.entrySet()) {
+            if (alias.getKey() == null || alias.getValue() == null) {
+                throw new IllegalArgumentException("Alias pattern and replacement cannot be null");
+            }
+            aliasPatterns.put(Pattern.compile(alias.getKey()), alias.getValue());
+        }
+
+        LOG.debug("Processing " + apiMethodEnum.getName());
+        final T[] methods = apiMethodEnum.getEnumConstants();
+
+        // load lookup maps
+        for (T method : methods) {
+
+            final String name = method.getName();
+
+            // add method name aliases
+            for (Map.Entry<Pattern, String> aliasEntry : aliasPatterns.entrySet()) {
+                final Matcher matcher = aliasEntry.getKey().matcher(name);
+                if (matcher.find()) {
+                    // add method name alias
+                    String alias = matcher.replaceAll(aliasEntry.getValue());
+                    // convert first character to lowercase
+                    assert alias.length() > 1;
+                    final char firstChar = alias.charAt(0);
+                    if (!Character.isLowerCase(firstChar)) {
+                        final StringBuilder builder = new StringBuilder();
+                        builder.append(Character.toLowerCase(firstChar)).append(alias.substring(1));
+                        alias = builder.toString();
+                    }
+                    ALIASES.put(alias, name);
+                }
+            }
+
+            // map method name to Enum
+            List<T> overloads = METHOD_MAP.get(name);
+            if (overloads == null) {
+                overloads = new ArrayList<T>();
+                METHOD_MAP.put(method.getName(), overloads);
+            }
+            overloads.add(method);
+
+            // add arguments for this method
+            List<Object> arguments = ARGUMENTS_MAP.get(name);
+            if (arguments == null) {
+                arguments = new ArrayList<Object>();
+                ARGUMENTS_MAP.put(name, arguments);
+            }
+
+            // process all arguments for this method
+            final int nArgs = method.getArgNames().size();
+            final String[] argNames = method.getArgNames().toArray(new String[nArgs]);
+            final Class<?>[] argTypes = method.getArgTypes().toArray(new Class[nArgs]);
+            for (int i = 0; i < nArgs; i++) {
+                final String argName = argNames[i];
+                final Class<?> argType = argTypes[i];
+                if (!arguments.contains(argName)) {
+                    arguments.add(argType);
+                    arguments.add(argName);
+                }
+
+                // also collect argument names for all methods, and detect clashes here
+                final Class<?> previousType = VALID_ARGUMENTS.get(argName);
+                if (previousType != null && previousType != argType) {
+                    throw new ExceptionInInitializerError(String.format(
+                        "Argument %s has ambiguous types (%s, %s) across methods!",
+                        name, previousType, argType));
+                } else if (previousType == null) {
+                    VALID_ARGUMENTS.put(argName, argType);
+                }
+            }
+
+        }
+
+        LOG.debug("Found {} unique method names in {} methods", METHOD_MAP.size(), methods.length);
+    }
+
+    /**
+     * Gets methods that match the given name and arguments.<p/>
+     * Note that the args list is a required subset of arguments for returned methods.
+     * @param name case sensitive full method name to lookup
+     * @param argNames unordered required argument names
+     * @return non-null unmodifiable list of methods that take all of the given arguments, empty if there is no match
+     */
+    public List<T> getCandidateMethods(String name, String... argNames) {
+        List<T> methods = METHOD_MAP.get(name);
+        if (methods == null) {
+            if (ALIASES.containsKey(name)) {
+                methods = METHOD_MAP.get(ALIASES.get(name));
+            }
+        }
+        if (methods == null) {
+            LOG.debug("No matching method for method {}", name);
+            return Collections.emptyList();
+        }
+        int nArgs = argNames != null ? argNames.length : 0;
+        if (nArgs == 0) {
+            LOG.debug("Found {} methods for method {}", methods.size(), name);
+            return Collections.unmodifiableList(methods);
+        } else {
+            final List<T> filteredSet = filterMethods(methods, MatchType.SUBSET, argNames);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Found {} filtered methods for {}",
+                    filteredSet.size(), name + Arrays.toString(argNames).replace('[', '(').replace(']', ')'));
+            }
+            return filteredSet;
+        }
+    }
+
+    /**
+     * Filters a list of methods to those that take the given set of arguments.
+     *
+     * @param methods list of methods to filter
+     * @param matchType whether the arguments are an exact match, a subset or a super set of method args
+     * @param argNames argument names to filter the list
+     * @return methods with arguments that satisfy the match type.<p/>
+     * For SUPER_SET match, if methods with exact match are found, methods that take a subset are ignored
+     */
+    public List<T> filterMethods(List<T> methods, MatchType matchType,
+                                                          String... argNames) {
+        List<String> argsList = Arrays.asList(argNames);
+        // list of methods that have all args in the given names
+        final List<T> result = new ArrayList<T>();
+        final List<T> extraArgs = new ArrayList<T>();
+
+        for (T method : methods) {
+            final List<String> methodArgs = method.getArgNames();
+            switch (matchType) {
+            case EXACT:
+                // method must take all args, and no more
+                if (methodArgs.containsAll(argsList) && argsList.containsAll(methodArgs)) {
+                    result.add(method);
+                }
+                break;
+            case SUBSET:
+                // all args are required, method may take more
+                if (methodArgs.containsAll(argsList)) {
+                    result.add(method);
+                }
+                break;
+            default:
+            case SUPER_SET:
+                // all method args must be present
+                if (argsList.containsAll(methodArgs)) {
+                    if (methodArgs.containsAll(argsList)) {
+                        // prefer exact match to avoid unused args
+                        result.add(method);
+                    } else {
+                        // method takes a subset, unused args
+                        extraArgs.add(method);
+                    }
+                }
+                break;
+            }
+        }
+
+        return Collections.unmodifiableList(result.isEmpty() ? extraArgs : result);
+    }
+
+    /**
+     * Gets argument types and names for all overloaded methods and aliases with the given name.
+     * @param name method name, either an exact name or an alias, exact matches are checked first
+     * @return list of arguments of the form Class type1, String name1, Class type2, String name2,...
+     */
+    public List<Object> getArguments(final String name) throws IllegalArgumentException {
+        List<Object> arguments = ARGUMENTS_MAP.get(name);
+        if (arguments == null) {
+            if (ALIASES.containsKey(name)) {
+                arguments = ARGUMENTS_MAP.get(ALIASES.get(name));
+            }
+        }
+        if (arguments == null) {
+            throw new IllegalArgumentException(name);
+        }
+        return Collections.unmodifiableList(arguments);
+    }
+
+    /**
+     * Get missing properties.
+     * @param methodName method name
+     * @param argNames available arguments
+     * @return Set of missing argument names
+     */
+    public Set<String> getMissingProperties(String methodName, Set<String> argNames) {
+        final List<Object> argsWithTypes = getArguments(methodName);
+        final Set<String> missingArgs = new HashSet<String>();
+
+        for (int i = 1; i < argsWithTypes.size(); i += 2) {
+            final String name = (String) argsWithTypes.get(i);
+            if (!argNames.contains(name)) {
+                missingArgs.add(name);
+            }
+        }
+
+        return missingArgs;
+    }
+
+    /**
+     * Get argument types and names used by all methods.
+     * @return map with argument names as keys, and types as values
+     */
+    public Map<String, Class<?>> allArguments() {
+        return Collections.unmodifiableMap(VALID_ARGUMENTS);
+    }
+
+    /**
+     * Get the type for the given argument name.
+     * @param argName argument name
+     * @return argument type
+     */
+    public Class<?> getType(String argName) throws IllegalArgumentException {
+        final Class<?> type = VALID_ARGUMENTS.get(argName);
+        if (type == null) {
+            throw new IllegalArgumentException(argName);
+        }
+        return type;
+    }
+
+    public T getHighestPriorityMethod(List<T> filteredMethods) {
+        T highest = null;
+        for (T method : filteredMethods) {
+            if (highest == null || method.compareTo(highest) > 0) {
+                highest = method;
+            }
+        }
+        return highest;
+    }
+
+    /**
+     * Invokes given method with argument values from given properties.
+     *
+     * @param proxy Proxy object for invoke
+     * @param method method to invoke
+     * @param properties Map of arguments
+     * @return result of method invocation
+     * @throws org.apache.camel.RuntimeCamelException on errors
+     */
+    public Object invokeMethod(Object proxy, T method, Map<String, Object> properties)
+        throws RuntimeCamelException {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Invoking {} with arguments {}", method.getName(), properties);
+        }
+
+        final List<String> argNames = method.getArgNames();
+        final Object[] values = new Object[argNames.size()];
+        final List<Class<?>> argTypes = method.getArgTypes();
+        final Class<?>[] types = argTypes.toArray(new Class[argTypes.size()]);
+        int index = 0;
+        for (String name : argNames) {
+            Object value = properties.get(name);
+
+            // is the parameter an array type?
+            if (value != null && types[index].isArray()) {
+                Class<?> type = types[index];
+
+                if (value instanceof Collection) {
+                    // convert collection to array
+                    Collection<?> collection = (Collection<?>) value;
+                    Object array = Array.newInstance(type.getComponentType(), collection.size());
+                    if (array instanceof Object[]) {
+                        collection.toArray((Object[]) array);
+                    } else {
+                        int i = 0;
+                        for (Object el : collection) {
+                            Array.set(array, i++, el);
+                        }
+                    }
+                    value = array;
+                } else if (value.getClass().isArray()
+                    && type.getComponentType().isAssignableFrom(value.getClass().getComponentType())) {
+                    // convert derived array to super array
+                    final int size = Array.getLength(value);
+                    Object array = Array.newInstance(type.getComponentType(), size);
+                    for (int i = 0; i < size; i++) {
+                        Array.set(array, i, Array.get(value, i));
+                    }
+                    value = array;
+                } else {
+                    throw new IllegalArgumentException(
+                        String.format("Cannot convert %s to %s", value.getClass(), type));
+                }
+            }
+
+            values[index++] = value;
+        }
+
+        try {
+            return method.getMethod().invoke(proxy, values);
+        } catch (Throwable e) {
+            throw new RuntimeCamelException(
+                String.format("Error invoking %s with %s: %s", method.getName(), properties, e.getMessage()), e);
+        }
+    }
+
+    public static enum MatchType {
+        EXACT, SUBSET, SUPER_SET
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodImpl.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodImpl.java b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodImpl.java
new file mode 100644
index 0000000..2884164
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodImpl.java
@@ -0,0 +1,123 @@
+/**
+ * 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.util.component;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Delegate class for {@link ApiMethod}.
+ * This class is instantiated by Enumerations for Api Proxy types.
+ * <p>
+ *     For example:
+ * </p>
+ * <pre>
+ * {@code
+ *     public enum HelloWorldMethod implements ApiMethod {
+ *         SAYHI(String.class, "sayHi", "name", String.class);
+ *
+ *         private ApiMethodImpl apiMethod;
+ *
+ *         private HelloWorldMethods(Class<?> resultType, String name, Object... args) throws IllegalArgumentException {
+ *             this.apiMethod = new ApiMethod(HelloWorld.class, resultType, name, args);
+ *         }
+ *
+ *         // implement ApiMethod interface
+ *         String getName() { return apiMethod.getName(); }
+ *         Class<?> getResultType() {return apiMethod.getResultType(); }
+ *         List<String> getArgNames() { return apiMethod.getArgNames(); }
+ *         List<Class<?>> getArgTypes() {return apiMethod.getArgTypes(); }
+ *         Method getMethod() { return apiMethod.getMethod(); }
+ *     }
+ * }
+ * </pre>
+ */
+public final class ApiMethodImpl implements ApiMethod {
+
+    // name, result class, ordered argument names and classes, and Method to invoke
+    private final String name;
+    private final Class<?> resultType;
+    private final List<String> argNames;
+    private final List<Class<?>> argTypes;
+    private final Method method;
+
+    public ApiMethodImpl(Class<?> proxyType, Class<?> resultType, String name, Object... args) throws IllegalArgumentException {
+        this.name = name;
+        this.resultType = resultType;
+
+        if (args.length % 2 != 0) {
+            throw new IllegalArgumentException("Invalid parameter list, "
+                + "must be of the form 'Class arg1, String arg1Name, Class arg2, String arg2Name...");
+        }
+        int nArgs = args.length / 2;
+        this.argNames = new ArrayList<String>(nArgs);
+        this.argTypes = new ArrayList<Class<?>>(nArgs);
+        for (int i = 0; i < nArgs; i++) {
+            this.argTypes.add((Class<?>) args[i * 2]);
+            this.argNames.add((String) args[i * 2 + 1]);
+        }
+
+        // find method in Proxy type
+        try {
+            this.method = proxyType.getMethod(name, argTypes.toArray(new Class[nArgs]));
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException(
+                String.format("Missing method %s %s", name, argTypes.toString().replace('[', '(').replace(']', ')')),
+                e);
+        }
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Class<?> getResultType() {
+        return resultType;
+    }
+
+    @Override
+    public List<String> getArgNames() {
+        return Collections.unmodifiableList(argNames);
+    }
+
+    @Override
+    public List<Class<?>> getArgTypes() {
+        return Collections.unmodifiableList(argTypes);
+    }
+
+    @Override
+    public Method getMethod() {
+        return method;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{")
+            .append("name=").append(name)
+            .append(", resultType=").append(resultType)
+            .append(", argNames=").append(argNames)
+            .append(", argTypes=").append(argTypes)
+            .append("}");
+        return builder.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodParser.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodParser.java b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodParser.java
new file mode 100644
index 0000000..4c6523a
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodParser.java
@@ -0,0 +1,288 @@
+/**
+ * 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.util.component;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Parser base class for generating ApiMethod enumerations.
+ */
+public abstract class ApiMethodParser<T> {
+
+    private static final Pattern METHOD_PATTERN = Pattern.compile("\\s*(\\S+)\\s+(\\S+)\\s*\\(\\s*([\\S\\s,]*)\\)\\s*;?\\s*");
+    private static final Pattern ARGS_PATTERN = Pattern.compile("\\s*(\\S+)\\s+([^\\s,]+)\\s*,?");
+    private static final String JAVA_LANG = "java.lang.";
+    private static final Map<String, Class> PRIMITIVE_TYPES;
+
+    static {
+        PRIMITIVE_TYPES = new HashMap<String, Class>();
+        PRIMITIVE_TYPES.put("int", Integer.TYPE);
+        PRIMITIVE_TYPES.put("long", Long.TYPE);
+        PRIMITIVE_TYPES.put("double", Double.TYPE);
+        PRIMITIVE_TYPES.put("float", Float.TYPE);
+        PRIMITIVE_TYPES.put("boolean", Boolean.TYPE);
+        PRIMITIVE_TYPES.put("char", Character.TYPE);
+        PRIMITIVE_TYPES.put("byte", Byte.TYPE);
+        PRIMITIVE_TYPES.put("void", Void.TYPE);
+        PRIMITIVE_TYPES.put("short", Short.TYPE);
+    }
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final Class<T> proxyType;
+    private List<String> signatures;
+    private ClassLoader classLoader = ApiMethodParser.class.getClassLoader();
+
+    public ApiMethodParser(Class<T> proxyType) {
+        this.proxyType = proxyType;
+    }
+
+    public Class<T> getProxyType() {
+        return proxyType;
+    }
+
+    public final List<String> getSignatures() {
+        return signatures;
+    }
+
+    public final void setSignatures(List<String> signatures) {
+        this.signatures = new ArrayList<String>();
+        this.signatures.addAll(signatures);
+    }
+
+    public final ClassLoader getClassLoader() {
+        return classLoader;
+    }
+
+    public final void setClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    /**
+     * Parses the method signatures from {@code getSignatures()}.
+     * @return list of Api methods as {@link ApiMethodModel}
+     */
+    public List<ApiMethodModel> parse() {
+        // parse sorted signatures and generate descriptions
+        List<ApiMethodModel> result = new ArrayList<ApiMethodModel>();
+        for (String signature: signatures) {
+            // remove all type parameters and modifiers
+            signature = signature.replaceAll("<[^>]*>|public|final", "");
+            log.debug("Processing " + signature);
+
+            final Matcher methodMatcher = METHOD_PATTERN.matcher(signature);
+            if (!methodMatcher.matches()) {
+                throw new IllegalArgumentException("Invalid method signature " + signature);
+            }
+
+            final Class<?> resultType = forName(methodMatcher.group(1));
+            final String name = methodMatcher.group(2);
+            final String argSignature = methodMatcher.group(3);
+
+            final List<Argument> arguments = new ArrayList<Argument>();
+
+            List<Class<?>> argTypes = new ArrayList<Class<?>>();
+            final Matcher argsMatcher = ARGS_PATTERN.matcher(argSignature);
+            while (argsMatcher.find()) {
+                final Class<?> type = forName(argsMatcher.group(1));
+                arguments.add(new Argument(argsMatcher.group(2), type));
+                argTypes.add(type);
+            }
+
+            Method method;
+            try {
+                method = proxyType.getMethod(name, argTypes.toArray(new Class<?>[argTypes.size()]));
+            } catch (NoSuchMethodException e) {
+                throw new IllegalArgumentException("Method not found [" + signature + "] in type " + proxyType.getName());
+            }
+            result.add(new ApiMethodModel(name, resultType, arguments, method));
+        }
+
+        Collections.sort(result, new Comparator<ApiMethodModel>() {
+            @Override
+            public int compare(ApiMethodModel model1, ApiMethodModel model2) {
+                final int nameCompare = model1.name.compareTo(model2.name);
+                if (nameCompare != 0) {
+                    return nameCompare;
+                } else {
+
+                    final int nArgs1 = model1.arguments.size();
+                    final int nArgsCompare = nArgs1 - model2.arguments.size();
+                    if (nArgsCompare != 0) {
+                        return nArgsCompare;
+                    } else {
+                        // same number of args, compare arg names, kinda arbitrary to use alphabetized order
+                        for (int i = 0; i < nArgs1; i++) {
+                            final int argCompare = model1.arguments.get(i).name.compareTo(model2.arguments.get(i).name);
+                            if (argCompare != 0) {
+                                return argCompare;
+                            }
+                        }
+                        // duplicate methods???
+                        log.warn("Duplicate methods found [" + model1 + "], [" + model2 + "]");
+                        return 0;
+                    }
+                }
+            }
+        });
+
+        // assign unique names to every method model
+        final Map<String, Integer> dups = new HashMap<String, Integer>();
+        for (ApiMethodModel model : result) {
+            // TODO watch out, this uses default locale to convert to upper case
+            String uniqueName = model.name.toUpperCase();
+            Integer suffix = dups.get(uniqueName);
+            if (suffix == null) {
+                dups.put(uniqueName, 1);
+            } else {
+                dups.put(uniqueName, suffix + 1);
+                StringBuilder builder = new StringBuilder(uniqueName);
+                builder.append("_").append(suffix);
+                uniqueName = builder.toString();
+            }
+            model.uniqueName = uniqueName;
+        }
+        return result;
+    }
+
+    protected Class<?> forName(String className) {
+        return forName(className, classLoader);
+    }
+
+    public static Class<?> forName(String className, ClassLoader classLoader) {
+        Class<?> result;
+        try {
+            // lookup primitive types first
+            result = PRIMITIVE_TYPES.get(className);
+            if (result == null) {
+                result = Class.forName(className, true, classLoader);
+            }
+        } catch (ClassNotFoundException e) {
+            // check if array type
+            if (className.endsWith("[]")) {
+                final int firstDim = className.indexOf('[');
+                final int nDimensions = (className.length() - firstDim) / 2;
+                return Array.newInstance(forName(className.substring(0, firstDim), classLoader), new int[nDimensions]).getClass();
+            }
+            try {
+                // try loading from default Java package java.lang
+                result = Class.forName(JAVA_LANG + className, true, classLoader);
+            } catch (ClassNotFoundException e1) {
+                throw new IllegalArgumentException("Error loading class " + className);
+            }
+        }
+
+        return result;
+    }
+
+    public static final class ApiMethodModel {
+        private final String name;
+        private final Class<?> resultType;
+        private final List<Argument> arguments;
+        private final Method method;
+
+        private String uniqueName;
+
+        protected ApiMethodModel(String name, Class<?> resultType, List<Argument> arguments, Method method) {
+            this.name = name;
+            this.resultType = resultType;
+            this.arguments = arguments;
+            this.method = method;
+        }
+
+        protected ApiMethodModel(String uniqueName, String name, Class<?> resultType, List<Argument> arguments, Method method) {
+            this.name = name;
+            this.uniqueName = uniqueName;
+            this.resultType = resultType;
+            this.arguments = arguments;
+            this.method = method;
+        }
+
+        public String getUniqueName() {
+            return uniqueName;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Class<?> getResultType() {
+            return resultType;
+        }
+
+        public Method getMethod() {
+            return method;
+        }
+
+        public List<Argument> getArguments() {
+            return arguments;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append(resultType.getName()).append(" ");
+            builder.append(name).append("(");
+            for (Argument argument : arguments) {
+                builder.append(argument.getType().getCanonicalName()).append(" ");
+                builder.append(argument.getName()).append(", ");
+            }
+            if (!arguments.isEmpty()) {
+                builder.delete(builder.length() - 2, builder.length());
+            }
+            builder.append(");");
+            return builder.toString();
+        }
+    }
+
+    public static final class Argument {
+        private final String name;
+        private final Class<?> type;
+
+        protected Argument(String name, Class<?> type) {
+            this.name = name;
+            this.type = type;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Class<?> getType() {
+            return type;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append(type.getCanonicalName()).append(" ").append(name);
+            return builder.toString();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodPropertiesHelper.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodPropertiesHelper.java b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodPropertiesHelper.java
new file mode 100644
index 0000000..ea0f442
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/component/ApiMethodPropertiesHelper.java
@@ -0,0 +1,103 @@
+/**
+ * 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.util.component;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.util.IntrospectionSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper class to work with ApiMethod arguments.
+ */
+public final class ApiMethodPropertiesHelper<C> {
+
+    private final Logger LOG = LoggerFactory.getLogger(ApiMethodPropertiesHelper.class);
+
+    // set of field names which are specific to the api, to be excluded from method argument considerations
+    private final Set<String> COMPONENT_CONFIG_FIELDS = new HashSet<String>();
+
+    private final Class<?> componentConfigClass;
+    private final String propertyPrefix;
+
+    public ApiMethodPropertiesHelper(Class<C> componentConfiguration, String propertyPrefix) {
+
+        this.componentConfigClass = componentConfiguration;
+        this.propertyPrefix = propertyPrefix;
+
+        for (Field field : componentConfiguration.getDeclaredFields()) {
+            COMPONENT_CONFIG_FIELDS.add(field.getName());
+        }
+    }
+
+    /**
+     * Gets exchange header properties that start with propertyPrefix.
+     *
+     * @param exchange Camel exchange
+     * @param properties map to collect properties with required prefix
+     */
+    public Map<String, Object> getExchangeProperties(Exchange exchange, Map<String, Object> properties) {
+        final int prefixLength = propertyPrefix.length();
+        int nProperties = 0;
+        for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
+            if (entry.getKey().startsWith(propertyPrefix)) {
+                properties.put(entry.getKey().substring(prefixLength),
+                    entry.getValue());
+                nProperties++;
+            }
+        }
+        LOG.debug("Found {} properties in exchange", nProperties);
+        return properties;
+    }
+
+    public void getEndpointProperties(Object endpointConfiguration,
+                                             Map<String, Object> properties) {
+
+        if (IntrospectionSupport.getProperties(endpointConfiguration, properties, null, false)) {
+            final Set<String> names = properties.keySet();
+            // remove component config properties so we only have endpoint properties
+            names.removeAll(COMPONENT_CONFIG_FIELDS);
+        }
+        if (LOG.isDebugEnabled()) {
+            final Set<String> names = properties.keySet();
+            LOG.debug("Found endpoint properties {}",
+                    names.retainAll(getValidEndpointProperties(endpointConfiguration)));
+        }
+    }
+
+    public Set<String> getEndpointPropertyNames(Object endpointConfiguration) {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        getEndpointProperties(endpointConfiguration, properties);
+        return Collections.unmodifiableSet(properties.keySet());
+    }
+
+    public Set<String> getValidEndpointProperties(Object endpointConfiguration) {
+        Set<String> fields = new HashSet<String>();
+        for (Field field : endpointConfiguration.getClass().getDeclaredFields()) {
+            fields.add(field.getName());
+        }
+        return Collections.unmodifiableSet(fields);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/main/java/org/apache/camel/util/component/ArgumentSubstitutionParser.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/util/component/ArgumentSubstitutionParser.java b/camel-core/src/main/java/org/apache/camel/util/component/ArgumentSubstitutionParser.java
new file mode 100644
index 0000000..06ab253
--- /dev/null
+++ b/camel-core/src/main/java/org/apache/camel/util/component/ArgumentSubstitutionParser.java
@@ -0,0 +1,143 @@
+package org.apache.camel.util.component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Adds support for parameter name substitutions.
+ */
+public class ArgumentSubstitutionParser<T> extends ApiMethodParser<T> {
+
+    private final HashMap<Pattern, Map<Pattern, List<NameReplacement>>> methodMap;
+
+    /**
+     * Create a parser using regular expressions to adapt parameter names.
+     * @param proxyType Proxy class.
+     * @param substitutions an array of <b>ordered</b> Argument adapters.
+     */
+    public ArgumentSubstitutionParser(Class<T> proxyType, Substitution[] substitutions) {
+        super(proxyType);
+        Map<String, Map<String, List<NameReplacement>>> regexMap = new HashMap<String, Map<String, List<NameReplacement>>>();
+
+        for (Substitution tuple : substitutions) {
+            tuple.validate();
+
+            final NameReplacement nameReplacement = new NameReplacement();
+            nameReplacement.replacement = tuple.replacement;
+            if (tuple.argType != null) {
+                nameReplacement.type = forName(tuple.argType);
+            }
+
+            Map<String, List<NameReplacement>> replacementMap = regexMap.get(tuple.method);
+            if (replacementMap == null) {
+                replacementMap = new HashMap<String, List<NameReplacement>>();
+                regexMap.put(tuple.method, replacementMap);
+            }
+            List<NameReplacement> replacements = replacementMap.get(tuple.argName);
+            if (replacements == null) {
+                replacements = new ArrayList<NameReplacement>();
+                replacementMap.put(tuple.argName, replacements);
+            }
+            replacements.add(nameReplacement);
+        }
+
+        // now compile the patterns, all this because Pattern doesn't override equals()!!!
+        this.methodMap = new LinkedHashMap<Pattern, Map<Pattern, List<NameReplacement>>>();
+        for (Map.Entry<String, Map<String, List<NameReplacement>>> method : regexMap.entrySet()) {
+            Map<Pattern, List<NameReplacement>> argMap = new LinkedHashMap<Pattern, List<NameReplacement>>();
+            for (Map.Entry<String, List<NameReplacement>> arg : method.getValue().entrySet()) {
+                argMap.put(Pattern.compile(arg.getKey()), arg.getValue());
+            }
+            methodMap.put(Pattern.compile(method.getKey()), argMap);
+        }
+    }
+
+    @Override
+    public List<ApiMethodModel> parse() {
+        final List<ApiMethodModel> result = new ArrayList<ApiMethodModel>();
+
+        for (ApiMethodModel model : super.parse()) {
+            // look for method name matches
+            for (Map.Entry<Pattern, Map<Pattern, List<NameReplacement>>> methodEntry : methodMap.entrySet()) {
+                if (methodEntry.getKey().matcher(model.getName()).matches()) {
+
+                    // look for arg name matches
+                    final List<Argument> updatedArguments = new ArrayList<Argument>();
+                    final Map<Pattern, List<NameReplacement>> argMap = methodEntry.getValue();
+                    for (Argument argument : model.getArguments()) {
+                        for (Map.Entry<Pattern, List<NameReplacement>> argEntry : argMap.entrySet()) {
+                            final Matcher matcher = argEntry.getKey().matcher(argument.getName());
+                            if (matcher.find()) {
+                                final List<NameReplacement> adapters = argEntry.getValue();
+                                for (NameReplacement adapter : adapters) {
+                                    if (adapter.type == null || adapter.type.isAssignableFrom(argument.getType())) {
+                                        argument = new Argument(matcher.replaceAll(adapter.replacement), argument.getType());
+                                    }
+                                }
+                            }
+                        }
+
+                        updatedArguments.add(argument);
+                    }
+
+                    model = new ApiMethodModel(model.getName(), model.getUniqueName(), model.getResultType(),
+                            updatedArguments, model.getMethod());
+                }
+            }
+
+            result.add(model);
+        }
+
+        return result;
+    }
+
+    public static class Substitution {
+
+        private String method;
+        private String argName;
+        private String argType;
+        private String replacement;
+
+        /**
+         * Creates a substitution for all argument types.
+         * @param method regex to match method name
+         * @param argName regex to match argument name
+         * @param replacement replacement text for argument name
+         */
+        public Substitution(String method, String argName, String replacement) {
+            this.method = method;
+            this.argName = argName;
+            this.replacement = replacement;
+        }
+
+        /**
+         * Creates a substitution for a specific argument type.
+         * @param method regex to match method name
+         * @param argName regex to match argument name
+         * @param argType argument type as String
+         * @param replacement replacement text for argument name
+         */
+        public Substitution(String method, String argName, String argType, String replacement) {
+            this.method = method;
+            this.argName = argName;
+            this.argType = argType;
+            this.replacement = replacement;
+        }
+
+        public void validate() {
+            if (method == null || argName == null || replacement == null) {
+                throw new IllegalArgumentException("Properties method, argName and replacement MUST be provided");
+            }
+        }
+    }
+
+    private static class NameReplacement {
+        private String replacement;
+        private Class<?> type;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/test/java/org/apache/camel/util/component/ApiMethodHelperTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/util/component/ApiMethodHelperTest.java b/camel-core/src/test/java/org/apache/camel/util/component/ApiMethodHelperTest.java
new file mode 100644
index 0000000..f41bc76
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/util/component/ApiMethodHelperTest.java
@@ -0,0 +1,169 @@
+/**
+ * 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.util.component;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class ApiMethodHelperTest {
+
+    private static TestMethod[] sayHis = new TestMethod[] { TestMethod.SAYHI, TestMethod.SAYHI_1};
+    private static ApiMethodHelper<TestMethod> apiMethodHelper;
+
+    static {
+        final HashMap<String, String> aliases = new HashMap<String, String>();
+        aliases.put("say(.*)", "$1");
+        apiMethodHelper = new ApiMethodHelper<TestMethod>(TestMethod.class, aliases);
+    }
+
+    @Test
+    public void testGetCandidateMethods() {
+        List<TestMethod> methods = apiMethodHelper.getCandidateMethods("sayHi");
+        assertEquals("Can't find sayHi(*)", 2, methods.size());
+
+        methods = apiMethodHelper.getCandidateMethods("hi");
+        assertEquals("Can't find sayHi(name)", 2, methods.size());
+
+        methods = apiMethodHelper.getCandidateMethods("hi", "name");
+        assertEquals("Can't find sayHi(name)", 1, methods.size());
+
+        methods = apiMethodHelper.getCandidateMethods("greetMe");
+        assertEquals("Can't find greetMe(name)", 1, methods.size());
+
+        methods = apiMethodHelper.getCandidateMethods("greetUs", "name1");
+        assertEquals("Can't find greetUs(name1, name2)", 1, methods.size());
+    }
+
+    @Test
+    public void testFilterMethods() {
+        List<TestMethod> methods = apiMethodHelper.filterMethods(Arrays.asList(sayHis), ApiMethodHelper.MatchType.EXACT);
+        assertEquals("Exact match failed for sayHi()", 1, methods.size());
+        assertEquals("Exact match failed for sayHi()", TestMethod.SAYHI, methods.get(0));
+
+        methods = apiMethodHelper.filterMethods(Arrays.asList(sayHis), ApiMethodHelper.MatchType.SUBSET);
+        assertEquals("Subset match failed for sayHi(*)", 2, methods.size());
+
+        methods = apiMethodHelper.filterMethods(Arrays.asList(sayHis), ApiMethodHelper.MatchType.SUBSET, "name");
+        assertEquals("Subset match failed for sayHi(name)", 1, methods.size());
+        assertEquals("Exact match failed for sayHi()", TestMethod.SAYHI_1, methods.get(0));
+
+        methods = apiMethodHelper.filterMethods(Arrays.asList(sayHis), ApiMethodHelper.MatchType.SUPER_SET, "name");
+        assertEquals("Super set match failed for sayHi(name)", 1, methods.size());
+        assertEquals("Exact match failed for sayHi()", TestMethod.SAYHI_1, methods.get(0));
+
+        methods = apiMethodHelper.filterMethods(Arrays.asList(TestMethod.values()), ApiMethodHelper.MatchType.SUPER_SET, "name");
+        assertEquals("Super set match failed for sayHi(name)", 2, methods.size());
+    }
+
+    @Test
+    public void testGetArguments() {
+        assertEquals("GetArguments failed for hi", 2, apiMethodHelper.getArguments("hi").size());
+        assertEquals("GetArguments failed for greetMe", 2, apiMethodHelper.getArguments("greetMe").size());
+        assertEquals("GetArguments failed for greetUs", 4, apiMethodHelper.getArguments("greetUs").size());
+    }
+
+    @Test
+    public void testGetMissingProperties() throws Exception {
+        assertEquals("Missing properties for hi", 1,
+                apiMethodHelper.getMissingProperties("hi", new HashSet<String>()).size());
+
+        final HashSet<String> argNames = new HashSet<String>();
+        argNames.add("name");
+        assertEquals("Missing properties for greetMe", 0,
+                apiMethodHelper.getMissingProperties("greetMe", argNames).size());
+
+        argNames.clear();
+        argNames.add("name1");
+        assertEquals("Missing properties for greetMe", 1,
+                apiMethodHelper.getMissingProperties("greetUs", argNames).size());
+    }
+
+    @Test
+    public void testAllArguments() throws Exception {
+        assertEquals("Get all arguments", 6, apiMethodHelper.allArguments().size());
+    }
+
+    @Test
+    public void testGetType() throws Exception {
+        assertEquals("Get type name", String.class, apiMethodHelper.getType("name"));
+        assertEquals("Get type name1", String.class, apiMethodHelper.getType("name1"));
+        assertEquals("Get type name2", String.class, apiMethodHelper.getType("name2"));
+    }
+
+    @Test
+    public void testGetHighestPriorityMethod() throws Exception {
+        assertEquals("Get highest priority method",
+                TestMethod.SAYHI_1, apiMethodHelper.getHighestPriorityMethod(Arrays.asList(sayHis)));
+    }
+
+    @Test
+    public void testInvokeMethod() throws Exception {
+        TestProxy proxy = new TestProxy();
+        assertEquals("sayHi()", "Hello!", apiMethodHelper.invokeMethod(proxy, TestMethod.SAYHI, Collections.EMPTY_MAP));
+
+        final HashMap<String, Object> properties = new HashMap<String, Object>();
+        properties.put("name", "Dave");
+
+        assertEquals("sayHi(name)", "Hello Dave", apiMethodHelper.invokeMethod(proxy, TestMethod.SAYHI_1, properties));
+        assertEquals("greetMe(name)", "Greetings Dave", apiMethodHelper.invokeMethod(proxy, TestMethod.GREETME, properties));
+
+        properties.clear();
+        properties.put("name1", "Dave");
+        properties.put("name2", "Frank");
+        assertEquals("greetUs(name1, name2)", "Greetings Dave, Frank", apiMethodHelper.invokeMethod(proxy, TestMethod.GREETUS, properties));
+    }
+
+    static enum TestMethod implements ApiMethod {
+
+        SAYHI(String.class, "sayHi"),
+        SAYHI_1(String.class, "sayHi", String.class, "name"),
+        GREETME(String.class, "greetMe", String.class, "name"),
+        GREETUS(String.class, "greetUs", String.class, "name1", String.class, "name2"),
+        GREETALL(String.class, "greetAll", new String[0].getClass(), "names"),
+        GREETALL_1(String.class, "greetAll", List.class, "nameList"),
+        GREETTIMES(new String[0].getClass(), "greetTimes", String.class, "name", int.class, "times");
+
+        private final ApiMethod apiMethod;
+
+        private TestMethod(Class<?> resultType, String name, Object... args) {
+            this.apiMethod = new ApiMethodImpl(TestProxy.class, resultType, name, args);
+        }
+
+        @Override
+        public String getName() { return apiMethod.getName(); }
+
+        @Override
+        public Class<?> getResultType() { return apiMethod.getResultType(); }
+
+        @Override
+        public List<String> getArgNames() { return apiMethod.getArgNames(); }
+
+        @Override
+        public List<Class<?>> getArgTypes() { return apiMethod.getArgTypes(); }
+
+        @Override
+        public Method getMethod() { return apiMethod.getMethod(); }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/test/java/org/apache/camel/util/component/ApiMethodPropertiesHelperTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/util/component/ApiMethodPropertiesHelperTest.java b/camel-core/src/test/java/org/apache/camel/util/component/ApiMethodPropertiesHelperTest.java
new file mode 100644
index 0000000..c711c5f
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/util/component/ApiMethodPropertiesHelperTest.java
@@ -0,0 +1,122 @@
+/**
+ * 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.util.component;
+
+import java.util.HashMap;
+
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.impl.DefaultExchange;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class ApiMethodPropertiesHelperTest {
+
+    private static final String TEST_PREFIX = "TestComponent.";
+
+    private static final String PROPERTY_1 = TEST_PREFIX + "property1";
+    private static final String PROPERTY_2 = TEST_PREFIX + "property2";
+    private static final String PROPERTY_3 = TEST_PREFIX + "property3";
+    private static final String PROPERTY_4 = TEST_PREFIX + "property4";
+
+    private static final String VALUE_1 = "value1";
+    private static final long VALUE_2 = 2;
+    private static final String VALUE_3 = "value3";
+    private static final String VALUE_4 = "true";
+
+    private static ApiMethodPropertiesHelper propertiesHelper =
+            new ApiMethodPropertiesHelper(TestComponentConfiguration.class, TEST_PREFIX);
+
+    @Test
+    public void testGetExchangeProperties() throws Exception {
+        final HashMap<String, Object> properties = new HashMap<String, Object>();
+        final DefaultExchange exchange = new DefaultExchange(new MockEndpoint());
+        exchange.getIn().setHeader(PROPERTY_1, VALUE_1);
+        exchange.getIn().setHeader(PROPERTY_2, VALUE_2);
+        exchange.getIn().setHeader(PROPERTY_3, VALUE_3);
+        exchange.getIn().setHeader(PROPERTY_4, VALUE_4);
+        propertiesHelper.getExchangeProperties(exchange, properties);
+        assertEquals(4, properties.size());
+    }
+
+    @Test
+    public void testGetEndpointProperties() throws Exception {
+        final HashMap<String, Object> properties = new HashMap<String, Object>();
+        final TestEndpointConfiguration endpointConfiguration = new TestEndpointConfiguration();
+        endpointConfiguration.setProperty1(VALUE_1);
+        endpointConfiguration.setProperty2(VALUE_2);
+        endpointConfiguration.setProperty3(VALUE_3);
+        endpointConfiguration.setProperty4(Boolean.valueOf(VALUE_4));
+        propertiesHelper.getEndpointProperties(endpointConfiguration, properties);
+        assertEquals(2, properties.size());
+    }
+
+    @Test
+    public void testGetEndpointPropertyNames() throws Exception {
+        final TestEndpointConfiguration endpointConfiguration = new TestEndpointConfiguration();
+        endpointConfiguration.setProperty1(VALUE_1);
+        endpointConfiguration.setProperty4(Boolean.valueOf(VALUE_4));
+        assertEquals(1, propertiesHelper.getEndpointPropertyNames(endpointConfiguration).size());
+    }
+
+    @Test
+    public void testGetValidEndpointProperties() throws Exception {
+        assertEquals(2, propertiesHelper.getValidEndpointProperties(new TestEndpointConfiguration()).size());
+    }
+
+    private static class TestComponentConfiguration {
+        private String property1;
+        private Long property2;
+
+        public String getProperty1() {
+            return property1;
+        }
+
+        public void setProperty1(String property1) {
+            this.property1 = property1;
+        }
+
+        public long getProperty2() {
+            return property2;
+        }
+
+        public void setProperty2(Long property2) {
+            this.property2 = property2;
+        }
+    }
+
+    private static class TestEndpointConfiguration extends TestComponentConfiguration {
+        private String property3;
+        private Boolean property4;
+
+        public String getProperty3() {
+            return property3;
+        }
+
+        public void setProperty3(String property3) {
+            this.property3 = property3;
+        }
+
+        public Boolean getProperty4() {
+            return property4;
+        }
+
+        public void setProperty4(Boolean property4) {
+            this.property4 = property4;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/test/java/org/apache/camel/util/component/ArgumentSubstitutionParserTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/util/component/ArgumentSubstitutionParserTest.java b/camel-core/src/test/java/org/apache/camel/util/component/ArgumentSubstitutionParserTest.java
new file mode 100644
index 0000000..ce0153d
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/util/component/ArgumentSubstitutionParserTest.java
@@ -0,0 +1,52 @@
+package org.apache.camel.util.component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+import static org.apache.camel.util.component.ArgumentSubstitutionParser.*;
+import static org.junit.Assert.assertEquals;
+
+public class ArgumentSubstitutionParserTest {
+
+    private static final String PERSON = "person";
+
+    @Test
+    public void testParse() throws Exception {
+
+        final Substitution[] adapters = new Substitution[3];
+        adapters[0] = new Substitution(".+", "name", PERSON);
+        adapters[1] = new Substitution("greet.+", "person([0-9]+)", "astronaut$1");
+        adapters[2] = new Substitution(".+", "(.+)", "java.util.List", "$1List");
+
+        final ApiMethodParser<TestProxy> parser = new ArgumentSubstitutionParser<TestProxy>(TestProxy.class, adapters);
+
+        final ArrayList<String> signatures = new ArrayList<String>();
+        signatures.add("public String sayHi();");
+        signatures.add("public String sayHi(final String name);");
+        signatures.add("public final String greetMe(final String name);");
+        signatures.add("public final String greetUs(final String name1, String name2);");
+        signatures.add("public final String greetAll(String[] names);");
+        signatures.add("public final String greetAll(java.util.List<String> names);");
+        signatures.add("public final String[] greetTimes(String name, int times);");
+        parser.setSignatures(signatures);
+
+        final List<ApiMethodParser.ApiMethodModel> methodModels = parser.parse();
+        assertEquals(7, methodModels.size());
+
+        final ApiMethodParser.ApiMethodModel sayHi1 = methodModels.get(6);
+        assertEquals(PERSON, sayHi1.getArguments().get(0).getName());
+        assertEquals("SAYHI_1", sayHi1.getUniqueName());
+
+        final ApiMethodParser.ApiMethodModel greetMe = methodModels.get(2);
+        assertEquals(PERSON, greetMe.getArguments().get(0).getName());
+
+        final ApiMethodParser.ApiMethodModel greetUs = methodModels.get(4);
+        assertEquals("astronaut1", greetUs.getArguments().get(0).getName());
+        assertEquals("astronaut2", greetUs.getArguments().get(1).getName());
+
+        final ApiMethodParser.ApiMethodModel greetAll = methodModels.get(1);
+        assertEquals("personsList", greetAll.getArguments().get(0).getName());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/10a0b615/camel-core/src/test/java/org/apache/camel/util/component/TestProxy.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/util/component/TestProxy.java b/camel-core/src/test/java/org/apache/camel/util/component/TestProxy.java
new file mode 100644
index 0000000..d707eb8
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/util/component/TestProxy.java
@@ -0,0 +1,64 @@
+/**
+ * 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.util.component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class TestProxy {
+    public String sayHi() {
+        return "Hello!";
+    }
+
+    public String sayHi(final String name) {
+        return "Hello " + name;
+    }
+
+    public final String greetMe(final String name) {
+        return "Greetings " + name;
+    }
+
+    public final String greetUs(final String name1, String name2) {
+        return "Greetings " + name1 + ", " + name2;
+    }
+
+    public final String greetAll(final String[] names) {
+        StringBuilder builder = new StringBuilder("Greetings ");
+        for (String name : names) {
+            builder.append(name).append(", ");
+        }
+        builder.delete(builder.length() - 2, builder.length());
+        return builder.toString();
+    }
+
+    public final String greetAll(List<String> names) {
+        StringBuilder builder = new StringBuilder("Greetings ");
+        for (String name : names) {
+            builder.append(name).append(", ");
+        }
+        builder.delete(builder.length() - 2, builder.length());
+        return builder.toString();
+    }
+
+    public final String[] greetTimes(String name, int times) {
+        final List<String> result = new ArrayList<String>();
+        for (int i = 0; i < times; i++) {
+            result.add("Greetings " + name);
+        }
+        return result.toArray(new String[result.size()]);
+    }
+}