Package org.openengsb.core.ekb.common

Source Code of org.openengsb.core.ekb.common.EDBConverter

/**
* Licensed to the Austrian Association for Software Tool Integration (AASTI)
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. The AASTI 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.openengsb.core.ekb.common;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.reflect.FieldUtils;
import org.openengsb.core.api.context.ContextHolder;
import org.openengsb.core.api.model.FileWrapper;
import org.openengsb.core.api.model.ModelWrapper;
import org.openengsb.core.api.model.OpenEngSBModel;
import org.openengsb.core.api.model.OpenEngSBModelEntry;
import org.openengsb.core.api.model.annotation.OpenEngSBForeignKey;
import org.openengsb.core.edb.api.EDBConstants;
import org.openengsb.core.edb.api.EDBObject;
import org.openengsb.core.edb.api.EDBObjectEntry;
import org.openengsb.core.edb.api.EngineeringDatabaseService;
import org.openengsb.core.ekb.api.ConnectorInformation;
import org.openengsb.core.ekb.api.EKBCommit;
import org.openengsb.core.ekb.api.EKBException;
import org.openengsb.core.util.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;

/**
* The EDBConverter class responsibility is the converting between EDBObjects and models and the vice-versa.
*/
public class EDBConverter {
    private static final Logger LOGGER = LoggerFactory.getLogger(EDBConverter.class);
   
    public static final String FILEWRAPPER_FILENAME_SUFFIX = ".filename";
    public static final String REFERENCE_PREFIX = "refersTo_";
    private EngineeringDatabaseService edbService;

    public EDBConverter(EngineeringDatabaseService edbService) {
        this.edbService = edbService;
    }

    /**
     * Converts an EDBObject to a model of the given model type.
     */
    @SuppressWarnings("unchecked")
    public <T> T convertEDBObjectToModel(Class<T> model, EDBObject object) {
        return (T) convertEDBObjectToUncheckedModel(model, object);
    }

    /**
     * Converts a list of EDBObjects to a list of models of the given model type.
     */
    public <T> List<T> convertEDBObjectsToModelObjects(Class<T> model, List<EDBObject> objects) {
        List<T> models = new ArrayList<>();
        for (EDBObject object : objects) {
            T instance = convertEDBObjectToModel(model, object);
            if (instance != null) {
                models.add(instance);
            }
        }
        return models;
    }

    /**
     * Tests if an EDBObject has the correct model class in which it should be converted. Returns false if the model
     * type is not fitting, returns true if the model type is fitting or model type is unknown.
     */
    private boolean checkEDBObjectModelType(EDBObject object, Class<?> model) {
        String modelClass = object.getString(EDBConstants.MODEL_TYPE);
        if (modelClass == null) {
            LOGGER.warn(String.format("The EDBObject with the oid %s has no model type information."
                    + "The resulting model may be a different model type than expected.", object.getOID()));
        }
        if (modelClass != null && !modelClass.equals(model.getName())) {
            return false;
        }
        return true;
    }

    /**
     * Converts an EDBObject to a model by analyzing the object and trying to call the corresponding setters of the
     * model.
     */
    private Object convertEDBObjectToUncheckedModel(Class<?> model, EDBObject object) {
        if (!checkEDBObjectModelType(object, model)) {
            return null;
        }
        filterEngineeringObjectInformation(object, model);
        List<OpenEngSBModelEntry> entries = new ArrayList<>();
        for (PropertyDescriptor propertyDescriptor : getPropertyDescriptorsForClass(model)) {
            if (propertyDescriptor.getWriteMethod() == null
                    || propertyDescriptor.getName().equals(ModelUtils.MODEL_TAIL_FIELD_NAME)) {
                continue;
            }
            Object value = getValueForProperty(propertyDescriptor, object);
            Class<?> propertyClass = propertyDescriptor.getPropertyType();
            if (propertyClass.isPrimitive()) {
                entries.add(new OpenEngSBModelEntry(propertyDescriptor.getName(), value, ClassUtils
                    .primitiveToWrapper(propertyClass)));
            } else {
                entries.add(new OpenEngSBModelEntry(propertyDescriptor.getName(), value, propertyClass));
            }
        }

        for (Map.Entry<String, EDBObjectEntry> objectEntry : object.entrySet()) {
            EDBObjectEntry entry = objectEntry.getValue();
            Class<?> entryType;
            try {
                entryType = model.getClassLoader().loadClass(entry.getType());
                entries.add(new OpenEngSBModelEntry(entry.getKey(), entry.getValue(), entryType));
            } catch (ClassNotFoundException e) {
                LOGGER.error("Unable to load class {} of the model tail", entry.getType());
            }
        }
        return ModelUtils.createModel(model, entries);
    }

    /**
     * Returns all property descriptors for a given class.
     */
    private List<PropertyDescriptor> getPropertyDescriptorsForClass(Class<?> clasz) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(clasz);
            return Arrays.asList(beanInfo.getPropertyDescriptors());
        } catch (IntrospectionException e) {
            LOGGER.error("instantiation exception while trying to create instance of class {}", clasz.getName());
        }
        return Lists.newArrayList();
    }

    /**
     * Generate the value for a specific property of a model out of an EDBObject.
     */
    private Object getValueForProperty(PropertyDescriptor propertyDescriptor, EDBObject object) {
        Method setterMethod = propertyDescriptor.getWriteMethod();
        String propertyName = propertyDescriptor.getName();
        Object value = object.getObject(propertyName);
        Class<?> parameterType = setterMethod.getParameterTypes()[0];

        // TODO: OPENENGSB-2719 do that in a better way than just an if-else series
        if (Map.class.isAssignableFrom(parameterType)) {
            List<Class<?>> classes = getGenericMapParameterClasses(setterMethod);
            value = getMapValue(classes.get(0), classes.get(1), propertyName, object);
        } else if (List.class.isAssignableFrom(parameterType)) {
            Class<?> clazz = getGenericListParameterClass(setterMethod);
            value = getListValue(clazz, propertyName, object);
        } else if (parameterType.isArray()) {
            Class<?> clazz = parameterType.getComponentType();
            value = getArrayValue(clazz, propertyName, object);
        } else if (value == null) {
            return null;
        } else if (OpenEngSBModel.class.isAssignableFrom(parameterType)) {
            Object timestamp = object.getObject(EDBConstants.MODEL_TIMESTAMP);
            Long time = System.currentTimeMillis();
            if (timestamp != null) {
                try {
                    time = Long.parseLong(timestamp.toString());
                } catch (NumberFormatException e) {
                    LOGGER.warn("The model with the oid {} has an invalid timestamp.", object.getOID());
                }
            }
            EDBObject obj = edbService.getObject((String) value, time);
            value = convertEDBObjectToUncheckedModel(parameterType, obj);
            object.remove(propertyName);
        } else if (parameterType.equals(FileWrapper.class)) {
            FileWrapper wrapper = new FileWrapper();
            String filename = object.getString(propertyName + FILEWRAPPER_FILENAME_SUFFIX);
            String content = (String) value;
            wrapper.setFilename(filename);
            wrapper.setContent(Base64.decodeBase64(content));
            value = wrapper;
            object.remove(propertyName + FILEWRAPPER_FILENAME_SUFFIX);
        } else if (parameterType.equals(File.class)) {
            return null;
        } else if (object.containsKey(propertyName)) {
            if (parameterType.isEnum()) {
                value = getEnumValue(parameterType, value);
            }
        }
        object.remove(propertyName);
        return value;
    }

    /**
     * Get the type of the list parameter of a setter.
     */
    private Class<?> getGenericListParameterClass(Method setterMethod) {
        return getGenericParameterClasses(setterMethod, 1).get(0);
    }

    /**
     * Get the type of the map parameter of a setter
     */
    private List<Class<?>> getGenericMapParameterClasses(Method setterMethod) {
        return getGenericParameterClasses(setterMethod, 2);
    }

    /**
     * Loads the generic parameter classes up to the given depth (1 for lists, 2 for maps)
     */
    private List<Class<?>> getGenericParameterClasses(Method setterMethod, int depth) {
        Type t = setterMethod.getGenericParameterTypes()[0];
        ParameterizedType pType = (ParameterizedType) t;
        List<Class<?>> classes = new ArrayList<>();
        for (int i = 0; i < depth; i++) {
            classes.add((Class<?>) pType.getActualTypeArguments()[i]);
        }
        return classes;
    }

    /**
     * Gets a list object out of an EDBObject.
     */
    @SuppressWarnings("unchecked")
    private <T> List<T> getListValue(Class<T> type, String propertyName, EDBObject object) {
        List<T> temp = new ArrayList<>();
        for (int i = 0;; i++) {
            String property = getEntryNameForList(propertyName, i);
            Object obj = object.getObject(property);
            if (obj == null) {
                break;
            }
            if (OpenEngSBModel.class.isAssignableFrom(type)) {
                obj = convertEDBObjectToUncheckedModel(type, edbService.getObject(object.getString(property)));
            }
            temp.add((T) obj);
            object.remove(property);
        }
        return temp;
    }

    /**
     * Gets an array object out of an EDBObject.
     */
    @SuppressWarnings("unchecked")
    private <T> T[] getArrayValue(Class<T> type, String propertyName, EDBObject object) {
        List<T> elements = getListValue(type, propertyName, object);
        T[] ar = (T[]) Array.newInstance(type, elements.size());
        return elements.toArray(ar);
    }

    /**
     * Gets a map object out of an EDBObject.
     */
    private Object getMapValue(Class<?> keyType, Class<?> valueType, String propertyName, EDBObject object) {
        Map<Object, Object> temp = new HashMap<>();
        for (int i = 0;; i++) {
            String keyProperty = getEntryNameForMapKey(propertyName, i);
            String valueProperty = getEntryNameForMapValue(propertyName, i);
            if (!object.containsKey(keyProperty)) {
                break;
            }
            Object key = object.getObject(keyProperty);
            Object value = object.getObject(valueProperty);
            if (OpenEngSBModel.class.isAssignableFrom(keyType)) {
                key = convertEDBObjectToUncheckedModel(keyType, edbService.getObject(key.toString()));
            }
            if (OpenEngSBModel.class.isAssignableFrom(valueType)) {
                value = convertEDBObjectToUncheckedModel(valueType, edbService.getObject(value.toString()));
            }
            temp.put(key, value);
            object.remove(keyProperty);
            object.remove(valueProperty);
        }
        return temp;
    }

    /**
     * Gets an enum value out of an object.
     */
    private Object getEnumValue(Class<?> type, Object value) {
        Object[] enumValues = type.getEnumConstants();
        for (Object enumValue : enumValues) {
            if (enumValue.toString().equals(value.toString())) {
                value = enumValue;
                break;
            }
        }
        return value;
    }

    /**
     * Converts the models of an EKBCommit to EDBObjects and return an object which contains the three corresponding
     * lists
     */
    public ConvertedCommit convertEKBCommit(EKBCommit commit) {
        ConvertedCommit result = new ConvertedCommit();
        ConnectorInformation information = commit.getConnectorInformation();
        result.setInserts(convertModelsToEDBObjects(commit.getInserts(), information));
        result.setUpdates(convertModelsToEDBObjects(commit.getUpdates(), information));
        result.setDeletes(convertModelsToEDBObjects(commit.getDeletes(), information));
        return result;
    }

    /**
     * Convert a list of models to a list of EDBObjects (the version retrieving is not considered here. This is done in
     * the EDB directly).
     */
    public List<EDBObject> convertModelsToEDBObjects(List<OpenEngSBModel> models, ConnectorInformation info) {
        List<EDBObject> result = new ArrayList<>();
        if (models != null) {
            for (Object model : models) {
                result.addAll(convertModelToEDBObject(model, info));
            }
        }
        return result;
    }

    /**
     * Converts an OpenEngSBModel object to an EDBObject (the version retrieving is not considered here. This is done in
     * the EDB directly).
     */
    public List<EDBObject> convertModelToEDBObject(Object model, ConnectorInformation info) {
        if (!OpenEngSBModel.class.isAssignableFrom(model.getClass())) {
            throw new IllegalArgumentException("This function need to get a model passed");
        }
        List<EDBObject> objects = new ArrayList<>();
        if (model != null) {
            convertSubModel((OpenEngSBModel) model, objects, info);
        }
        return objects;
    }

    /**
     * Recursive function to generate a list of EDBObjects out of a model object.
     */
    private String convertSubModel(OpenEngSBModel model, List<EDBObject> objects, ConnectorInformation info) {
        String contextId = ContextHolder.get().getCurrentContextId();
        String oid = ModelWrapper.wrap(model).getCompleteModelOID();
        EDBObject object = new EDBObject(oid);
        try {
            fillEDBObjectWithEngineeringObjectInformation(object, model);
        } catch (IllegalAccessException e) {
            LOGGER.warn("Unable to fill completely the EngineeringObjectInformation into the EDBObject", e);
            throw new EKBException("Unable to fill completely the EngineeringObjectInformation into the EDBObject", e);
        }
        for (OpenEngSBModelEntry entry : model.toOpenEngSBModelEntries()) {
            if (entry.getValue() == null) {
                continue;
            } else if (entry.getType().equals(FileWrapper.class)) {
                try {
                    FileWrapper wrapper = (FileWrapper) entry.getValue();
                    String content = Base64.encodeBase64String(wrapper.getContent());
                    object.putEDBObjectEntry(entry.getKey(), content, String.class);
                    object.putEDBObjectEntry(entry.getKey() + FILEWRAPPER_FILENAME_SUFFIX,
                        wrapper.getFilename(), String.class);
                } catch (IOException e) {
                    LOGGER.error(e.getMessage());
                }
            } else if (OpenEngSBModel.class.isAssignableFrom(entry.getType())) {
                OpenEngSBModel temp = (OpenEngSBModel) entry.getValue();
                String subOid = convertSubModel(temp, objects, info);
                object.putEDBObjectEntry(entry.getKey(), subOid, String.class);
            } else if (List.class.isAssignableFrom(entry.getType())) {
                List<?> list = (List<?>) entry.getValue();
                if (list == null || list.size() == 0) {
                    continue;
                }
                Boolean modelItems = null;
                for (int i = 0; i < list.size(); i++) {
                    Object item = list.get(i);
                    if (modelItems == null) {
                        modelItems = OpenEngSBModel.class.isAssignableFrom(item.getClass());
                    }
                    if (modelItems) {
                        item = convertSubModel((OpenEngSBModel) item, objects, info);
                    }
                    String entryName = getEntryNameForList(entry.getKey(), i);
                    object.putEDBObjectEntry(entryName, item, item.getClass());
                }
            } else if (entry.getType().isArray()) {
                Object[] array = (Object[]) entry.getValue();
                if (array == null || array.length == 0) {
                    continue;
                }
                Boolean modelItems = null;
                for (int i = 0; i < array.length; i++) {
                    Object item = array[i];
                    if (modelItems == null) {
                        modelItems = OpenEngSBModel.class.isAssignableFrom(item.getClass());
                    }
                    if (modelItems) {
                        item = convertSubModel((OpenEngSBModel) item, objects, info);
                    }
                    String entryName = getEntryNameForList(entry.getKey(), i);
                    object.putEDBObjectEntry(entryName, item, item.getClass());
                }
            } else if (Map.class.isAssignableFrom(entry.getType())) {
                Map<?, ?> map = (Map<?, ?>) entry.getValue();
                if (map == null || map.size() == 0) {
                    continue;
                }
                Boolean keyIsModel = null;
                Boolean valueIsModel = null;
                int i = 0;
                for (Map.Entry<?, ?> ent : map.entrySet()) {
                    if (keyIsModel == null) {
                        keyIsModel = OpenEngSBModel.class.isAssignableFrom(ent.getKey().getClass());
                    }
                    if (valueIsModel == null) {
                        valueIsModel = OpenEngSBModel.class.isAssignableFrom(ent.getValue().getClass());
                    }
                    Object key = ent.getKey();
                    Object value = ent.getValue();
                    if (keyIsModel) {
                        key = convertSubModel((OpenEngSBModel) key, objects, info);
                    }
                    if (valueIsModel) {
                        value = convertSubModel((OpenEngSBModel) value, objects, info);
                    }
                    object.putEDBObjectEntry(getEntryNameForMapKey(entry.getKey(), i), key);
                    object.putEDBObjectEntry(getEntryNameForMapValue(entry.getKey(), i), value);
                    i++;
                }
            } else {
                object.putEDBObjectEntry(entry.getKey(), entry.getValue(), entry.getType());
            }
        }
        object.putEDBObjectEntry(EDBConstants.MODEL_TYPE, model.retrieveModelName());
        object.putEDBObjectEntry(EDBConstants.MODEL_TYPE_VERSION, model.retrieveModelVersion());
        object.putEDBObjectEntry("domainId", info.getDomainId());
        object.putEDBObjectEntry("connectorId", info.getConnectorId());
        object.putEDBObjectEntry("instanceId", info.getInstanceId());
        object.putEDBObjectEntry("contextId", contextId);
        objects.add(object);
        return oid;
    }

    /**
     * Adds to the EDBObject special entries which mark that a model is referring to other models through
     * OpenEngSBForeignKey annotations
     */
    private void fillEDBObjectWithEngineeringObjectInformation(EDBObject object, OpenEngSBModel model)
        throws IllegalAccessException {
        if (!new AdvancedModelWrapper(model).isEngineeringObject()) {
            return;
        }
        for (Field field : model.getClass().getDeclaredFields()) {
            OpenEngSBForeignKey annotation = field.getAnnotation(OpenEngSBForeignKey.class);
            if (annotation == null) {
                continue;
            }
            String value = (String) FieldUtils.readField(field, model, true);
            if (value == null) {
                continue;
            }
            value = String.format("%s/%s", ContextHolder.get().getCurrentContextId(), value);
            String key = getEOReferenceStringFromAnnotation(annotation);
            object.put(key, new EDBObjectEntry(key, value, String.class));
        }
    }

    /**
     * Filters the reference prefix values added in the model to EDBObject conversion out of the EDBObject
     */
    private void filterEngineeringObjectInformation(EDBObject object, Class<?> model) {
        if (!AdvancedModelWrapper.isEngineeringObjectClass(model)) {
            return;
        }
        Iterator<String> keys = object.keySet().iterator();
        while (keys.hasNext()) {
            if (keys.next().startsWith(REFERENCE_PREFIX)) {
                keys.remove();
            }
        }
    }

    /**
     * Returns the entry name for a map key in the EDB format. E.g. the map key for the property "map" with the index 0
     * would be "map.0.key".
     */
    public static String getEntryNameForMapKey(String property, Integer index) {
        return getEntryNameForMap(property, true, index);
    }

    /**
     * Returns the entry name for a map value in the EDB format. E.g. the map value for the property "map" with the
     * index 0 would be "map.0.value".
     */
    public static String getEntryNameForMapValue(String property, Integer index) {
        return getEntryNameForMap(property, false, index);
    }

    /**
     * Returns the entry name for a map element in the EDB format. The key parameter defines if the entry name should be
     * generated for the key or the value of the map. E.g. the map key for the property "map" with the index 0 would be
     * "map.0.key".
     */
    private static String getEntryNameForMap(String property, Boolean key, Integer index) {
        return String.format("%s.%d.%s", property, index, key ? "key" : "value");
    }

    /**
     * Returns the entry name for a list element in the EDB format. E.g. the list element for the property "list" with
     * the index 0 would be "list.0".
     */
    public static String getEntryNameForList(String property, Integer index) {
        return String.format("%s.%d", property, index);
    }

    /**
     * Converts an OpenEngSBForeignKey annotation to the fitting format which will be added to an EDBObject.
     */
    public static String getEOReferenceStringFromAnnotation(OpenEngSBForeignKey key) {
        return String.format("%s%s:%s", REFERENCE_PREFIX, key.modelType(), key.modelVersion().toString());
    }
}
TOP

Related Classes of org.openengsb.core.ekb.common.EDBConverter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.