com.haulmont.cuba.core.app.RdbmsStore.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.core.app.RdbmsStore.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * 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 com.haulmont.cuba.core.app;

import com.haulmont.bali.util.Preconditions;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.chile.core.model.Session;
import com.haulmont.chile.core.model.impl.AbstractInstance;
import com.haulmont.cuba.core.*;
import com.haulmont.cuba.core.app.dynamicattributes.DynamicAttributesManagerAPI;
import com.haulmont.cuba.core.app.queryresults.QueryResultsManagerAPI;
import com.haulmont.cuba.core.entity.*;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.core.sys.EntityFetcher;
import com.haulmont.cuba.security.entity.ConstraintOperationType;
import com.haulmont.cuba.security.entity.EntityAttrAccess;
import com.haulmont.cuba.security.entity.EntityOp;
import com.haulmont.cuba.security.entity.PermissionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.persistence.NoResultException;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static org.apache.commons.lang.StringUtils.isBlank;

/**
 * INTERNAL.
 * Implementation of the {@link DataStore} interface working with a relational database through ORM.
 */
@Component(RdbmsStore.NAME)
@Scope("prototype")
public class RdbmsStore implements DataStore {

    public static final String NAME = "cuba_RdbmsStore";

    private static final Logger log = LoggerFactory.getLogger(RdbmsStore.class);

    @Inject
    protected Metadata metadata;

    @Inject
    protected ViewRepository viewRepository;

    @Inject
    protected ServerConfig serverConfig;

    @Inject
    protected PersistenceSecurity security;

    @Inject
    protected AttributeSecuritySupport attributeSecurity;

    @Inject
    protected Persistence persistence;

    @Inject
    protected UserSessionSource userSessionSource;

    @Inject
    protected QueryResultsManagerAPI queryResultsManager;

    @Inject
    protected EntityLoadInfoBuilder entityLoadInfoBuilder;

    @Inject
    protected DynamicAttributesManagerAPI dynamicAttributesManagerAPI;

    @Inject
    protected QueryTransformerFactory queryTransformerFactory;

    @Inject
    protected EntityFetcher entityFetcher;

    protected String storeName;

    public RdbmsStore(String storeName) {
        this.storeName = storeName;
    }

    @Nullable
    @Override
    public <E extends Entity> E load(LoadContext<E> context) {
        if (log.isDebugEnabled()) {
            log.debug("load: metaClass={}, id={}, view={}", context.getMetaClass(), context.getId(),
                    context.getView());
        }

        final MetaClass metaClass = metadata.getSession().getClassNN(context.getMetaClass());

        if (!isEntityOpPermitted(metaClass, EntityOp.READ)) {
            log.debug("reading of {} not permitted, returning null", metaClass);
            return null;
        }

        E result = null;
        boolean needToApplyConstraints = needToApplyByPredicate(context);
        try (Transaction tx = createLoadTransaction()) {
            final EntityManager em = persistence.getEntityManager(storeName);

            if (!context.isSoftDeletion())
                em.setSoftDeletion(false);
            persistence.getEntityManagerContext(storeName).setDbHints(context.getDbHints());

            // If maxResults=1 and the query is not by ID we should not use getSingleResult() for backward compatibility
            boolean singleResult = !(context.getQuery() != null && context.getQuery().getMaxResults() == 1
                    && context.getQuery().getQueryString() != null);

            View view = createRestrictedView(context);
            com.haulmont.cuba.core.Query query = createQuery(em, context, singleResult);
            query.setView(view);

            //noinspection unchecked
            List<E> resultList = executeQuery(query, singleResult);
            if (!resultList.isEmpty()) {
                result = resultList.get(0);
            }

            if (result != null && needToApplyInMemoryReadConstraints(context)
                    && security.filterByConstraints(result)) {
                result = null;
            }

            if (result instanceof BaseGenericIdEntity && context.isLoadDynamicAttributes()) {
                dynamicAttributesManagerAPI.fetchDynamicAttributes(
                        Collections.singletonList((BaseGenericIdEntity) result),
                        collectEntityClassesWithDynamicAttributes(context.getView()));
            }

            if (result != null && needToApplyConstraints) {
                security.calculateFilteredData(result);
            }

            if (result != null) {
                attributeSecurity.onLoad(result, view);
            }

            tx.commit();
        }

        if (result != null) {
            if (needToApplyConstraints) {
                security.applyConstraints(result);
            }
            attributeSecurity.afterLoad(result);
        }

        return result;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <E extends Entity> List<E> loadList(LoadContext<E> context) {
        if (log.isDebugEnabled())
            log.debug("loadList: metaClass=" + context.getMetaClass() + ", view=" + context.getView()
                    + (context.getPrevQueries().isEmpty() ? "" : ", from selected") + ", query="
                    + (context.getQuery() == null ? null
                            : DataServiceQueryBuilder.printQuery(context.getQuery().getQueryString()))
                    + (context.getQuery() == null || context.getQuery().getFirstResult() == 0 ? ""
                            : ", first=" + context.getQuery().getFirstResult())
                    + (context.getQuery() == null || context.getQuery().getMaxResults() == 0 ? ""
                            : ", max=" + context.getQuery().getMaxResults()));

        MetaClass metaClass = metadata.getClassNN(context.getMetaClass());

        if (!isEntityOpPermitted(metaClass, EntityOp.READ)) {
            log.debug("reading of {} not permitted, returning empty list", metaClass);
            return Collections.emptyList();
        }

        queryResultsManager.savePreviousQueryResults(context);

        List<E> resultList;
        boolean needToApplyConstraints = needToApplyByPredicate(context);
        try (Transaction tx = createLoadTransaction()) {
            EntityManager em = persistence.getEntityManager(storeName);
            em.setSoftDeletion(context.isSoftDeletion());
            persistence.getEntityManagerContext(storeName).setDbHints(context.getDbHints());

            boolean ensureDistinct = false;
            if (serverConfig.getInMemoryDistinct() && context.getQuery() != null) {
                QueryTransformer transformer = queryTransformerFactory
                        .transformer(context.getQuery().getQueryString());
                ensureDistinct = transformer.removeDistinct();
                if (ensureDistinct) {
                    context.getQuery().setQueryString(transformer.getResult());
                }
            }
            View view = createRestrictedView(context);
            Query query = createQuery(em, context, false);
            query.setView(view);

            resultList = getResultList(context, query, ensureDistinct);

            // Fetch dynamic attributes
            if (!resultList.isEmpty() && resultList.get(0) instanceof BaseGenericIdEntity
                    && context.isLoadDynamicAttributes()) {
                dynamicAttributesManagerAPI.fetchDynamicAttributes((List<BaseGenericIdEntity>) resultList,
                        collectEntityClassesWithDynamicAttributes(context.getView()));
            }

            if (needToApplyConstraints) {
                security.calculateFilteredData((Collection<Entity>) resultList);
            }

            attributeSecurity.onLoad(resultList, view);

            tx.commit();
        }

        if (needToApplyConstraints) {
            security.applyConstraints((Collection<Entity>) resultList);
        }

        attributeSecurity.afterLoad(resultList);

        return resultList;
    }

    @Override
    public long getCount(LoadContext<? extends Entity> context) {
        if (log.isDebugEnabled())
            log.debug("getCount: metaClass=" + context.getMetaClass()
                    + (context.getPrevQueries().isEmpty() ? "" : ", from selected") + ", query="
                    + (context.getQuery() == null ? null
                            : DataServiceQueryBuilder.printQuery(context.getQuery().getQueryString())));

        MetaClass metaClass = metadata.getClassNN(context.getMetaClass());

        if (!isEntityOpPermitted(metaClass, EntityOp.READ)) {
            log.debug("reading of {} not permitted, returning 0", metaClass);
            return 0;
        }

        queryResultsManager.savePreviousQueryResults(context);

        if (security.hasInMemoryConstraints(metaClass, ConstraintOperationType.READ, ConstraintOperationType.ALL)) {
            context = context.copy();
            List resultList;
            try (Transaction tx = createLoadTransaction()) {
                EntityManager em = persistence.getEntityManager(storeName);
                em.setSoftDeletion(context.isSoftDeletion());
                persistence.getEntityManagerContext(storeName).setDbHints(context.getDbHints());

                boolean ensureDistinct = false;
                if (serverConfig.getInMemoryDistinct() && context.getQuery() != null) {
                    QueryTransformer transformer = QueryTransformerFactory
                            .createTransformer(context.getQuery().getQueryString());
                    ensureDistinct = transformer.removeDistinct();
                    if (ensureDistinct) {
                        context.getQuery().setQueryString(transformer.getResult());
                    }
                }
                context.getQuery().setFirstResult(0);
                context.getQuery().setMaxResults(0);

                Query query = createQuery(em, context, false);
                query.setView(createRestrictedView(context));

                resultList = getResultList(context, query, ensureDistinct);

                tx.commit();
            }
            return resultList.size();
        } else {
            QueryTransformer transformer = QueryTransformerFactory
                    .createTransformer(context.getQuery().getQueryString());
            transformer.replaceWithCount();
            context = context.copy();
            context.getQuery().setQueryString(transformer.getResult());

            Number result;
            try (Transaction tx = createLoadTransaction()) {
                EntityManager em = persistence.getEntityManager(storeName);
                em.setSoftDeletion(context.isSoftDeletion());
                persistence.getEntityManagerContext(storeName).setDbHints(context.getDbHints());

                Query query = createQuery(em, context, false);
                result = (Number) query.getSingleResult();

                tx.commit();
            }

            return result.longValue();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Set<Entity> commit(CommitContext context) {
        if (log.isDebugEnabled())
            log.debug("commit: commitInstances=" + context.getCommitInstances() + ", removeInstances="
                    + context.getRemoveInstances());

        Set<Entity> res = new HashSet<>();
        List<Entity> persisted = new ArrayList<>();
        List<BaseGenericIdEntity> identityEntitiesToStoreDynamicAttributes = new ArrayList<>();
        List<CategoryAttributeValue> attributeValuesToRemove = new ArrayList<>();

        try (Transaction tx = persistence.createTransaction(storeName)) {
            EntityManager em = persistence.getEntityManager(storeName);
            checkPermissions(context);

            if (!context.isSoftDeletion())
                em.setSoftDeletion(false);

            persistence.getEntityManagerContext(storeName).setDbHints(context.getDbHints());

            List<BaseGenericIdEntity> entitiesToStoreDynamicAttributes = new ArrayList<>();

            // persist new
            for (Entity entity : context.getCommitInstances()) {
                if (PersistenceHelper.isNew(entity)) {
                    attributeSecurity.beforePersist(entity);
                    em.persist(entity);
                    checkOperationPermitted(entity, ConstraintOperationType.CREATE);
                    if (!context.isDiscardCommitted()) {
                        entityFetcher.fetch(entity, getViewFromContextOrNull(context, entity), true);
                        res.add(entity);
                    }
                    persisted.add(entity);

                    if (entityHasDynamicAttributes(entity)) {
                        if (entity instanceof BaseDbGeneratedIdEntity) {
                            identityEntitiesToStoreDynamicAttributes.add((BaseGenericIdEntity) entity);
                        } else {
                            entitiesToStoreDynamicAttributes.add((BaseGenericIdEntity) entity);
                        }
                    }
                }
            }

            // merge the rest - instances can be detached or not
            for (Entity entity : context.getCommitInstances()) {
                if (!PersistenceHelper.isNew(entity)) {
                    if (isAuthorizationRequired()) {
                        security.checkSecurityToken(entity, null);
                    }
                    security.restoreSecurityStateAndFilteredData(entity);
                    attributeSecurity.beforeMerge(entity);

                    Entity merged = em.merge(entity);
                    entityFetcher.fetch(merged, getViewFromContext(context, entity));
                    attributeSecurity.afterMerge(merged);

                    checkOperationPermitted(merged, ConstraintOperationType.UPDATE);
                    if (!context.isDiscardCommitted()) {
                        res.add(merged);
                    }
                    if (entityHasDynamicAttributes(entity)) {
                        BaseGenericIdEntity originalBaseGenericIdEntity = (BaseGenericIdEntity) entity;
                        BaseGenericIdEntity mergedBaseGenericIdEntity = (BaseGenericIdEntity) merged;
                        mergedBaseGenericIdEntity
                                .setDynamicAttributes(originalBaseGenericIdEntity.getDynamicAttributes());
                        entitiesToStoreDynamicAttributes.add(mergedBaseGenericIdEntity);
                    }
                }
            }

            for (BaseGenericIdEntity entity : entitiesToStoreDynamicAttributes) {
                dynamicAttributesManagerAPI.storeDynamicAttributes(entity);
            }

            // remove
            for (Entity entity : context.getRemoveInstances()) {
                if (isAuthorizationRequired()) {
                    security.checkSecurityToken(entity, null);
                }
                security.restoreSecurityStateAndFilteredData(entity);

                Entity e;
                if (entity instanceof SoftDelete) {
                    attributeSecurity.beforeMerge(entity);
                    e = em.merge(entity);
                    entityFetcher.fetch(e, getViewFromContext(context, entity));
                    attributeSecurity.afterMerge(e);
                } else {
                    e = em.merge(entity);
                }
                checkOperationPermitted(e, ConstraintOperationType.DELETE);
                em.remove(e);
                if (!context.isDiscardCommitted()) {
                    res.add(e);
                }

                if (entityHasDynamicAttributes(entity)) {
                    Map<String, CategoryAttributeValue> dynamicAttributes = ((BaseGenericIdEntity) entity)
                            .getDynamicAttributes();

                    //dynamicAttributes checked for null in entityHasDynamicAttributes()
                    //noinspection ConstantConditions
                    for (CategoryAttributeValue categoryAttributeValue : dynamicAttributes.values()) {
                        if (!PersistenceHelper.isNew(categoryAttributeValue)) {
                            if (Stores.isMain(storeName)) {
                                em.remove(categoryAttributeValue);
                            } else {
                                attributeValuesToRemove.add(categoryAttributeValue);
                            }
                            if (!context.isDiscardCommitted()) {
                                res.add(categoryAttributeValue);
                            }
                        }
                    }
                }

                if (!context.isDiscardCommitted() && isAuthorizationRequired()
                        && userSessionSource.getUserSession().hasConstraints()) {
                    security.filterByConstraints(res);
                }
            }

            tx.commit();
        }

        if (!attributeValuesToRemove.isEmpty()) {
            try (Transaction tx = persistence.createTransaction()) {
                EntityManager em = persistence.getEntityManager();
                for (CategoryAttributeValue entity : attributeValuesToRemove) {
                    em.remove(entity);
                }
                tx.commit();
            }
        }

        try (Transaction tx = persistence.createTransaction(storeName)) {
            for (BaseGenericIdEntity entity : identityEntitiesToStoreDynamicAttributes) {
                dynamicAttributesManagerAPI.storeDynamicAttributes(entity);
            }
            tx.commit();
        }

        if (!context.isDiscardCommitted() && isAuthorizationRequired()
                && userSessionSource.getUserSession().hasConstraints()) {
            security.applyConstraints(res);
        }

        if (!context.isDiscardCommitted()) {
            for (Entity entity : res) {
                if (!persisted.contains(entity)) {
                    attributeSecurity.afterCommit(entity);
                }
            }
            updateReferences(persisted, res);
        }

        return res;
    }

    @Override
    public List<KeyValueEntity> loadValues(ValueLoadContext context) {
        Preconditions.checkNotNullArgument(context, "context is null");
        Preconditions.checkNotNullArgument(context.getQuery(), "query is null");

        ValueLoadContext.Query contextQuery = context.getQuery();

        if (log.isDebugEnabled())
            log.debug("query: " + (DataServiceQueryBuilder.printQuery(contextQuery.getQueryString()))
                    + (contextQuery.getFirstResult() == 0 ? "" : ", first=" + contextQuery.getFirstResult())
                    + (contextQuery.getMaxResults() == 0 ? "" : ", max=" + contextQuery.getMaxResults()));

        QueryParser queryParser = queryTransformerFactory.parser(contextQuery.getQueryString());
        if (!checkValueQueryPermissions(queryParser)) {
            return Collections.emptyList();
        }

        List<KeyValueEntity> entities = new ArrayList<>();

        try (Transaction tx = createLoadTransaction()) {
            EntityManager em = persistence.getEntityManager(storeName);
            em.setSoftDeletion(context.isSoftDeletion());

            List<String> keys = context.getProperties();

            DataServiceQueryBuilder queryBuilder = AppBeans.get(DataServiceQueryBuilder.NAME);
            queryBuilder.init(contextQuery.getQueryString(), contextQuery.getParameters(), null,
                    metadata.getClassNN(KeyValueEntity.class).getName());
            Query query = queryBuilder.getQuery(em);

            if (contextQuery.getFirstResult() != 0)
                query.setFirstResult(contextQuery.getFirstResult());
            if (contextQuery.getMaxResults() != 0)
                query.setMaxResults(contextQuery.getMaxResults());

            List resultList = query.getResultList();
            List<Integer> notPermittedSelectIndexes = getNotPermittedSelectIndexes(queryParser);
            for (Object item : resultList) {
                KeyValueEntity entity = new KeyValueEntity();
                entity.setIdName(context.getIdName());
                entities.add(entity);

                if (item instanceof Object[]) {
                    Object[] row = (Object[]) item;
                    for (int i = 0; i < keys.size(); i++) {
                        String key = keys.get(i);
                        if (row.length > i) {
                            if (notPermittedSelectIndexes.contains(i)) {
                                entity.setValue(key, null);
                            } else {
                                entity.setValue(key, row[i]);
                            }
                        }
                    }
                } else if (!keys.isEmpty()) {
                    if (!notPermittedSelectIndexes.isEmpty()) {
                        entity.setValue(keys.get(0), null);
                    } else {
                        entity.setValue(keys.get(0), item);
                    }
                }
            }

            tx.commit();
        }

        return entities;
    }

    protected View getViewFromContext(CommitContext context, Entity entity) {
        View view = context.getViews().get(entity);
        if (view == null) {
            view = viewRepository.getView(entity.getClass(), View.LOCAL);
        }
        return attributeSecurity.createRestrictedView(view);
    }

    @Nullable
    protected View getViewFromContextOrNull(CommitContext context, Entity entity) {
        View view = context.getViews().get(entity);
        if (view == null) {
            return null;
        }
        return attributeSecurity.createRestrictedView(view);
    }

    protected void checkOperationPermitted(Entity entity, ConstraintOperationType operationType) {
        if (isAuthorizationRequired() && userSessionSource.getUserSession().hasConstraints()
                && security.hasConstraints(entity.getMetaClass()) && !security.isPermitted(entity, operationType)) {
            throw new RowLevelSecurityException(operationType + " is not permitted for entity " + entity,
                    entity.getMetaClass().getName(), operationType);
        }
    }

    protected boolean entityHasDynamicAttributes(Entity entity) {
        return entity instanceof BaseGenericIdEntity
                && ((BaseGenericIdEntity) entity).getDynamicAttributes() != null;
    }

    protected Query createQuery(EntityManager em, LoadContext context, boolean singleResult) {
        LoadContext.Query contextQuery = context.getQuery();
        if ((contextQuery == null || isBlank(contextQuery.getQueryString())) && context.getId() == null)
            throw new IllegalArgumentException("Query string or ID needed");

        DataServiceQueryBuilder queryBuilder = AppBeans.get(DataServiceQueryBuilder.NAME);
        queryBuilder.init(contextQuery == null ? null : contextQuery.getQueryString(),
                contextQuery == null ? null : contextQuery.getParameters(), context.getId(),
                context.getMetaClass());

        queryBuilder.setSingleResult(singleResult);

        if (!context.getPrevQueries().isEmpty()) {
            log.debug("Restrict query by previous results");
            queryBuilder.restrictByPreviousResults(userSessionSource.getUserSession().getId(),
                    context.getQueryKey());
        }
        Query query = queryBuilder.getQuery(em);

        if (contextQuery != null) {
            if (contextQuery.getFirstResult() != 0)
                query.setFirstResult(contextQuery.getFirstResult());
            if (contextQuery.getMaxResults() != 0)
                query.setMaxResults(contextQuery.getMaxResults());
            if (contextQuery.isCacheable()) {
                query.setCacheable(contextQuery.isCacheable());
            }
        }

        return query;
    }

    protected View createRestrictedView(LoadContext context) {
        View view = context.getView() != null ? context.getView()
                : viewRepository.getView(metadata.getClassNN(context.getMetaClass()), View.LOCAL);
        View copy = View.copy(attributeSecurity.createRestrictedView(view));
        if (context.isLoadPartialEntities() && !needToApplyInMemoryReadConstraints(context)
                && !needToApplyAttributeAccess(context)) {
            copy.setLoadPartialEntities(true);
        }
        return copy;
    }

    @SuppressWarnings("unchecked")
    protected <E extends Entity> List<E> getResultList(LoadContext<E> context, Query query,
            boolean ensureDistinct) {
        List<E> list = executeQuery(query, false);
        int initialSize = list.size();
        if (initialSize == 0) {
            return list;
        }
        boolean needApplyConstraints = needToApplyInMemoryReadConstraints(context);
        boolean filteredByConstraints = false;
        if (needApplyConstraints) {
            filteredByConstraints = security.filterByConstraints((Collection<Entity>) list);
        }
        if (!ensureDistinct) {
            return filteredByConstraints ? getResultListIteratively(context, query, list, initialSize, true) : list;
        }

        int requestedFirst = context.getQuery().getFirstResult();
        LinkedHashSet<E> set = new LinkedHashSet<>(list);
        if (set.size() == list.size() && requestedFirst == 0 && !filteredByConstraints) {
            // If this is the first chunk and it has no duplicates and security constraints are not applied, just return it
            return list;
        }
        // In case of not first chunk, even if there where no duplicates, start filling the set from zero
        // to ensure correct paging
        return getResultListIteratively(context, query, set, initialSize, needApplyConstraints);
    }

    @SuppressWarnings("unchecked")
    protected <E extends Entity> List<E> getResultListIteratively(LoadContext<E> context, Query query,
            Collection<E> filteredCollection, int initialSize, boolean needApplyConstraints) {
        int requestedFirst = context.getQuery().getFirstResult();
        int requestedMax = context.getQuery().getMaxResults();

        if (requestedMax == 0) {
            // set contains all items if query without paging
            return new ArrayList<>(filteredCollection);
        }

        int setSize = initialSize + requestedFirst;
        int factor = filteredCollection.size() == 0 ? 2 : initialSize / filteredCollection.size() * 2;

        filteredCollection.clear();

        int firstResult = 0;
        int maxResults = (requestedFirst + requestedMax) * factor;
        int i = 0;
        while (filteredCollection.size() < setSize) {
            if (i++ > 10000) {
                log.warn("In-memory distinct: endless loop detected for " + context);
                break;
            }
            query.setFirstResult(firstResult);
            query.setMaxResults(maxResults);
            //noinspection unchecked
            List<E> list = query.getResultList();
            if (list.size() == 0) {
                break;
            }
            if (needApplyConstraints) {
                security.filterByConstraints((Collection<Entity>) list);
            }
            filteredCollection.addAll(list);

            firstResult = firstResult + maxResults;
        }

        // Copy by iteration because subList() returns non-serializable class
        int max = Math.min(requestedFirst + requestedMax, filteredCollection.size());
        List<E> result = new ArrayList<>(max - requestedFirst);
        int j = 0;
        for (E item : filteredCollection) {
            if (j >= max)
                break;
            if (j >= requestedFirst)
                result.add(item);
            j++;
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    protected <E extends Entity> List<E> executeQuery(Query query, boolean singleResult) {
        List<E> list;
        try {
            if (singleResult) {
                try {
                    E result = (E) query.getSingleResult();
                    list = new ArrayList<>(1);
                    list.add(result);
                } catch (NoResultException e) {
                    list = Collections.emptyList();
                }
            } else {
                list = query.getResultList();
            }
        } catch (javax.persistence.PersistenceException e) {
            if (e.getCause() instanceof org.eclipse.persistence.exceptions.QueryException && e.getMessage() != null
                    && e.getMessage().contains("Fetch group cannot be set on report query")) {
                throw new DevelopmentException("DataManager cannot execute query for single attributes");
            } else {
                throw e;
            }
        }
        return list;
    }

    protected void checkPermissions(CommitContext context) {
        Set<MetaClass> checkedCreateRights = new HashSet<>();
        Set<MetaClass> checkedUpdateRights = new HashSet<>();
        Set<MetaClass> checkedDeleteRights = new HashSet<>();

        for (Entity entity : context.getCommitInstances()) {
            if (entity == null)
                continue;

            if (PersistenceHelper.isNew(entity)) {
                checkPermission(checkedCreateRights, entity.getMetaClass(), EntityOp.CREATE);
            } else {
                checkPermission(checkedUpdateRights, entity.getMetaClass(), EntityOp.UPDATE);
            }
        }

        for (Entity entity : context.getRemoveInstances()) {
            if (entity == null)
                continue;

            checkPermission(checkedDeleteRights, entity.getMetaClass(), EntityOp.DELETE);
        }
    }

    protected void checkPermission(Set<MetaClass> cache, MetaClass metaClass, EntityOp operation) {
        if (cache.contains(metaClass))
            return;
        checkPermission(metaClass, operation);
        cache.add(metaClass);
    }

    protected void checkPermission(MetaClass metaClass, EntityOp operation) {
        if (!isEntityOpPermitted(metaClass, operation))
            throw new AccessDeniedException(PermissionType.ENTITY_OP, metaClass.getName());
    }

    protected boolean checkValueQueryPermissions(QueryParser queryParser) {
        if (isAuthorizationRequired()) {
            queryParser.getQueryPaths().stream().filter(path -> !path.isSelectedPath()).forEach(path -> {
                MetaClass metaClass = metadata.getClassNN(path.getEntityName());
                MetaPropertyPath propertyPath = metaClass.getPropertyPath(path.getPropertyPath());
                if (propertyPath == null) {
                    throw new IllegalStateException(
                            String.format("query path '%s' is unresolved", path.getFullPath()));
                }
                if (!isEntityAttrViewPermitted(propertyPath)) {
                    throw new AccessDeniedException(PermissionType.ENTITY_ATTR,
                            metaClass + "." + path.getFullPath());
                }
            });
            MetaClass metaClass = metadata.getClassNN(queryParser.getEntityName());
            if (!isEntityOpPermitted(metaClass, EntityOp.READ)) {
                log.debug("reading of {} not permitted, returning empty list", metaClass);
                return false;
            }
            if (security.hasInMemoryConstraints(metaClass, ConstraintOperationType.READ,
                    ConstraintOperationType.ALL)) {
                String msg = String.format("%s is not permitted for %s", ConstraintOperationType.READ,
                        metaClass.getName());
                if (serverConfig.getDisableLoadValuesIfConstraints()) {
                    throw new RowLevelSecurityException(msg, metaClass.getName(), ConstraintOperationType.READ);
                } else {
                    log.debug(msg);
                }
            }
            Set<String> entityNames = queryParser.getAllEntityNames();
            entityNames.remove(metaClass.getName());
            for (String entityName : entityNames) {
                MetaClass entityMetaClass = metadata.getClassNN(entityName);
                if (!isEntityOpPermitted(entityMetaClass, EntityOp.READ)) {
                    log.debug("reading of {} not permitted, returning empty list", entityMetaClass);
                    return false;
                }
                if (security.hasConstraints(entityMetaClass)) {
                    String msg = String.format("%s is not permitted for %s", ConstraintOperationType.READ,
                            entityName);
                    if (serverConfig.getDisableLoadValuesIfConstraints()) {
                        throw new RowLevelSecurityException(msg, entityName, ConstraintOperationType.READ);
                    } else {
                        log.debug(msg);
                    }
                }
            }
        }
        return true;
    }

    protected boolean isEntityOpPermitted(MetaClass metaClass, EntityOp operation) {
        return !isAuthorizationRequired() || security.isEntityOpPermitted(metaClass, operation);
    }

    protected boolean isEntityAttrViewPermitted(MetaPropertyPath metaPropertyPath) {
        for (MetaProperty metaProperty : metaPropertyPath.getMetaProperties()) {
            if (!security.isEntityAttrPermitted(metaProperty.getDomain(), metaProperty.getName(),
                    EntityAttrAccess.VIEW)) {
                return false;
            }
        }
        return true;
    }

    protected boolean isAuthorizationRequired() {
        return serverConfig.getDataManagerChecksSecurityOnMiddleware()
                || AppContext.getSecurityContextNN().isAuthorizationRequired();
    }

    protected List<Integer> getNotPermittedSelectIndexes(QueryParser queryParser) {
        List<Integer> indexes = new ArrayList<>();
        if (isAuthorizationRequired()) {
            int index = 0;
            for (QueryParser.QueryPath path : queryParser.getQueryPaths()) {
                if (path.isSelectedPath()) {
                    MetaClass metaClass = metadata.getClassNN(path.getEntityName());
                    if (!Objects.equals(path.getPropertyPath(), path.getVariableName())
                            && !isEntityAttrViewPermitted(metaClass.getPropertyPath(path.getPropertyPath()))) {
                        indexes.add(index);
                    }
                    index++;
                }
            }
        }
        return indexes;
    }

    /**
     * Update references from newly persisted entities to merged detached entities. Otherwise a new entity can
     * contain a stale instance of merged entity.
     *
     * @param persisted persisted entities
     * @param committed all committed entities
     */
    protected void updateReferences(Collection<Entity> persisted, Collection<Entity> committed) {
        for (Entity persistedEntity : persisted) {
            for (Entity entity : committed) {
                if (entity != persistedEntity) {
                    updateReferences(persistedEntity, entity, new HashSet<>());
                }
            }
        }
    }

    protected void updateReferences(Entity entity, Entity refEntity, Set<Entity> visited) {
        if (entity == null || refEntity == null || visited.contains(entity))
            return;
        visited.add(entity);

        MetaClass refEntityMetaClass = refEntity.getMetaClass();
        for (MetaProperty property : entity.getMetaClass().getProperties()) {
            if (!property.getRange().isClass() || !property.getRange().asClass().equals(refEntityMetaClass))
                continue;
            if (PersistenceHelper.isLoaded(entity, property.getName())) {
                if (property.getRange().getCardinality().isMany()) {
                    Collection collection = entity.getValue(property.getName());
                    if (collection != null) {
                        for (Object obj : collection) {
                            updateReferences((Entity) obj, refEntity, visited);
                        }
                    }
                } else {
                    Entity value = entity.getValue(property.getName());
                    if (value != null) {
                        if (value.getId().equals(refEntity.getId())) {
                            if (entity instanceof AbstractInstance) {
                                if (property.isReadOnly() && metadata.getTools().isNotPersistent(property)) {
                                    continue;
                                }
                                ((AbstractInstance) entity).setValue(property.getName(), refEntity, false);
                            }
                        } else {
                            updateReferences(value, refEntity, visited);
                        }
                    }
                }
            }
        }
    }

    protected boolean needToApplyInMemoryReadConstraints(LoadContext context) {
        return isAuthorizationRequired() && userSessionSource.getUserSession().hasConstraints()
                && needToApplyByPredicate(context, metaClass -> security.hasInMemoryConstraints(metaClass,
                        ConstraintOperationType.READ, ConstraintOperationType.ALL));
    }

    protected boolean needToApplyByPredicate(LoadContext context) {
        return isAuthorizationRequired() && userSessionSource.getUserSession().hasConstraints()
                && needToApplyByPredicate(context, metaClass -> security.hasConstraints(metaClass));
    }

    protected boolean needToApplyAttributeAccess(LoadContext context) {
        return needToApplyByPredicate(context, metaClass -> attributeSecurity.isAttributeAccessEnabled(metaClass));
    }

    protected boolean needToApplyByPredicate(LoadContext context, Predicate<MetaClass> hasConstraints) {
        if (context.getView() == null) {
            MetaClass metaClass = metadata.getSession().getClassNN(context.getMetaClass());
            return hasConstraints.test(metaClass);
        }

        Session session = metadata.getSession();
        for (Class aClass : collectEntityClasses(context.getView(), new HashSet<>())) {
            if (hasConstraints.test(session.getClassNN(aClass))) {
                return true;
            }
        }
        return false;
    }

    protected Set<Class> collectEntityClassesWithDynamicAttributes(@Nullable View view) {
        if (view == null) {
            return Collections.emptySet();
        }
        return collectEntityClasses(view, new HashSet<>()).stream()
                .filter(BaseGenericIdEntity.class::isAssignableFrom)
                .filter(aClass -> !dynamicAttributesManagerAPI
                        .getAttributesForMetaClass(metadata.getClassNN(aClass)).isEmpty())
                .collect(Collectors.toSet());
    }

    protected Set<Class> collectEntityClasses(View view, Set<View> visited) {
        if (visited.contains(view)) {
            return Collections.emptySet();
        } else {
            visited.add(view);
        }

        HashSet<Class> classes = new HashSet<>();
        classes.add(view.getEntityClass());
        for (ViewProperty viewProperty : view.getProperties()) {
            if (viewProperty.getView() != null) {
                classes.addAll(collectEntityClasses(viewProperty.getView(), visited));
            }
        }
        return classes;
    }

    protected Transaction createLoadTransaction() {
        TransactionParams txParams = new TransactionParams();
        if (serverConfig.getUseReadOnlyTransactionForLoad()) {
            txParams.setReadOnly(true);
        }
        return persistence.createTransaction(storeName, txParams);
    }
}