Repository: camel Updated Branches: refs/heads/master e1289b864 -> 803d14888
FUSETOOLS-1347: Add built-in functions for transformation Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/c53a5c81 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/c53a5c81 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/c53a5c81 Branch: refs/heads/master Commit: c53a5c814fc235cd1c47f6abd0e3bfc2b9356ac4 Parents: e1289b8 Author: John Verhaeg <john.verh...@gmail.com> Authored: Tue Aug 25 22:48:57 2015 -0500 Committer: Jonathan Anstey <jans...@gmail.com> Committed: Tue Sep 1 09:32:56 2015 -0230 ---------------------------------------------------------------------- .../camel/component/dozer/CustomMapper.java | 204 +++++++++++++++---- .../camel/component/dozer/DozerProducer.java | 6 + .../dozer/CustomMapperParametersTest.java | 62 ++++++ 3 files changed, 231 insertions(+), 41 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/c53a5c81/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/CustomMapper.java ---------------------------------------------------------------------- diff --git a/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/CustomMapper.java b/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/CustomMapper.java index 331de0c..0807160 100644 --- a/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/CustomMapper.java +++ b/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/CustomMapper.java @@ -16,7 +16,12 @@ */ package org.apache.camel.component.dozer; +import java.lang.reflect.Array; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; import org.apache.camel.spi.ClassResolver; @@ -25,71 +30,188 @@ import org.apache.camel.spi.ClassResolver; * required to extend/implement Dozer-specific classes. */ public class CustomMapper extends BaseConverter { - + private ClassResolver resolver; - + public CustomMapper(ClassResolver resolver) { this.resolver = resolver; } - + @Override - public Object convert(Object existingDestinationFieldValue, - Object sourceFieldValue, - Class<?> destinationClass, - Class<?> sourceClass) { + public Object convert(Object existingDestinationFieldValue, + Object sourceFieldValue, + Class<?> destinationClass, + Class<?> sourceClass) { try { return mapCustom(sourceFieldValue); } finally { done(); } } - - Method selectMethod(Class<?> customClass, Object fromType) { - Method method = null; - for (Method m : customClass.getDeclaredMethods()) { - if (m.getReturnType() != null - && m.getParameterTypes().length == 1 - && m.getParameterTypes()[0].isAssignableFrom(fromType.getClass())) { - method = m; - break; - } - } - return method; + + private Object invokeFunction(Method method, + Object customObj, + Object source, + String[][] parameters) throws Exception { + Class<?>[] prmTypes = method.getParameterTypes(); + Object[] methodPrms = new Object[prmTypes.length]; + methodPrms[0] = source; + for (int parameterNdx = 0, methodPrmNdx = 1; parameterNdx < parameters.length; parameterNdx++, methodPrmNdx++) { + if (method.isVarArgs() && methodPrmNdx == prmTypes.length - 1) { + Object array = Array.newInstance(prmTypes[methodPrmNdx].getComponentType(), parameters.length - parameterNdx); + for (int arrayNdx = 0; parameterNdx < parameters.length; parameterNdx++, arrayNdx++) { + String[] parts = parameters[parameterNdx]; + Array.set(array, arrayNdx, resolver.resolveClass(parts[0]).getConstructor(String.class).newInstance(parts[1])); + } + methodPrms[methodPrmNdx] = array; + } else { + String[] parts = parameters[parameterNdx]; + methodPrms[methodPrmNdx] = resolver.resolveClass(parts[0]).getConstructor(String.class).newInstance(parts[1]);; + } + } + return method.invoke(customObj, methodPrms); } Object mapCustom(Object source) { - Object customMapObj; - Method mapMethod; - - // The converter parameter is stored in a thread local variable, so + // The converter parameter is stored in a thread local variable, so // we need to parse the parameter on each invocation - String[] params = getParameter().split(","); - String className = params[0]; - String operation = params.length > 1 ? params[1] : null; - + // ex: custom-converter-param="org.example.MyMapping,map" + // className = org.example.MyMapping + // operation = map + String[] prms = getParameter().split(","); + String className = prms[0]; + String operation = prms.length > 1 ? prms[1] : null; + + // now attempt to process any additional parameters passed along + // ex: custom-converter-param="org.example.MyMapping,substring,java.lang.Integer=3,java.lang.Integer=10" + // className = org.example.MyMapping + // operation = substring + // parameters = ["java.lang.Integer=3","java.lang.Integer=10"] + String[][] prmTypesAndValues; + if (prms.length > 2) { + // Break parameters down into types and values + prmTypesAndValues = new String[prms.length - 2][2]; + for (int ndx = 0; ndx < prmTypesAndValues.length; ndx++) { + String prm = prms[ndx + 2]; + String[] parts = prm.split("="); + if (parts.length != 2) throw new RuntimeException("Value missing for parameter " + prm); + prmTypesAndValues[ndx][0] = parts[0]; + prmTypesAndValues[ndx][1] = parts[1]; + } + } else prmTypesAndValues = null; + + Object customObj; + Method method = null; try { Class<?> customClass = resolver.resolveClass(className); - customMapObj = customClass.newInstance(); + customObj = customClass.newInstance(); + // If a specific mapping operation has been supplied use that - if (operation != null) { - mapMethod = customClass.getMethod(operation, source.getClass()); + if (operation != null && prmTypesAndValues != null) { + method = selectMethod(customClass, operation, source, prmTypesAndValues); + } else if (operation != null) { + method = customClass.getMethod(operation, source.getClass()); } else { - mapMethod = selectMethod(customClass, source); + method = selectMethod(customClass, source); } - } catch (Exception cnfEx) { - throw new RuntimeException("Failed to load custom mapping", cnfEx); + } catch (Exception e) { + throw new RuntimeException("Failed to load custom function", e); } - + // Verify that we found a matching method - if (mapMethod == null) { - throw new RuntimeException("No eligible custom mapping methods in " + className); + if (method == null) { + throw new RuntimeException("No eligible custom function methods in " + className); } - + // Invoke the custom mapping method try { - return mapMethod.invoke(customMapObj, source); - } catch (Exception ex) { - throw new RuntimeException("Error while invoking custom mapping", ex); + if (prmTypesAndValues != null) { + return invokeFunction(method, customObj, source, prmTypesAndValues); + } else { + return method.invoke(customObj, source); + } + } catch (Exception e) { + throw new RuntimeException("Error while invoking custom function", e); } } -} + + private boolean parametersMatchParameterList(Class<?>[] prmTypes, + String[][] parameters) { + int ndx = 0; + while (ndx < prmTypes.length) { + Class<?> prmType = prmTypes[ndx]; + if (ndx >= parameters.length) return ndx == prmTypes.length - 1 && prmType.isArray(); + if (ndx == prmTypes.length - 1 && prmType.isArray()) { // Assume this only occurs for functions with var args + Class<?> varArgClass = prmType.getComponentType(); + while (ndx < parameters.length) { + Class<?> prmClass = resolver.resolveClass(parameters[ndx][0]); + if (!varArgClass.isAssignableFrom(prmClass)) return false; + ndx++; + } + } else { + Class<?> prmClass = resolver.resolveClass(parameters[ndx][0]); + if (!prmTypes[ndx].isAssignableFrom(prmClass)) return false; + } + ndx++; + } + return true; + } + + Method selectMethod(Class<?> customClass, + Object source) { + Method method = null; + for (Method m : customClass.getDeclaredMethods()) { + if (m.getReturnType() != null + && m.getParameterTypes().length == 1 + && m.getParameterTypes()[0].isAssignableFrom(source.getClass())) { + method = m; + break; + } + } + return method; + } + + // Assumes source is a separate parameter in method even if it has var args and that there are no + // ambiguous calls based upon number and types of parameters + private Method selectMethod(Class<?> customClass, + String operation, + Object source, + String[][] parameters) { + // Create list of potential methods + List<Method> methods = new ArrayList<>(); + for (Method method : customClass.getDeclaredMethods()) { + methods.add(method); + } + + // Remove methods that are not applicable + for (Iterator<Method> iter = methods.iterator(); iter.hasNext();) { + Method method = iter.next(); + Class<?>[] prmTypes = method.getParameterTypes(); + if (!method.getName().equals(operation) + || method.getReturnType() == null + || !prmTypes[0].isAssignableFrom(source.getClass())) { + iter.remove(); + continue; + } + prmTypes = Arrays.copyOfRange(prmTypes, 1, prmTypes.length); // Remove source from type list + if (!method.isVarArgs() && prmTypes.length != parameters.length) { + iter.remove(); + continue; + } + if (!parametersMatchParameterList(prmTypes, parameters)) { + iter.remove(); + continue; + } + } + + // If more than one method is applicable, return the method whose prm list exactly matches the parameters + // if possible + if (methods.size() > 1) { + for (Method method : methods) { + if (!method.isVarArgs()) return method; + } + } + + return methods.size() > 0 ? methods.get(0) : null; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/c53a5c81/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/DozerProducer.java ---------------------------------------------------------------------- diff --git a/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/DozerProducer.java b/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/DozerProducer.java index b7b12c8..5ae109c 100644 --- a/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/DozerProducer.java +++ b/components/camel-dozer/src/main/java/org/apache/camel/component/dozer/DozerProducer.java @@ -53,6 +53,9 @@ public class DozerProducer extends DefaultProducer { if (unmarshalId != null) { LOG.debug("Unmarshalling input data using data format '{}'.", unmarshalId); resolveUnmarshaller(exchange, unmarshalId).process(exchange); + if (exchange.getException() != null) { + throw exchange.getException(); + } } // Load the target model class @@ -95,6 +98,9 @@ public class DozerProducer extends DefaultProducer { if (marshalId != null) { LOG.debug("Marshalling output data using data format '{}'.", marshalId); resolveMarshaller(exchange, marshalId).process(exchange); + if (exchange.getException() != null) { + throw exchange.getException(); + } } } http://git-wip-us.apache.org/repos/asf/camel/blob/c53a5c81/components/camel-dozer/src/test/java/org/apache/camel/component/dozer/CustomMapperParametersTest.java ---------------------------------------------------------------------- diff --git a/components/camel-dozer/src/test/java/org/apache/camel/component/dozer/CustomMapperParametersTest.java b/components/camel-dozer/src/test/java/org/apache/camel/component/dozer/CustomMapperParametersTest.java new file mode 100644 index 0000000..da3daec --- /dev/null +++ b/components/camel-dozer/src/test/java/org/apache/camel/component/dozer/CustomMapperParametersTest.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.dozer; + +import org.apache.camel.impl.DefaultClassResolver; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class CustomMapperParametersTest { + + private CustomMapper customMapper; + + @Before + public void setup() { + customMapper = new CustomMapper(new DefaultClassResolver()); + } + + @Test + public void shouldExecuteCustomFunctionWithArguments() throws Exception { + customMapper.setParameter(MapperWithMultiParmMethod.class.getName() + ",test,java.lang.Integer=12,java.lang.Integer=20"); + Object result = customMapper.mapCustom("JeremiahWasABullfrog"); + Assert.assertEquals("Bullfrog", result); + } + + @Test + public void shouldExecuteCustomFunctionWithVariableArguments() throws Exception { + customMapper.setParameter(MapperWithMultiParmMethod.class.getName() + ",add,java.lang.Integer=12,java.lang.Integer=20"); + Object result = customMapper.mapCustom("JeremiahWasABullfrog"); + Assert.assertEquals(32L, result); + } +} + +class MapperWithMultiParmMethod { + + public Object add(String source, Integer... operands) { + long sum = 0L; + for (Integer operand : operands) { + sum += operand; + } + return sum; + } + + public Object test(String source, Integer beginindex, Integer endindex) { + return source.substring(beginindex.intValue(), endindex.intValue()); + } +} +