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

org.bonitasoft.engine.business.data.impl.BusinessDataServiceImpl Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2019 Bonitasoft S.A.
 * Bonitasoft, 32 rue Gustave Eiffel - 38000 Grenoble
 * 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
 * version 2.1 of the License.
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 **/
package org.bonitasoft.engine.business.data.impl;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

import org.apache.commons.lang3.StringUtils;
import org.bonitasoft.engine.bdm.BDMQueryUtil;
import org.bonitasoft.engine.bdm.Entity;
import org.bonitasoft.engine.bdm.model.BusinessObject;
import org.bonitasoft.engine.bdm.model.BusinessObjectModel;
import org.bonitasoft.engine.bdm.model.Query;
import org.bonitasoft.engine.bdm.model.QueryParameter;
import org.bonitasoft.engine.bdm.model.field.RelationField.Type;
import org.bonitasoft.engine.bpm.businessdata.BusinessDataQueryResult;
import org.bonitasoft.engine.bpm.businessdata.impl.BusinessDataQueryMetadataImpl;
import org.bonitasoft.engine.bpm.businessdata.impl.BusinessDataQueryResultImpl;
import org.bonitasoft.engine.business.data.BusinessDataModelRepository;
import org.bonitasoft.engine.business.data.BusinessDataRepository;
import org.bonitasoft.engine.business.data.BusinessDataService;
import org.bonitasoft.engine.business.data.JsonBusinessDataSerializer;
import org.bonitasoft.engine.business.data.NonUniqueResultException;
import org.bonitasoft.engine.business.data.SBusinessDataNotFoundException;
import org.bonitasoft.engine.business.data.SBusinessDataRepositoryException;
import org.bonitasoft.engine.commons.ClassReflector;
import org.bonitasoft.engine.commons.JavaMethodInvoker;
import org.bonitasoft.engine.commons.TypeConverterUtil;
import org.bonitasoft.engine.commons.exceptions.SReflectException;

public class BusinessDataServiceImpl implements BusinessDataService {

    private final BusinessDataRepository businessDataRepository;

    private final JsonBusinessDataSerializer jsonBusinessDataSerializer;

    private final BusinessDataModelRepository businessDataModelRepository;

    private final TypeConverterUtil typeConverterUtil;

    private final BusinessDataReloader businessDataReloader;

    private final CountQueryProvider countQueryProvider;

    public BusinessDataServiceImpl(final BusinessDataRepository businessDataRepository,
            final JsonBusinessDataSerializer jsonBusinessDataSerializer,
            final BusinessDataModelRepository businessDataModelRepository, final TypeConverterUtil typeConverterUtil,
            BusinessDataReloader businessDataReloader, CountQueryProvider countQueryProvider) {
        this.businessDataRepository = businessDataRepository;
        this.jsonBusinessDataSerializer = jsonBusinessDataSerializer;
        this.businessDataModelRepository = businessDataModelRepository;
        this.typeConverterUtil = typeConverterUtil;
        this.businessDataReloader = businessDataReloader;
        this.countQueryProvider = countQueryProvider;
    }

    @Override
    public boolean isBusinessData(final Object data) {
        return isEntity(data) || isListOfEntities(data);
    }

    private boolean isListOfEntities(final Object data) {
        if (data == null) {
            return false;
        }
        if (!List.class.isAssignableFrom(data.getClass())) {
            return false;
        }
        @SuppressWarnings("rawtypes")
        final List dataList = (List) data;
        if (dataList.isEmpty()) {
            return true;
        }
        return isEntity(dataList.get(0));

    }

    private boolean isEntity(final Object data) {
        if (data == null) {
            return false;
        }
        return Entity.class.isAssignableFrom(data.getClass());
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object callJavaOperation(final Object businessObject, final Object valueToSetObjectWith,
            final String methodName, final String parameterType)
            throws SBusinessDataNotFoundException, SBusinessDataRepositoryException {
        if (businessObject == null) {
            throw new SBusinessDataNotFoundException("business data is null");
        }
        if (isEntity(businessObject)) {
            return callJavaOperationOnEntity((Entity) businessObject, valueToSetObjectWith, methodName, parameterType);
        }
        if (isListOfEntities(businessObject)) {
            return callJavaOperationOnEntityList((List) businessObject, valueToSetObjectWith, methodName,
                    parameterType);
        }
        throw new SBusinessDataRepositoryException("not a business data");
    }

    private Object callJavaOperationOnEntityList(final List businessObject, final Object valueToSetObjectWith,
            final String methodName,
            final String parameterType)
            throws SBusinessDataRepositoryException, SBusinessDataNotFoundException {
        try {
            invokeJavaMethod(businessObject, methodName, parameterType, valueToSetObjectWith);
            return businessObject;
        } catch (final Exception e) {
            throw new SBusinessDataRepositoryException(e);
        }
    }

    private Object callJavaOperationOnEntity(final Entity businessObject, final Object valueToSetObjectWith,
            final String methodName,
            final String parameterType)
            throws SBusinessDataRepositoryException, SBusinessDataNotFoundException {
        Entity jpaEntity = businessDataReloader.reloadEntitySoftly(businessObject);
        final Object valueToSet = loadValueToSet(businessObject, valueToSetObjectWith, methodName);
        try {
            invokeJavaMethod(jpaEntity, methodName, parameterType, valueToSet);
            // TODO Auto-generated method stub
            return jpaEntity;
        } catch (final Exception e) {
            throw new SBusinessDataRepositoryException(e);
        }
    }

    protected void invokeJavaMethod(final Object objectToSet, final String methodName, final String parameterType,
            final Object valueToSet)
            throws ClassNotFoundException,
            NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        final JavaMethodInvoker methodInvoker = new JavaMethodInvoker();
        methodInvoker.invokeJavaMethod(parameterType, valueToSet, objectToSet, methodName, parameterType);
    }

    @SuppressWarnings("unchecked")
    private Object loadValueToSet(final Entity businessObject, final Object valueToSetObjectWith,
            final String methodName)
            throws SBusinessDataNotFoundException, SBusinessDataRepositoryException {
        Object valueToSet;
        if (isEntity(valueToSetObjectWith)) {
            final Type relationType = getRelationType(businessObject, methodName);
            valueToSet = getPersistedValue((Entity) valueToSetObjectWith, relationType);
        } else if (isListOfEntities(valueToSetObjectWith)) {
            final Type relationType = getRelationType(businessObject, methodName);
            valueToSet = getPersistedValues((List) valueToSetObjectWith, relationType);
        } else {
            valueToSet = valueToSetObjectWith;
        }
        return valueToSet;
    }

    private List getPrimaryKeys(final List entities) throws SBusinessDataNotFoundException {
        List primaryKeys;
        primaryKeys = new ArrayList<>();
        for (final Entity entity : entities) {
            if (entity.getPersistenceId() == null) {
                throw new SBusinessDataNotFoundException(String.format(
                        "Forbidden instance of %s found. It is only possible to reference persisted instances in an aggregation relation.",
                        businessDataReloader.getEntityRealClass(entity).getName()));
            }
            primaryKeys.add(entity.getPersistenceId());
        }
        return primaryKeys;
    }

    private Object getPersistedValues(final List entities, final Type type)
            throws SBusinessDataNotFoundException {
        if (entities.isEmpty()) {
            return new ArrayList();
        }
        if (Type.AGGREGATION.equals(type)) {
            return businessDataRepository.findByIds(businessDataReloader.getEntityRealClass(entities.get(0)),
                    getPrimaryKeys(entities));
        } else {
            return entities;
        }
    }

    private Entity getPersistedValue(final Entity entity, final Type type) throws SBusinessDataNotFoundException {
        if (Type.AGGREGATION.equals(type)) {
            try {
                return businessDataReloader.reloadEntity(entity);
            } catch (SBusinessDataNotFoundException e) {
                throw new SBusinessDataNotFoundException(String.format(
                        "Forbidden instance of %s found. It is only possible to reference persisted instances in an aggregation relation.",
                        businessDataReloader.getEntityRealClass(entity).getName()), e);
            }
        } else {
            return entity;
        }
    }

    private Type getRelationType(final Entity businessObject, final String methodName)
            throws SBusinessDataRepositoryException {
        final String fieldName = ClassReflector.getFieldName(methodName);
        Annotation[] annotations;
        try {
            annotations = businessObject.getClass().getDeclaredField(fieldName).getAnnotations();
        } catch (final NoSuchFieldException e) {
            return null;
        } catch (final SecurityException e) {
            throw new SBusinessDataRepositoryException(e);
        }
        for (final Annotation annotation : annotations) {
            final Set> annotationKeySet = getAnnotationKeySet();
            if (annotationKeySet.contains(annotation.annotationType())) {
                try {
                    final Method cascade = annotation.getClass().getMethod("cascade");
                    final CascadeType[] cascadeTypes = (CascadeType[]) cascade.invoke(annotation);
                    if (CascadeType.MERGE.equals(cascadeTypes[0])) {
                        return Type.AGGREGATION;
                    }
                    if (CascadeType.ALL.equals(cascadeTypes[0])) {
                        return Type.COMPOSITION;
                    }
                } catch (final Exception e) {
                    throw new SBusinessDataRepositoryException(e);
                }
            }
        }
        return null;
    }

    private Set> getAnnotationKeySet() {
        // FIXME use custom annotation on methods
        final Set> annotationKeySet = new HashSet<>();
        annotationKeySet.add(OneToOne.class);
        annotationKeySet.add(OneToMany.class);
        annotationKeySet.add(ManyToMany.class);
        annotationKeySet.add(ManyToOne.class);
        return annotationKeySet;
    }

    @Override
    public Serializable getJsonEntity(final String entityClassName, final Long identifier,
            final String businessDataURIPattern)
            throws SBusinessDataNotFoundException, SBusinessDataRepositoryException {
        final Class entityClass = loadClass(entityClassName);
        final Entity entity = businessDataRepository.findById(entityClass, identifier);
        return jsonBusinessDataSerializer.serializeEntity(entity, businessDataURIPattern);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Serializable getJsonEntities(final String entityClassName, final List identifiers,
            final String businessDataURIPattern)
            throws SBusinessDataRepositoryException {
        final Class entityClass = loadClass(entityClassName);
        final List entities = businessDataRepository.findByIdentifiers(entityClass, identifiers);
        return jsonBusinessDataSerializer.serializeEntities(entities, businessDataURIPattern);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Serializable getJsonChildEntity(final String entityClassName, final Long identifier,
            final String childFieldName,
            final String businessDataURIPattern)
            throws SBusinessDataNotFoundException, SBusinessDataRepositoryException {
        final Class entityClass = loadClass(entityClassName);
        final Object entity = businessDataRepository.findById(entityClass, identifier);

        Object childEntity;
        java.lang.reflect.Type getterReturnType;
        try {
            final String getterName = ClassReflector.getGetterName(childFieldName);
            childEntity = ClassReflector.invokeGetter(entity, getterName);
            getterReturnType = ClassReflector.getGetterReturnType(entityClass, getterName);
        } catch (final SReflectException e) {
            throw new SBusinessDataRepositoryException(e);
        }

        if (childEntity == null) {
            return JsonBusinessDataSerializer.EMPTY_OBJECT;
        }
        if (childEntity instanceof Entity) {
            final Entity unwrap = businessDataRepository.unwrap((Entity) childEntity);
            return jsonBusinessDataSerializer.serializeEntity(unwrap, businessDataURIPattern);
        } else if (childEntity instanceof List) {
            final Class type = (Class) ((ParameterizedType) getterReturnType).getActualTypeArguments()[0];
            if (Entity.class.isAssignableFrom(type)) {
                return jsonBusinessDataSerializer.serializeEntities((List) childEntity, businessDataURIPattern);
            }
        }
        return null;
    }

    @Override
    public BusinessDataQueryResult getJsonQueryEntities(final String entityClassName, final String queryName,
            final Map parameters,
            final Integer startIndex, final Integer maxResults, final String businessDataURIPattern)
            throws SBusinessDataRepositoryException {
        final Class businessDataClass = loadClass(entityClassName);
        BusinessObject businessObject = getBusinessObjectFromClassName(entityClassName);
        final Query queryDefinition = getQueryDefinition(businessObject, entityClassName, queryName);
        final Map queryParameters = getQueryParameters(queryDefinition, parameters);
        final List list = businessDataRepository.findListByNamedQuery(
                getQualifiedQueryName(businessDataClass, queryName),
                getQueryReturnType(queryDefinition, entityClassName), queryParameters, startIndex, maxResults);

        BusinessDataQueryMetadataImpl businessDataQueryMetadata = null;
        final Query countQueryDefinition = getCountQueryDefinition(businessDataClass, businessObject, queryDefinition);
        if (countQueryDefinition != null) {
            try {
                businessDataQueryMetadata = new BusinessDataQueryMetadataImpl(startIndex, maxResults,
                        (Long) businessDataRepository.findByNamedQuery(
                                getQualifiedQueryName(businessDataClass, countQueryDefinition.getName()),
                                getQueryReturnType(countQueryDefinition, entityClassName), queryParameters));
            } catch (NonUniqueResultException e) {
                throw new SBusinessDataRepositoryException("unable to count results for query " + queryName);
            }
        }
        Serializable jsonResults;
        if (queryDefinition.isCountQuery()) {
            jsonResults = jsonBusinessDataSerializer.serializeCountResult((List) list, entityClassName);
        } else {
            jsonResults = jsonBusinessDataSerializer.serializeEntities((List) list, businessDataURIPattern);
        }
        return new BusinessDataQueryResultImpl(jsonResults, businessDataQueryMetadata);
    }

    private Query getCountQueryDefinition(Class businessDataClass, BusinessObject businessObject,
            Query queryDefinition) {
        final Query countQueryDefinition = countQueryProvider.getCountQueryDefinition(businessObject, queryDefinition);
        if (ensureQueryIsDefinedInEntity(businessDataClass, countQueryDefinition)) {
            return countQueryDefinition;
        }
        return null;
    }

    private boolean ensureQueryIsDefinedInEntity(Class businessDataClass,
            Query countQueryDefinition) {
        final NamedQueries namedQueries = businessDataClass.getAnnotation(NamedQueries.class);
        if (namedQueries == null || countQueryDefinition == null) {
            return false;
        }
        for (NamedQuery namedQuery : namedQueries.value()) {
            if (namedQuery.name().equals(getQualifiedQueryName(businessDataClass, countQueryDefinition.getName()))) {
                return true;
            }
        }
        return false;
    }

    private Class getQueryReturnType(final Query queryDefinition, final String entityClassName)
            throws SBusinessDataRepositoryException {
        if (queryDefinition.hasMultipleResults()) {
            return loadClass(entityClassName);
        }
        try {
            return (Class) Thread.currentThread().getContextClassLoader()
                    .loadClass(queryDefinition.getReturnType());
        } catch (final ClassNotFoundException e) {
            throw new SBusinessDataRepositoryException("unable to load class " + queryDefinition.getReturnType());
        }
    }

    private String getQualifiedQueryName(final Class businessDataClass, final String queryName) {
        return String.format("%s.%s", businessDataClass.getSimpleName(), queryName);
    }

    private Map getQueryParameters(final Query queryDefinition,
            final Map parameters)
            throws SBusinessDataRepositoryException {
        final Set errors = new HashSet<>();
        final Map queryParameters = new HashMap<>();
        for (final QueryParameter queryParameter : queryDefinition.getQueryParameters()) {
            if (parameters != null && parameters.containsKey(queryParameter.getName())) {
                queryParameters.put(queryParameter.getName(),
                        convertToType(loadSerializableClass(queryParameter.getClassName()),
                                parameters.get(queryParameter.getName())));
            } else {
                errors.add(queryParameter.getName());
            }
        }
        if (!errors.isEmpty()) {
            final StringBuilder errorMessage = new StringBuilder().append("parameter(s) are missing for query named ")
                    .append(queryDefinition.getName())
                    .append(" : ");
            errorMessage.append(StringUtils.join(errors, ","));
            throw new SBusinessDataRepositoryException(errorMessage.toString());
        }
        return queryParameters;
    }

    private Serializable convertToType(final Class clazz, final Serializable parameterValue) {
        return (Serializable) typeConverterUtil.convertToType(clazz, parameterValue);
    }

    private Query getQueryDefinition(BusinessObject businessObject, String className, final String queryName)
            throws SBusinessDataRepositoryException {
        final List allQueries = new ArrayList<>();
        allQueries.addAll(businessObject.getQueries());
        allQueries.addAll(BDMQueryUtil.createProvidedQueriesForBusinessObject(businessObject));
        for (final Query query : allQueries) {
            if (query.getName().equals(queryName)) {
                return query;
            }
        }
        throw new SBusinessDataRepositoryException(
                "unable to get query " + queryName + " for business object " + className);
    }

    private BusinessObject getBusinessObjectFromClassName(String className) throws SBusinessDataRepositoryException {
        BusinessObject businessObject = null;
        final BusinessObjectModel businessObjectModel = businessDataModelRepository.getBusinessObjectModel();
        if (businessObjectModel != null) {
            for (final BusinessObject currentBo : businessObjectModel.getBusinessObjects()) {
                if (currentBo.getQualifiedName().equals(className)) {
                    businessObject = currentBo;
                }
            }
        }
        return businessObject;
    }

    @SuppressWarnings("unchecked")
    protected Class loadClass(final String returnType) throws SBusinessDataRepositoryException {
        try {
            return (Class) Thread.currentThread().getContextClassLoader().loadClass(returnType);
        } catch (final ClassNotFoundException e) {
            throw new SBusinessDataRepositoryException(e);
        }
    }

    protected Class loadSerializableClass(final String className)
            throws SBusinessDataRepositoryException {
        try {
            return (Class) Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (final ClassNotFoundException e) {
            throw new SBusinessDataRepositoryException("unable to load class " + className);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy