org.nmdp.hmlfhirconvertermodels.domain.base.CascadingUpdate.java Source code

Java tutorial

Introduction

Here is the source code for org.nmdp.hmlfhirconvertermodels.domain.base.CascadingUpdate.java

Source

package org.nmdp.hmlfhirconvertermodels.domain.base;

/**
 * Created by Andrew S. Brown, Ph.D., <andrew@nmdp.org>, on 5/26/17.
 * <p>
 * service-hml-fhir-converter-models
 * Copyright (c) 2012-2017 National Marrow Donor Program (NMDP)
 * <p>
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 * <p>
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library;  if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
 * <p>
 * > http://www.fsf.org/licensing/licenses/lgpl.html
 * > http://www.opensource.org/licenses/lgpl-license.php
 */

import com.mongodb.*;

import org.apache.log4j.Logger;

import org.bson.types.ObjectId;

import org.nmdp.hmlfhirconvertermodels.domain.hml.ICascadable;
import org.nmdp.hmlfhirconvertermodels.domain.internal.MongoConfiguration;
import org.nmdp.hmlfhirconvertermodels.util.*;

import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import javax.xml.bind.annotation.XmlAttribute;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

abstract class CascadingUpdate<T extends SwaggerConverter<T, U>, U> implements ICascadingUpdate<T, U> {

    @Transient
    private final static Logger LOG = Logger.getLogger(CascadingUpdate.class);

    @Override
    public void updateCollectionProperties(T entity, MongoConfiguration mongoConfiguration) {
        save(entity, mongoConfiguration, true);
    }

    @Override
    public void saveCollectionProperties(T entity, MongoConfiguration mongoConfiguration) {
        save(entity, mongoConfiguration, false);
    }

    private void save(T entity, MongoConfiguration mongoConfiguration, Boolean isUpdate) {
        List<Field> cascadingFields = getSavableFields(entity);
        MongoOperations mongoOperations = getMongoOperations(mongoConfiguration);

        for (Field field : cascadingFields) {
            Class<?> propertyClass = field.getType();
            Document document = getDocumentFromField(propertyClass, field);
            String collectionName = document.collection();
            field.setAccessible(true);
            handleCascade(field, entity, mongoOperations, collectionName, isUpdate);
        }
    }

    private void handleCascade(Field field, T entity, MongoOperations mongoOperations, String collectionName,
            Boolean isUpdate) {
        try {
            Object model = convertToDataModel(field, entity);

            if (isUpdate) {
                handleUpdatedDate(model, field);
            }

            handleDateField(model, field);
            setModelField(model, field, mongoOperations, collectionName, entity);
        } catch (Exception ex) {
            LOG.error(ex);
        }
    }

    private void setModelField(Object model, Field field, MongoOperations mongoOperations, String collectionName,
            T entity) {
        field.setAccessible(true);

        try {
            if (field.getGenericType() instanceof ParameterizedType) {
                List<IMongoDataRepositoryModel> savedCollection = new ArrayList<>();
                for (IMongoDataRepositoryModel item : (List<IMongoDataRepositoryModel>) model) {
                    IMongoDataRepositoryModel savedItem = upsert(item, mongoOperations, collectionName);
                    savedCollection.add(savedItem);
                }

                field.set(entity, savedCollection);
                return;
            }

            field.set(entity, upsert((IMongoDataRepositoryModel) model, mongoOperations, collectionName));
        } catch (Exception ex) {
            LOG.error(ex);
        }
    }

    private void handleDateField(Object model, Field field) {
        field.setAccessible(true);

        try {
            if (field.getGenericType() instanceof ParameterizedType) {
                Converters.handleListDateField((List<IMongoDataRepositoryModel>) model);
                return;
            }

            Converters.handleDateField((IMongoDataRepositoryModel) model);
        } catch (Exception ex) {
            LOG.error(ex);
        }
    }

    private Object convertToDataModel(Field field, T entity) {
        field.setAccessible(true);

        try {
            if (field.getGenericType() instanceof ParameterizedType) {
                return (List<IMongoDataRepositoryModel>) field.get(entity);
            }

            return (IMongoDataRepositoryModel) field.get(entity);
        } catch (Exception ex) {
            LOG.error(ex);
            return null;
        }
    }

    private void handleUpdatedDate(Object model, Field field) {
        field.setAccessible(true);

        if (field.getGenericType() instanceof ParameterizedType) {
            for (IMongoDataRepositoryModel m : (List<IMongoDataRepositoryModel>) model) {
                setUpdatedDateOnModel(m);
            }

            return;
        }

        setUpdatedDateOnModel((IMongoDataRepositoryModel) model);
    }

    private void setUpdatedDateOnModel(IMongoDataRepositoryModel model) {
        try {
            Field field = model.getClass().getDeclaredField("dateUpdated");
            field.setAccessible(true);
            field.set(model, new Date());
        } catch (Exception ex) {
            LOG.error(ex);
        }
    }

    private MongoOperations getMongoOperations(MongoConfiguration configuration) {
        return new MongoTemplate(new MongoClient(createMongoConnectionString(configuration)),
                configuration.getDatabaseName());
    }

    private List<Field> getSavableFields(T entity) {
        return Arrays.stream(entity.getClass().getDeclaredFields()).filter(Objects::nonNull)
                .filter(r -> implementsCascading(r)).filter(r -> !isStaticField(r)).collect(Collectors.toList());
    }

    private IMongoDataRepositoryModel upsert(IMongoDataRepositoryModel model, MongoOperations mongoOperations,
            String collectionName) throws Exception {
        WriteResult writeResult = mongoOperations.upsert(buildUpsertQuery(model), buildUpsertUpdate(model),
                collectionName);
        DBCollection collection = mongoOperations.getCollection(collectionName);
        Object upsertedId;

        if (writeResult.isUpdateOfExisting()) {
            try {
                Field field = model.getClass().getDeclaredField("id");
                field.setAccessible(true);
                String id = field.get(model).toString();
                upsertedId = objectifyId(id);
            } catch (Exception ex) {
                LOG.error(ex);
                throw new Exception("Unable to evaluate 'id' property", ex);
            }
        } else {
            upsertedId = writeResult.getUpsertedId();
        }

        DBObject result = collection.findOne(upsertedId);
        return model.convertGenericResultToModel(result, model, getDocumentProperties(model));
    }

    private ObjectId objectifyId(String id) {
        return new ObjectId(id);
    }

    private Query buildUpsertQuery(IMongoDataRepositoryModel model) {
        List<String> propertyNames = new ArrayList<>();

        if (hasDocumentId(model)) {
            propertyNames.add("id");
            return org.nmdp.hmlfhirconvertermodels.util.QueryBuilder.buildEqualsPropertyQuery(model, propertyNames,
                    true);
        }

        propertyNames = getDocumentProperties(model).stream().filter(Objects::nonNull)
                .filter(obj -> obj.getName() != "dateUpdated" && obj.getName() != "id").map(obj -> obj.getName())
                .collect(Collectors.toList());

        return org.nmdp.hmlfhirconvertermodels.util.QueryBuilder.buildEqualsPropertyQuery(model, propertyNames,
                false);
    }

    private boolean hasDocumentId(IMongoDataRepositoryModel model) {
        Field id = getDocumentProperties(model).stream().filter(prop -> prop.getName() == "id").findFirst().get();

        id.setAccessible(true);
        if (getFieldValue(model, id) == null) {
            return false;
        }

        return true;
    }

    private Update buildUpsertUpdate(IMongoDataRepositoryModel model) {
        Update update = new Update();
        List<Field> documentPropertyFields = getDocumentProperties(model);

        for (Field field : documentPropertyFields) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object fieldValue = getFieldValue(model, field);

            if (fieldName == "id") {
                continue;
            }

            update.set(fieldName, fieldValue);
        }

        return update;
    }

    private List<Field> getDocumentProperties(IMongoDataRepositoryModel model) {
        return Arrays.stream(model.getClass().getDeclaredFields()).filter(Objects::nonNull)
                .filter(obj -> !Arrays.stream(obj.getAnnotations()).filter(Objects::nonNull)
                        .filter(a -> a.annotationType().equals(XmlAttribute.class)).collect(Collectors.toList())
                        .isEmpty())
                .collect(Collectors.toList());
    }

    private Object getFieldValue(Object model, Field field) {
        try {
            return field.get(model);
        } catch (Exception ex) {
            LOG.error(ex);
        }

        return null;
    }

    private Document getDocumentFromField(Class<?> propertyClass, Field field) {
        Type type = field.getGenericType();

        if (!(type instanceof ParameterizedType)) {
            return (Document) Arrays.stream(propertyClass.getAnnotations()).filter(Objects::nonNull)
                    .filter(a -> a.annotationType().equals(Document.class)).findFirst().orElse(null);
        }

        return (Document) Arrays.stream(((ParameterizedType) field.getGenericType()).getActualTypeArguments())
                .filter(Objects::nonNull).map(t -> getDocumentAnnotation((Class<?>) t)).filter(Objects::nonNull)
                .findFirst().orElse(null);
    }

    private Annotation getDocumentAnnotation(Class<?> aClass) {
        return Arrays.stream(aClass.getDeclaredAnnotations()).filter(Objects::nonNull)
                .filter(annotation -> annotation.annotationType().equals(Document.class)).findFirst().orElse(null);
    }

    private Boolean implementsCascading(Field field) {
        Type type = field.getGenericType();

        if (!(type instanceof ParameterizedType)) {
            return Arrays.stream(field.getType().getInterfaces()).filter(Objects::nonNull)
                    .anyMatch(i -> i.equals(ICascadable.class));
        }

        return Arrays.stream(field.getDeclaringClass().getInterfaces()).filter(Objects::nonNull)
                .anyMatch(c -> c.equals(ICascadable.class));
    }

    private Boolean isStaticField(Field field) {
        field.setAccessible(true);
        return Modifier.isStatic(field.getModifiers());
    }

    private String createMongoConnectionString(MongoConfiguration config) {
        return config.getHost() + ":" + config.getPortNo();
    }
}