/** * 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 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; /** * Allows a user to customize a field mapping using a POJO that is not * 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) { try { return mapCustom(sourceFieldValue, sourceClass); } finally { done(); } } 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, Class<?> sourceClass) { // The converter parameter is stored in a thread local variable, so // we need to parse the parameter on each invocation // 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; try { Class<?> customClass = resolver.resolveMandatoryClass(className); customObj = customClass.newInstance(); // If a specific mapping operation has been supplied use that if (operation != null && prmTypesAndValues != null) { method = selectMethod(customClass, operation, sourceClass, prmTypesAndValues); } else if (operation != null) { method = customClass.getMethod(operation, sourceClass); } else { method = selectMethod(customClass, sourceClass); } } catch (Exception e) { throw new RuntimeException("Failed to load custom function", e); } // Verify that we found a matching method if (method == null) { throw new RuntimeException("No eligible custom function methods in " + className); } // Invoke the custom mapping method try { 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, Class<?> sourceClass) { Method method = null; for (Method m : customClass.getDeclaredMethods()) { if (m.getReturnType() != null && m.getParameterTypes().length == 1 && m.getParameterTypes()[0].isAssignableFrom(sourceClass)) { 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, Class<?> sourceClass, 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(sourceClass)) { 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; } }