TargetValueConverter.java

/*
 * 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.java.core;

import java.lang.reflect.Modifier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.atlasmap.api.AtlasException;
import io.atlasmap.core.AtlasPath;
import io.atlasmap.core.AtlasPath.SegmentContext;
import io.atlasmap.core.AtlasUtil;
import io.atlasmap.java.v2.JavaEnumField;
import io.atlasmap.java.v2.JavaField;
import io.atlasmap.spi.AtlasConversionService;
import io.atlasmap.spi.AtlasInternalSession;
import io.atlasmap.v2.AuditStatus;
import io.atlasmap.v2.CollectionType;
import io.atlasmap.v2.Field;
import io.atlasmap.v2.FieldType;
import io.atlasmap.v2.LookupEntry;
import io.atlasmap.v2.LookupTable;

/**
 * TODO consolidate with JavaFieldWriterUtil - https://github.com/atlasmap/atlasmap/issues/730 .
 */
public class TargetValueConverter {
    private static final Logger LOG = LoggerFactory.getLogger(TargetValueConverter.class);

    private AtlasConversionService conversionService = null;
    private JavaFieldWriterUtil writerUtil = null;
    private ClassLoader classLoader;

    public TargetValueConverter(ClassLoader loader, AtlasConversionService conversionService, JavaFieldWriterUtil writerUtil) {
        this.classLoader = loader;
        this.conversionService = conversionService;
        this.writerUtil = writerUtil;
    }

    public void populateTargetField(AtlasInternalSession session, LookupTable lookupTable, Field sourceField,
            Object parentObject, Field targetField) throws AtlasException {
        if (sourceField == null) {
            AtlasUtil.addAudit(session, (String)null, "Source field cannot be null", AuditStatus.ERROR, null);
            return;
        }
        if (targetField == null) {
            AtlasUtil.addAudit(session, (String)null, "Target field cannot be null", AuditStatus.ERROR, null);
            return;
        }
        Object sourceValue = sourceField.getValue();

        if (LOG.isDebugEnabled()) {
            LOG.debug("processTargetMapping srcPath={} srcVal={} srcType={}  tgtPath={} tgtdocId={}",
                sourceField.getPath(), sourceField.getValue(), sourceField.getFieldType(),
                targetField.getPath(), targetField.getDocId());
        }

        String targetClassName = (targetField instanceof JavaField) ? ((JavaField) targetField).getClassName() : null;
        targetClassName = (targetField instanceof JavaEnumField) ? ((JavaEnumField) targetField).getClassName()
                : targetClassName;
        if (targetClassName == null && parentObject != null) {
            SegmentContext segment = new AtlasPath(targetField.getPath()).getLastSegment();
            Class<?> clazz = segment.getCollectionType() == CollectionType.NONE
                    ? writerUtil.resolveChildClass(parentObject, segment)
                    : writerUtil.resolveCollectionItemClass(parentObject, segment);
            if (targetField.getFieldType() == null) {
                targetField.setFieldType(conversionService.fieldTypeFromClass(clazz));
            }
            if (targetField.getFieldType() == FieldType.COMPLEX) {
                targetClassName = clazz != null && !Modifier.isAbstract(clazz.getModifiers()) ? clazz.getName() : null;
                if (targetField instanceof JavaField) {
                    ((JavaField)targetField).setClassName(targetClassName);
                } else {
                    ((JavaEnumField)targetField).setClassName(targetClassName);
                }
            }
        }

        if (sourceField instanceof JavaEnumField || targetField instanceof JavaEnumField) {
            if (!(sourceField instanceof JavaEnumField) || !(targetField instanceof JavaEnumField)) {
                AtlasUtil.addAudit(session, targetField, String.format(
                        "Value conversion between enum fields and non-enum fields is not yet supported: sourceType=%s targetType=%s targetPath=%s msg=%s",
                        sourceField.getFieldType(), targetField.getFieldType(), targetField.getPath()),
                        AuditStatus.ERROR, sourceValue != null ? sourceValue.toString() : null);
            }
            populateEnumValue(session, lookupTable, (JavaEnumField) sourceField, (JavaEnumField) targetField);
            return;
        }

        JavaField javaTargetField = (JavaField) targetField;
        if (sourceValue == null) {
            if (targetField.getFieldType() != FieldType.COMPLEX) {
                AtlasUtil.addAudit(session, targetField, String.format(
                        "Null sourceValue for targetDocId=%s, targetPath=%s", targetField.getDocId(), targetField.getPath()),
                        AuditStatus.WARN, sourceValue != null ? sourceValue.toString() : null);
                targetField.setValue(null);
                return;
            }
            if (javaTargetField.getClassName() != null) {
                Object created = writerUtil.instantiateObject(writerUtil.loadClass(javaTargetField.getClassName()));
                javaTargetField.setValue(created);
                return;
            }
        }

        Object targetValue = doConvertTargetValue(session, sourceValue, targetClassName, targetField);
        targetField.setValue(targetValue);
    }

    public void convertTargetValue(AtlasInternalSession session,
            Object parentObject, Field targetField) throws AtlasException {
        String targetClassName = (targetField instanceof JavaField) ? ((JavaField) targetField).getClassName() : null;
        targetClassName = (targetField instanceof JavaEnumField) ? ((JavaEnumField) targetField).getClassName()
                : targetClassName;
        Object targetValue = doConvertTargetValue(session, targetField.getValue(), targetClassName, targetField);
        targetField.setValue(targetValue);
    }

    public void setClassLoader(ClassLoader loader) {
        this.classLoader = loader;
    }

    private Object doConvertTargetValue(AtlasInternalSession session, Object sourceValue,
            String targetClassName, Field targetField) throws AtlasException {
        if (sourceValue == null) {
            return null;
        }

        FieldType sourceType = conversionService.fieldTypeFromClass(sourceValue.getClass());
        FieldType targetType = targetField.getFieldType();
        Class<?> targetClazz = null;
        if (targetClassName == null) {
            if (targetType != null) {
                targetClazz = conversionService.classFromFieldType(targetType);
            } else {
                AtlasUtil.addAudit(session, targetField, String.format(
                                "Target field doesn't have fieldType nor className: automatic conversion won't work: targetPath=%s",
                                targetField.getPath()),
                        AuditStatus.WARN, sourceValue != null ? sourceValue.toString() : null);
            }
        } else if (conversionService.isPrimitive(targetClassName)) {
            targetClazz = conversionService.boxOrUnboxPrimitive(targetClassName);
        } else {
            try {
                targetClazz = classLoader.loadClass(targetClassName);
            } catch (ClassNotFoundException e) {
                AtlasUtil.addAudit(session, targetField,
                        String.format("Target field class '%s' was not found: sourceType=%s targetType=%s targetPath=%s msg=%s",
                                ((JavaField)targetField).getClassName(), sourceType, targetType, targetField.getPath(), e.getMessage()),
                        AuditStatus.ERROR, null);
                return null;
            }
        }

        if (targetClazz != null) {
            if (targetField.getFieldType() == null) {
                targetField.setFieldType(conversionService.fieldTypeFromClass(targetClazz));
            }
            if (conversionService.isConvertionAvailableFor(sourceValue, targetClazz)) {
                return conversionService.convertType(sourceValue, null, targetClazz, null);
            } else {
                return null;
            }
        } else {
            return sourceValue;
        }
    }

    @SuppressWarnings("unchecked")
    private Object populateEnumValue(AtlasInternalSession session, LookupTable lookupTable, JavaEnumField sourceField, JavaEnumField targetField) {
        if (sourceField == null || sourceField.getValue() == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Input enum field or value is null, field: " + sourceField);
            }
            return null;
        }

        String sourceValue = ((Enum<?>) sourceField.getValue()).name();
        String targetValue = sourceValue;
        if (lookupTable != null) {
            for (LookupEntry e : lookupTable.getLookupEntry()) {
                if (e.getSourceValue().equals(sourceValue)) {
                    targetValue = e.getTargetValue();
                    break;
                }
            }
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Mapped input enum value '" + sourceValue + "' to output enum value '" + targetValue + "'.");
        }

        if (targetValue == null) {
            return null;
        }

        @SuppressWarnings("rawtypes")
        Class enumClass = null;
        try {
            enumClass = this.classLoader.loadClass(targetField.getClassName());
        } catch (Exception e) {
            AtlasUtil.addAudit(session, targetField,
                    String.format("Could not find class for output field class '%s': %s", targetField.getClassName(), e.getMessage()),
                    AuditStatus.ERROR, targetValue);
            return null;
        }

        try {
            Enum<?> enumValue = Enum.valueOf(enumClass, targetValue);
            targetField.setValue(enumValue);
            return enumValue;
        } catch (IllegalArgumentException e) {
            AtlasUtil.addAudit(session, targetField,
                    String.format("No enum entry found for value '%s': %s", targetValue, e.getMessage()),
                    AuditStatus.ERROR, targetValue);
            return null;
        }
    }

}