All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.atlasmap.core.DefaultAtlasConversionService Maven / Gradle / Ivy

/**
 * Copyright (C) 2017 Red Hat, Inc.
 * 

* Licensed 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 io.atlasmap.core; import static java.util.Objects.hash; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.atlasmap.api.AtlasConversionException; import io.atlasmap.spi.AtlasConverter; import io.atlasmap.spi.AtlasConversionInfo; import io.atlasmap.spi.AtlasConversionService; import io.atlasmap.v2.FieldType; public class DefaultAtlasConversionService implements AtlasConversionService { private static final Logger LOG = LoggerFactory.getLogger(DefaultAtlasConversionService.class); private static final Set PRIMITIVE_CLASSNAMES = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("boolean", "byte", "char", "double", "float", "int", "long", "short"))); private static final Set PRIMITIVE_FIELDTYPES = Collections.unmodifiableSet(new HashSet<>( Arrays.asList(FieldType.BOOLEAN, FieldType.BYTE, FieldType.CHAR, FieldType.DECIMAL, FieldType.DOUBLE, FieldType.FLOAT, FieldType.INTEGER, FieldType.LONG, FieldType.SHORT, FieldType.STRING))); private static final Set BOXED_PRIMITIVE_CLASSNAMES = Collections.unmodifiableSet(new HashSet<>( Arrays.asList("java.lang.Boolean", "java.lang.Byte", "java.lang.Character", "java.lang.Double", "java.lang.Float", "java.lang.Integer", "java.lang.Long", "java.lang.Short", "java.lang.String"))); private static DefaultAtlasConversionService instance = null; private Map converterMethods = null; private Map customConverterMethods = null; // Used as the lookup key in the converter methods map private class ConverterKey { private String sourceClassName; private String targetClassName; public ConverterKey(String sourceClassName, String targetClassName) { this.sourceClassName = sourceClassName; this.targetClassName = targetClassName; } @Override public boolean equals(Object obj) { if (obj != null && obj instanceof ConverterKey) { ConverterKey s = (ConverterKey) obj; return sourceClassName.equals(s.sourceClassName) && targetClassName.equals(s.targetClassName); } return false; } @Override public int hashCode() { return hash(sourceClassName, targetClassName); } } // used to hold converter and method for future reflection use private class ConverterMethodHolder { private AtlasConverter converter; private Method method; private boolean staticMethod; private boolean containsFormat; public ConverterMethodHolder(AtlasConverter converter, Method method, boolean staticMethod, boolean containsFormat) { this.converter = converter; this.method = method; this.staticMethod = staticMethod; this.containsFormat = containsFormat; } public AtlasConverter getConverter() { return converter; } } private DefaultAtlasConversionService() { } public static DefaultAtlasConversionService getInstance() { if (instance == null) { synchronized (DefaultAtlasConversionService.class) { if (instance == null) { instance = new DefaultAtlasConversionService(); instance.init(); } } } return instance; } public static Set listPrimitiveClassNames() { return PRIMITIVE_CLASSNAMES; } @Override public Optional> findMatchingConverter(FieldType source, FieldType target) { // get the default types Class sourceClass = classFromFieldType(source); Class targetClass = classFromFieldType(target); if (sourceClass != null && targetClass != null) { return findMatchingConverter(sourceClass.getCanonicalName(), targetClass.getCanonicalName()); } return Optional.empty(); } @Override public Optional> findMatchingConverter(String sourceClassName, String targetClassName) { ConverterKey converterKey = new ConverterKey(sourceClassName, targetClassName); if (customConverterMethods.containsKey(converterKey)) { return Optional.of(customConverterMethods.get(converterKey).getConverter()); } else if (converterMethods.containsKey(converterKey)) { return Optional.of(converterMethods.get(converterKey).getConverter()); } else { return Optional.empty(); } } private void init() { loadConverters(); } @SuppressWarnings("rawtypes") private void loadConverters() { ClassLoader classLoader = this.getClass().getClassLoader(); final ServiceLoader converterServiceLoader = ServiceLoader.load(AtlasConverter.class, classLoader); final ServiceLoader compat = ServiceLoader.load(io.atlasmap.api.AtlasConverter.class, classLoader); // used to load up methods first; Map methodsLoadMap = new LinkedHashMap<>(); Map customMethodsLoadMap = new LinkedHashMap<>(); converterServiceLoader.forEach(atlasConverter -> loadConverterMethod(atlasConverter, methodsLoadMap, customMethodsLoadMap)); compat.forEach(atlasConverter -> loadConverterMethod(atlasConverter, methodsLoadMap, customMethodsLoadMap)); if (!methodsLoadMap.isEmpty()) { converterMethods = Collections.unmodifiableMap(methodsLoadMap); } if (!methodsLoadMap.isEmpty()) { customConverterMethods = Collections.unmodifiableMap(customMethodsLoadMap); } } private void loadConverterMethod(AtlasConverter atlasConverter, Map methodsLoadMap, Map customMethodsLoadMap) { if (LOG.isDebugEnabled()) { LOG.debug("Loading converter : " + atlasConverter.getClass().getCanonicalName()); } boolean inbuiltConverter = atlasConverter.getClass().getPackage().getName().startsWith("io.atlasmap"); Class klass = atlasConverter.getClass(); // collect all the specific conversion methods on the class while (klass != Object.class) { final List allMethods = new ArrayList<>(Arrays.asList(klass.getDeclaredMethods())); for (final Method method : allMethods) { // we filter out methods which aren't annotated @AtlasconversionInfo and have to // also filter out methods which are synthetic methods to avoid duplicates if (method.isAnnotationPresent(AtlasConversionInfo.class) && method.getParameters().length > 0 && !method.isSynthetic()) { String sourceClassName = method.getParameters()[0].getType().getCanonicalName(); ConverterKey coordinate = new ConverterKey(sourceClassName, method.getReturnType().getCanonicalName()); // if the method has three arguments and the last two as strings then they used // as the format attributes boolean containsFormat = false; if (method.getParameters().length == 3 && method.getParameters()[1].getType() == String.class && method.getParameters()[2].getType() == String.class) { containsFormat = true; } boolean staticMethod = Modifier.isStatic(method.getModifiers()); ConverterMethodHolder methodHolder = new ConverterMethodHolder(atlasConverter, method, staticMethod, containsFormat); if (inbuiltConverter) { if (!methodsLoadMap.containsKey(coordinate)) { methodsLoadMap.put(coordinate, methodHolder); } else { LOG.warn("Converter between " + sourceClassName + " and " + method.getReturnType().getCanonicalName() + " aleady exists."); } } else { if (!customMethodsLoadMap.containsKey(coordinate)) { customMethodsLoadMap.put(coordinate, methodHolder); } else { LOG.warn("Custom converter between " + sourceClassName + " and " + method.getReturnType().getCanonicalName() + " aleady exists."); } } } } // move to the upper class in the hierarchy in search for more methods klass = klass.getSuperclass(); } } @Override public Object copyPrimitive(Object sourceValue) { if (sourceValue == null) { return null; } Class clazz = sourceValue.getClass(); if (clazz == null) { return clazz; } else if (boolean.class.getName().equals(clazz.getName())) { return Boolean.valueOf((boolean) sourceValue); } else if (Boolean.class.getName().equals(clazz.getName())) { return Boolean.valueOf((Boolean) sourceValue); } else if (byte.class.getName().equals(clazz.getName())) { return Byte.valueOf((byte) sourceValue); } else if (Byte.class.getName().equals(clazz.getName())) { return Byte.valueOf((Byte) sourceValue); } else if (char.class.getName().equals(clazz.getName())) { return Character.valueOf((char) sourceValue); } else if (Character.class.getName().equals(clazz.getName())) { return Character.valueOf((Character) sourceValue); } else if (double.class.getName().equals(clazz.getName())) { return Double.valueOf((double) sourceValue); } else if (Double.class.getName().equals(clazz.getName())) { return Double.valueOf((Double) sourceValue); } else if (float.class.getName().equals(clazz.getName())) { return Float.valueOf((float) sourceValue); } else if (Float.class.getName().equals(clazz.getName())) { return Float.valueOf((Float) sourceValue); } else if (int.class.getName().equals(clazz.getName())) { return Integer.valueOf((int) sourceValue); } else if (Integer.class.getName().equals(clazz.getName())) { return Integer.valueOf((Integer) sourceValue); } else if (long.class.getName().equals(clazz.getName())) { return Long.valueOf((long) sourceValue); } else if (Long.class.getName().equals(clazz.getName())) { return Long.valueOf((Long) sourceValue); } else if (short.class.getName().equals(clazz.getName())) { return Short.valueOf((short) sourceValue); } else if (Short.class.getName().equals(clazz.getName())) { return Short.valueOf((Short) sourceValue); } // can't count on java copy return sourceValue; } @Override public Object convertType(Object sourceValue, FieldType origSourceType, FieldType targetType) throws AtlasConversionException { if (origSourceType == null || targetType == null) { throw new AtlasConversionException("FieldTypes must be specified on convertType method."); } if (isAssignableFieldType(origSourceType, targetType)) { return sourceValue; } return convertType(sourceValue, null, classFromFieldType(targetType), null); } @Override public Object convertType(Object sourceValue, String sourceFormat, FieldType targetType, String targetFormat) throws AtlasConversionException { return convertType(sourceValue, sourceFormat, classFromFieldType(targetType), targetFormat); } @Override public Object convertType(Object sourceValue, String sourceFormat, Class targetType, String targetFormat) throws AtlasConversionException { if (sourceValue == null || targetType == null) { throw new AtlasConversionException("AutoConversion requires sourceValue and targetType to be specified"); } if (targetType.isInstance(sourceValue)) { return sourceValue; } ConverterMethodHolder methodHolder = getConverter(sourceValue, targetType); if (methodHolder != null) { try { Object target = methodHolder.staticMethod ? null : methodHolder.converter; if (methodHolder.containsFormat) { return methodHolder.method.invoke(target, sourceValue, sourceFormat, targetFormat); } return methodHolder.method.invoke(target, sourceValue); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new AtlasConversionException("Invoking type convertor failed", e); } } throw new AtlasConversionException("Type Conversion is not supported for sT=" + sourceValue.getClass().getCanonicalName() + " tT=" + targetType.getCanonicalName()); } @Override public boolean isConvertionAvailableFor(Object sourceValue, Class targetType) { return targetType.isInstance(sourceValue) || getConverter(sourceValue, targetType) != null; } private ConverterMethodHolder getConverter(Object sourceValue, Class targetType) { Class boxedSourceClass = sourceValue.getClass(); if (sourceValue.getClass().isPrimitive()) { boxedSourceClass = boxOrUnboxPrimitive(boxedSourceClass); } Class boxedTargetClass = targetType; if (targetType.isPrimitive()) { boxedTargetClass = boxOrUnboxPrimitive(boxedTargetClass); } ConverterKey converterKey = new ConverterKey(boxedSourceClass.getCanonicalName(), boxedTargetClass.getCanonicalName()); // use custom converter first ConverterMethodHolder methodHolder = customConverterMethods.get(converterKey); if (methodHolder == null) { // try the inbuilt defaults methodHolder = converterMethods.get(converterKey); } return methodHolder; } @Override public Boolean isPrimitive(String className) { if (className == null) { return false; } if (PRIMITIVE_CLASSNAMES.contains(className)) { return true; } return false; } @Override public Boolean isPrimitive(Class clazz) { if (clazz == null) { return false; } if (PRIMITIVE_CLASSNAMES.contains(clazz.getCanonicalName())) { return true; } return false; } @Override public Boolean isPrimitive(FieldType fieldType) { if (fieldType == null) { return false; } return PRIMITIVE_FIELDTYPES.contains(fieldType); } @Override public Boolean isBoxedPrimitive(Class clazz) { if (clazz == null) { return false; } return BOXED_PRIMITIVE_CLASSNAMES.contains(clazz.getCanonicalName()); } @Override public Class boxOrUnboxPrimitive(String clazzName) { return classFromFieldType(fieldTypeFromClass(clazzName)); } @Override public Class boxOrUnboxPrimitive(Class clazz) { if (clazz == null) { return clazz; } else if (boolean.class.getName().equals(clazz.getName())) { return Boolean.class; } else if (Boolean.class.getName().equals(clazz.getName())) { return boolean.class; } else if (byte.class.getName().equals(clazz.getName())) { return Byte.class; } else if (Byte.class.getName().equals(clazz.getName())) { return byte.class; } else if (char.class.getName().equals(clazz.getName())) { return Character.class; } else if (Character.class.getName().equals(clazz.getName())) { return char.class; } else if (double.class.getName().equals(clazz.getName())) { return Double.class; } else if (Double.class.getName().equals(clazz.getName())) { return double.class; } else if (float.class.getName().equals(clazz.getName())) { return Float.class; } else if (Float.class.getName().equals(clazz.getName())) { return float.class; } else if (int.class.getName().equals(clazz.getName())) { return Integer.class; } else if (Integer.class.getName().equals(clazz.getName())) { return int.class; } else if (long.class.getName().equals(clazz.getName())) { return Long.class; } else if (Long.class.getName().equals(clazz.getName())) { return long.class; } else if (short.class.getName().equals(clazz.getName())) { return Short.class; } else if (Short.class.getName().equals(clazz.getName())) { return short.class; } return clazz; } @Override public FieldType fieldTypeFromClass(Class clazz) { if (clazz == null) { return null; } return fieldTypeFromClass(clazz.getName()); } @Override public FieldType fieldTypeFromClass(String className) { if (className == null || className.isEmpty()) { return FieldType.NONE; } switch (className) { case "java.lang.Object": return FieldType.ANY; case "java.math.BigInteger": return FieldType.BIG_INTEGER; case "boolean": case "java.lang.Boolean": return FieldType.BOOLEAN; case "byte": case "java.lang.Byte": return FieldType.BYTE; case "[B": case "[Ljava.lang.Byte": return FieldType.BYTE_ARRAY; case "char": return FieldType.CHAR; case "java.lang.Character": return FieldType.CHAR; case "java.math.BigDecimal": return FieldType.DECIMAL; case "double": case "java.lang.Double": return FieldType.DOUBLE; case "float": case "java.lang.Float": return FieldType.FLOAT; case "int": case "java.lang.Integer": case "java.util.concurrent.atomic.AtomicInteger": return FieldType.INTEGER; case "long": case "java.lang.Long": case "java.util.concurrent.atomic.AtomicLong": return FieldType.LONG; case "java.lang.Number": return FieldType.NUMBER; case "short": case "java.lang.Short": return FieldType.SHORT; case "java.lang.String": return FieldType.STRING; case "java.sql.Date": case "java.time.LocalDate": case "java.time.Month": case "java.time.MonthDay": case "java.time.Year": case "java.time.YearMonth": return FieldType.DATE; case "java.sql.Time": case "java.time.LocalTime": return FieldType.TIME; case "java.sql.Timestamp": case "java.time.LocalDateTime": case "java.util.Date": return FieldType.DATE_TIME; case "java.time.ZonedDateTime": case "java.util.Calendar": case "java.util.GregorianCalendar": return FieldType.DATE_TIME_TZ; default: return FieldType.COMPLEX; } } @Override public Class classFromFieldType(FieldType fieldType) { if (fieldType == null) { return null; } switch (fieldType) { case ANY: return Object.class; case BIG_INTEGER: return BigInteger.class; case BOOLEAN: return Boolean.class; case BYTE: return Byte.class; case BYTE_ARRAY: return Byte[].class; case CHAR: return java.lang.Character.class; case COMPLEX: // COMPLEX doesn't have representative class return null; case DATE: return java.time.LocalDate.class; case DATE_TIME: return Date.class; case DATE_TZ: case TIME_TZ: case DATE_TIME_TZ: return java.time.ZonedDateTime.class; case DECIMAL: return java.math.BigDecimal.class; case DOUBLE: return java.lang.Double.class; case FLOAT: return java.lang.Float.class; case INTEGER: return java.lang.Integer.class; case LONG: return java.lang.Long.class; case NONE: return null; case NUMBER: return java.lang.Number.class; case SHORT: return java.lang.Short.class; case STRING: return java.lang.String.class; case TIME: return java.time.LocalTime.class; default: throw new IllegalArgumentException( String.format("Unsupported field type '%s': corresponding Java class needs to be added in DefaultAtlasConversionService", fieldType)); } } @Override public Boolean isAssignableFieldType(FieldType source, FieldType target) { if (source == null || target == null) { return Boolean.FALSE; } if (source.equals(target) || target == FieldType.ANY) { return Boolean.TRUE; } // Check umbrella field types if (target == FieldType.NUMBER) { return source == FieldType.BIG_INTEGER || source == FieldType.BYTE || source == FieldType.DECIMAL || source == FieldType.DOUBLE || source == FieldType.FLOAT || source == FieldType.INTEGER || source == FieldType.LONG || source == FieldType.SHORT; } return Boolean.FALSE; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy