JavaFieldWriter.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.util.LinkedHashMap;
import java.util.Map;

import org.slf4j.LoggerFactory;

import io.atlasmap.api.AtlasException;
import io.atlasmap.core.AtlasPath;
import io.atlasmap.core.AtlasPath.SegmentContext;
import io.atlasmap.java.v2.JavaEnumField;
import io.atlasmap.java.v2.JavaField;
import io.atlasmap.spi.AtlasFieldWriter;
import io.atlasmap.spi.AtlasInternalSession;
import io.atlasmap.v2.CollectionType;
import io.atlasmap.v2.Field;
import io.atlasmap.v2.FieldGroup;
import io.atlasmap.v2.FieldType;
import io.atlasmap.v2.LookupTable;

public class JavaFieldWriter implements AtlasFieldWriter {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(JavaFieldWriter.class);

    private Object rootObject = null;
    private JavaFieldWriterUtil writerUtil;
    private TargetValueConverter converter;
    private Map<String, Object> pathParentQueue = new LinkedHashMap<>();
    private Class<?> collectionItemClass = null;

    public JavaFieldWriter(JavaFieldWriterUtil util) {
        this.writerUtil = util;
    }

    public Object prepareParentObject(AtlasInternalSession session) throws AtlasException {
        Field targetField = session.head().getTargetField();
        if (targetField == null) {
            throw new AtlasException("Target field cannot be null");
        }

        try {
            AtlasPath path = new AtlasPath(targetField.getPath());
            if (path.isRoot()) {
                return null;
            }
            if (rootObject == null) {
                throw new IllegalArgumentException("A root object must be set before process");
            }

            SegmentContext rootSegment = path.getRootSegment();
            Object parentObject = this.rootObject;
            if (rootSegment.getCollectionType() != CollectionType.NONE) {
                if (collectionItemClass == null) {
                    throw new AtlasException(
                            String.format("Collection item class must be specified to handle topmost collection, path=",
                                    path.toString()));
                }
                if (rootSegment.getCollectionIndex() == null) {
                    // Cannot proceed from collection segment without index
                    return null;
                }
                parentObject = writerUtil.getCollectionItem(rootObject, rootSegment);
                if (parentObject == null) {
                    this.rootObject = writerUtil.adjustCollectionSize(this.rootObject, rootSegment);
                    parentObject = writerUtil.createComplexCollectionItem(this.rootObject, collectionItemClass,
                            rootSegment);
                }
            }

            for (SegmentContext segmentContext : path.getSegments(false)) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Now processing segment: " + segmentContext);
                    LOG.debug("Parent object is currently: " + writeDocumentToString(false, parentObject));
                }

                if (segmentContext == path.getLastSegment()) {
                    return parentObject;
                }

                Object childObject = writerUtil.getChildObject(parentObject, segmentContext);
                if (childObject == null) {
                    childObject = writerUtil.createComplexChildObject(parentObject, segmentContext);
                }
                if (segmentContext.getCollectionType() != CollectionType.NONE) {
                    if (segmentContext.getCollectionIndex() == null) {
                        // Cannot proceed from collection segment without index
                        return null;
                    }
                    Object item = writerUtil.getCollectionItem(childObject, segmentContext);
                    if (item == null) {
                        Object adjusted = writerUtil.adjustCollectionSize(childObject, segmentContext);
                        if (adjusted != childObject) {
                            writerUtil.setChildObject(parentObject, adjusted, segmentContext);
                        }
                        item = writerUtil.createComplexCollectionItem(parentObject, adjusted, segmentContext);
                    }
                    childObject = item;
                }
                parentObject = childObject;
            }
            return null;
        } catch (Throwable t) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Error occured while preparing parent object for: " + targetField.getPath(), t);
            }
            if (t instanceof AtlasException) {
                throw (AtlasException) t;
            }
            throw new AtlasException(t);
        }
    }

    public void populateTargetFieldValue(AtlasInternalSession session, Object parentObject) throws AtlasException {
        Field sourceField = session.head().getSourceField();
        Field targetField = session.head().getTargetField();
        LookupTable lookupTable = session.head().getLookupTable();
        converter.populateTargetField(session, lookupTable, sourceField, parentObject, targetField);
    }

    public void enqueueFieldAndParent(Field field, Object parentObject) {
        this.pathParentQueue.put(field.getPath(), parentObject);
    }

    public void commitWriting(AtlasInternalSession session) throws AtlasException {
        Field targetField = session.head().getTargetField();
        try {
            if (targetField instanceof FieldGroup) {
               FieldGroup targetFieldGroup = (FieldGroup) targetField;
                if (targetFieldGroup.getField().size() > 0) {
                    for (Field f : targetFieldGroup.getField()) {
                        session.head().setTargetField(f);
                        doCommitWriting(session);
                    }
                } else {
                    AtlasPath path = new AtlasPath(targetField.getPath());
                    if (path.getLastSegment().getCollectionType() != CollectionType.NONE) {
                        // create an empty collection
                        doCommitWriting(session);
                    }
                }
                return;
            }
            doCommitWriting(session);
        } finally {
            if (!this.pathParentQueue.isEmpty()) {
                LOG.warn("Discarding uncommitted entry in the queue: {}", this.pathParentQueue);
                this.pathParentQueue.clear();
            }
        }
    }

    private void doCommitWriting(AtlasInternalSession session) throws AtlasException {
        Field targetField = session.head().getTargetField();
        AtlasPath path = new AtlasPath(targetField.getPath());
        Object parentObject = this.pathParentQueue.get(targetField.getPath());
        this.pathParentQueue.remove(targetField.getPath());
        converter.convertTargetValue(session, parentObject, targetField);
        SegmentContext lastSegment = path.getLastSegment();
        if (path.isRoot()) {
            if (lastSegment.getCollectionType() == CollectionType.NONE) {
                this.rootObject = targetField.getValue();
            } else {
                Object adjusted = writerUtil.adjustCollectionSize(this.rootObject, lastSegment);
                if (adjusted != this.rootObject) {
                    this.rootObject = adjusted;
                }
                writerUtil.setCollectionItem(adjusted, targetField.getValue(), lastSegment);
            }
            return;
        }
        if (parentObject == null) {
            return;
        }

        String targetClassName = null;
        if (targetField instanceof JavaField) {
            targetClassName = ((JavaField) targetField).getClassName();
        } else if (targetField instanceof JavaEnumField) {
            targetClassName = ((JavaEnumField) targetField).getClassName();
        }

        if (lastSegment.getCollectionType() == CollectionType.NONE) {
            // Don't handle null for JavaEnumField complex type using complex child object
            if (targetField.getFieldType() == FieldType.COMPLEX && !(targetField instanceof JavaEnumField)
                    && targetField.getValue() == null) {
                if (targetClassName != null && !targetClassName.isEmpty()) {
                    writerUtil.createComplexChildObject(parentObject, lastSegment,
                            writerUtil.loadClass(targetClassName));
                } else {
                    writerUtil.createComplexChildObject(parentObject, lastSegment);
                }
            } else {
                writerUtil.setChildObject(parentObject, targetField.getValue(), lastSegment);
            }
        } else {
            Object collection = writerUtil.getChildObject(parentObject, lastSegment);
            if (collection == null) {
                collection = writerUtil.createComplexChildObject(parentObject, lastSegment);
            }
            if (lastSegment.getCollectionIndex() == null) {
                // Collection field without index - just create collection object and keep it empty
                return;
            }
            Object adjusted = writerUtil.adjustCollectionSize(collection, lastSegment);
            if (adjusted != collection) {
                writerUtil.setChildObject(parentObject, adjusted, lastSegment);
            }
            if (targetField.getFieldType() == FieldType.COMPLEX && targetField.getValue() == null) {
                if (targetClassName != null && !targetClassName.isEmpty()) {
                    writerUtil.createComplexChildObject(parentObject, lastSegment,
                            writerUtil.loadClass(targetClassName));
                } else {
                    writerUtil.createComplexChildObject(parentObject, lastSegment);
                }
            } else {
                writerUtil.setCollectionItem(adjusted, targetField.getValue(), lastSegment);
            }
        }
    }

    public void write(AtlasInternalSession session) throws AtlasException {
        Object parentObject = prepareParentObject(session);
        populateTargetFieldValue(session, parentObject);
        Field targetField = session.head().getTargetField();
        enqueueFieldAndParent(targetField, parentObject);
        commitWriting(session);
    }

    public Object getRootObject() {
        return rootObject;
    }

    public void setRootObject(Object rootObject) {
        this.rootObject = rootObject;
    }

    public void setTargetValueConverter(TargetValueConverter converter) {
        this.converter = converter;
    }

    public void setCollectionItemClass(Class<?> clazz) {
        this.collectionItemClass = clazz;
    }

    private String writeDocumentToString(boolean stripSpaces, Object object) throws AtlasException {
        try {
            if (object == null) {
                return "";
            }

            String result = object.toString();

            if (stripSpaces) {
                result = result.replaceAll("\n|\r", "");
                result = result.replaceAll("> *?<", "><");
            }
            return result;
        } catch (Exception e) {
            throw new AtlasException(e);
        }
    }

}