com.haulmont.cuba.core.sys.QueryImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.core.sys.QueryImpl.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.sys;

import com.haulmont.bali.util.ReflectionHelper;
import com.haulmont.chile.core.datatypes.impl.EnumClass;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.cuba.core.TypedQuery;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.entity.IdProxy;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.core.sys.persistence.DbmsFeatures;
import com.haulmont.cuba.core.sys.persistence.DbmsSpecificFactory;
import org.apache.commons.collections.CollectionUtils;
import org.eclipse.persistence.config.CascadePolicy;
import org.eclipse.persistence.config.HintValues;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.jpa.JpaQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.persistence.Cache;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.TemporalType;
import java.util.*;

/**
 * Implementation of {@link TypedQuery} interface based on EclipseLink.
 */
public class QueryImpl<T> implements TypedQuery<T> {

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

    private Metadata metadata;
    private javax.persistence.EntityManager emDelegate;
    private JpaQuery query;
    private boolean isNative;
    private String queryString;
    private Class resultClass;
    private FetchGroupManager fetchGroupMgr;
    private EntityFetcher entityFetcher;
    private Set<Param> params = new HashSet<>();
    private LockModeType lockMode;
    private List<View> views = new ArrayList<>();
    private Integer maxResults;
    private Integer firstResult;
    private boolean singleResultExpected;

    private Collection<QueryMacroHandler> macroHandlers;

    public QueryImpl(EntityManagerImpl entityManager, boolean isNative, @Nullable Class resultClass) {
        this.emDelegate = entityManager.getDelegate();
        this.isNative = isNative;
        this.macroHandlers = AppBeans.getAll(QueryMacroHandler.class).values();
        //noinspection unchecked
        this.resultClass = resultClass;
        this.metadata = AppBeans.get(Metadata.NAME);
        this.fetchGroupMgr = AppBeans.get(FetchGroupManager.NAME);
        this.entityFetcher = AppBeans.get(EntityFetcher.NAME);
    }

    private JpaQuery<T> getQuery() {
        if (query == null) {
            View view = views.isEmpty() ? null : views.get(0);

            if (isNative) {
                log.trace("Creating SQL query: {}", queryString);
                if (resultClass == null)
                    query = (JpaQuery) emDelegate.createNativeQuery(queryString);
                else {
                    if (!Entity.class.isAssignableFrom(resultClass)) {
                        throw new IllegalArgumentException(
                                "Non-entity result class for native query is not supported" + " by EclipseLink: "
                                        + resultClass);
                    }
                    Class effectiveClass = metadata.getExtendedEntities().getEffectiveClass(resultClass);
                    query = (JpaQuery) emDelegate.createNativeQuery(queryString, effectiveClass);
                }
            } else {
                log.trace("Creating JPQL query: {}", queryString);
                String s = transformQueryString();
                log.trace("Transformed JPQL query: {}", s);

                Class effectiveClass = getEffectiveResultClass();
                if (effectiveClass != null) {
                    query = (JpaQuery) emDelegate.createQuery(s, effectiveClass);
                } else {
                    query = (JpaQuery) emDelegate.createQuery(s);
                }
                if (view != null) {
                    MetaClass metaClass = metadata.getClassNN(view.getEntityClass());
                    if (!metadata.getTools().isCacheable(metaClass) || !singleResultExpected) {
                        query.setHint(QueryHints.REFRESH, HintValues.TRUE);
                        query.setHint(QueryHints.REFRESH_CASCADE, CascadePolicy.CascadeByMapping);
                    }
                }
            }
            if (view != null && !view.loadPartialEntities()) {
                query.setFlushMode(FlushModeType.AUTO);
            } else {
                query.setFlushMode(FlushModeType.COMMIT);
            }

            boolean nullParam = false;
            for (Param param : params) {
                param.apply(query);
                if (param.value == null)
                    nullParam = true;
            }

            addMacroParams(query);

            // disable SQL caching to support "is null" generation
            if (nullParam)
                query.setHint(QueryHints.PREPARE, HintValues.FALSE);

            // Set maxResults and firstResult only if the query is not by ID, otherwise EclipseLink does not select
            // nested collections in some cases
            if (maxResults != null && !singleResultExpected)
                query.setMaxResults(maxResults);
            if (firstResult != null && !singleResultExpected)
                query.setFirstResult(firstResult);

            if (lockMode != null)
                query.setLockMode(lockMode);

            for (int i = 0; i < views.size(); i++) {
                if (i == 0)
                    fetchGroupMgr.setView(query, queryString, views.get(i), singleResultExpected);
                else
                    fetchGroupMgr.addView(query, queryString, views.get(i), singleResultExpected);
            }
        }
        //noinspection unchecked
        return query;
    }

    @Nullable
    private Class getEffectiveResultClass() {
        if (resultClass == null) {
            return null;
        }
        if (Entity.class.isAssignableFrom(resultClass)) {
            return metadata.getExtendedEntities().getEffectiveClass(resultClass);
        }
        return resultClass;
    }

    private void checkState() {
        if (query != null)
            throw new IllegalStateException("Query delegate has already been created");
    }

    private String transformQueryString() {
        String result = expandMacros(queryString);

        QueryParser parser = QueryTransformerFactory.createParser(result);

        String entityName = parser.getEntityName();
        Class effectiveClass = metadata.getExtendedEntities().getEffectiveClass(entityName);
        String effectiveEntityName = metadata.getSession().getClassNN(effectiveClass).getName();
        if (!effectiveEntityName.equals(entityName)) {
            QueryTransformer transformer = QueryTransformerFactory.createTransformer(result);
            transformer.replaceEntityName(effectiveEntityName);
            result = transformer.getResult();
        }

        for (Iterator<Param> iterator = params.iterator(); iterator.hasNext();) {
            Param param = iterator.next();
            if (param.value instanceof String && ((String) param.value).startsWith("(?i)")) {
                result = replaceCaseInsensitiveParam(result, param);
            } else if ((param.value instanceof Collection && CollectionUtils.isEmpty((Collection) param.value))) {
                QueryTransformer transformer = QueryTransformerFactory.createTransformer(result);
                transformer.replaceInCondition((String) param.name);
                result = transformer.getResult();
                iterator.remove();
            }
        }

        String nestedEntityName = parser.getEntityNameIfSecondaryReturnedInsteadOfMain();
        String nestedEntityPath = parser.getEntityPathIfSecondaryReturnedInsteadOfMain();
        if (nestedEntityName != null) {
            QueryTransformer transformer = QueryTransformerFactory.createTransformer(result);
            transformer.replaceWithSelectEntityVariable("tempEntityAlias");
            transformer.addFirstSelectionSource(String.format("%s tempEntityAlias", nestedEntityName));
            transformer.addWhereAsIs(String.format("tempEntityAlias.id = %s.id", nestedEntityPath));
            result = transformer.getResult();
        }

        return result;
    }

    private String expandMacros(String queryStr) {
        String result = queryStr;
        if (macroHandlers != null) {
            for (QueryMacroHandler handler : macroHandlers) {
                result = handler.expandMacro(result);
            }
        }
        return result;
    }

    private String replaceCaseInsensitiveParam(String queryStr, Param param) {
        if (!(param.name instanceof String)) // case insensitive search is supported only for named parameters
            return queryStr;

        QueryTransformer transformer = QueryTransformerFactory.createTransformer(queryStr);
        transformer.handleCaseInsensitiveParam((String) param.name);
        String result = transformer.getResult();

        param.value = ((String) param.value).substring(4).toLowerCase();
        return result;
    }

    private void addMacroParams(javax.persistence.TypedQuery jpaQuery) {
        if (macroHandlers != null) {
            for (QueryMacroHandler handler : macroHandlers) {

                Map<String, Object> namedParams = new HashMap<>();
                for (Param param : params) {
                    if (param.name instanceof String)
                        namedParams.put((String) param.name, param.value);
                }
                handler.setQueryParams(namedParams);

                for (Map.Entry<String, Object> entry : handler.getParams().entrySet()) {
                    jpaQuery.setParameter(entry.getKey(), entry.getValue());
                }
            }
        }
    }

    @Override
    public List<T> getResultList() {
        if (log.isDebugEnabled())
            log.debug(queryString.replaceAll("[\\t\\n\\x0B\\f\\r]", " "));

        singleResultExpected = false;

        List<T> resultList = getQuery().getResultList();
        for (T result : resultList) {
            if (result instanceof Entity) {
                for (View view : views) {
                    entityFetcher.fetch((Entity) result, view);
                }
            }
        }
        return resultList;
    }

    @Override
    public T getSingleResult() {
        if (log.isDebugEnabled())
            log.debug(queryString.replaceAll("[\\t\\n\\x0B\\f\\r]", " "));

        singleResultExpected = true;

        T result = getQuery().getSingleResult();
        if (result instanceof Entity) {
            for (View view : views) {
                entityFetcher.fetch((Entity) result, view);
            }
        }
        return result;
    }

    @Override
    @Nullable
    public T getFirstResult() {
        if (log.isDebugEnabled())
            log.debug(queryString.replaceAll("[\\t\\n\\x0B\\f\\r]", " "));

        Integer saveMaxResults = maxResults;
        maxResults = 1;
        try {
            List<T> resultList = getQuery().getResultList();
            if (resultList.isEmpty()) {
                return null;
            } else {
                T result = resultList.get(0);
                if (result instanceof Entity) {
                    for (View view : views) {
                        entityFetcher.fetch((Entity) result, view);
                    }
                }
                return result;
            }
        } finally {
            maxResults = saveMaxResults;
        }
    }

    @Override
    public int executeUpdate() {
        JpaQuery<T> query = getQuery();
        // In some cache configurations (in particular, when shared cache is on, but for some entities cache is set to ISOLATED),
        // EclipseLink does not evict updated entities from cache automatically.
        Cache cache = query.getEntityManager().getEntityManagerFactory().getCache();
        Class referenceClass = query.getDatabaseQuery().getReferenceClass();
        if (referenceClass != null) {
            cache.evict(referenceClass);
        } else {
            cache.evictAll();
        }
        return query.executeUpdate();
    }

    @Override
    public TypedQuery<T> setMaxResults(int maxResults) {
        this.maxResults = maxResults;
        if (query != null)
            query.setMaxResults(maxResults);
        return this;
    }

    @Override
    public TypedQuery<T> setFirstResult(int firstResult) {
        this.firstResult = firstResult;
        if (query != null)
            query.setFirstResult(firstResult);
        return this;
    }

    @Override
    public TypedQuery<T> setParameter(String name, Object value) {
        return setParameter(name, value, true);
    }

    @Override
    public TypedQuery<T> setParameter(String name, Object value, boolean implicitConversions) {
        checkState();

        if (value instanceof IdProxy) {
            value = ((IdProxy) value).getNN();
        } else if (implicitConversions) {
            value = handleImplicitConversions(value);
        }
        params.add(new Param(name, value));
        return this;
    }

    private Object handleImplicitConversions(Object value) {
        if (value instanceof Entity)
            value = ((Entity) value).getId();
        else if (value instanceof Collection) {
            List<Object> list = new ArrayList<>(((Collection) value).size());
            for (Object obj : ((Collection) value)) {
                list.add(obj instanceof Entity ? ((Entity) obj).getId() : obj);
            }
            value = list;
        } else if (value instanceof EnumClass) {
            value = ((EnumClass) value).getId();
        }
        return value;
    }

    @Override
    public TypedQuery<T> setParameter(String name, Date value, TemporalType temporalType) {
        checkState();
        params.add(new Param(name, value, temporalType));
        return this;
    }

    @Override
    public TypedQuery<T> setParameter(int position, Object value) {
        return setParameter(position, value, true);
    }

    @Override
    public TypedQuery<T> setParameter(int position, Object value, boolean implicitConversions) {
        checkState();
        DbmsFeatures dbmsFeatures = DbmsSpecificFactory.getDbmsFeatures();
        if (isNative && (value instanceof UUID) && (dbmsFeatures.getUuidTypeClassName() != null)) {
            Class c = ReflectionHelper.getClass(dbmsFeatures.getUuidTypeClassName());
            try {
                value = ReflectionHelper.newInstance(c, value);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Error setting parameter value", e);
            }
        } else if (value instanceof IdProxy) {
            value = ((IdProxy) value).getNN();
        } else if (implicitConversions) {
            value = handleImplicitConversions(value);
        }

        params.add(new Param(position, value));
        return this;
    }

    @Override
    public TypedQuery<T> setParameter(int position, Date value, TemporalType temporalType) {
        checkState();
        params.add(new Param(position, value, temporalType));
        return this;
    }

    @Override
    public TypedQuery<T> setLockMode(LockModeType lockMode) {
        checkState();
        this.lockMode = lockMode;
        return this;
    }

    @Override
    public TypedQuery<T> setView(View view) {
        checkState();
        views.clear();
        views.add(view);
        return this;
    }

    @Override
    public TypedQuery<T> setViewName(String viewName) {
        if (resultClass == null)
            throw new IllegalStateException("resultClass is null");

        setView(metadata.getViewRepository().getView(resultClass, viewName));
        return this;
    }

    @Override
    public TypedQuery<T> setView(Class<? extends Entity> entityClass, String viewName) {
        setView(metadata.getViewRepository().getView(entityClass, viewName));
        return this;
    }

    @Override
    public TypedQuery<T> addView(View view) {
        checkState();
        views.add(view);
        return this;
    }

    @Override
    public TypedQuery<T> addViewName(String viewName) {
        if (resultClass == null)
            throw new IllegalStateException("resultClass is null");

        addView(metadata.getViewRepository().getView(resultClass, viewName));
        return this;
    }

    @Override
    public TypedQuery<T> addView(Class<? extends Entity> entityClass, String viewName) {
        addView(metadata.getViewRepository().getView(entityClass, viewName));
        return this;
    }

    @Override
    public javax.persistence.Query getDelegate() {
        return getQuery();
    }

    @Override
    public String getQueryString() {
        return queryString;
    }

    @Override
    public TypedQuery<T> setQueryString(String queryString) {
        checkState();
        this.queryString = queryString;
        return this;
    }

    public void setSingleResultExpected(boolean singleResultExpected) {
        this.singleResultExpected = singleResultExpected;
    }

    protected static class Param {
        private Object name;
        private Object value;
        private TemporalType temporalType;

        public Param(Object name, Object value) {
            this.name = name;
            this.value = value;
        }

        public Param(Object name, Date value, TemporalType temporalType) {
            this.name = name;
            this.value = value;
            this.temporalType = temporalType;
        }

        public void apply(JpaQuery query) {
            if (temporalType != null) {
                if (name instanceof Integer)
                    query.setParameter((int) name, (Date) value, temporalType);
                else
                    query.setParameter((String) name, (Date) value, temporalType);
            } else {
                if (name instanceof Integer)
                    query.setParameter((int) name, value);
                else
                    query.setParameter((String) name, value);
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            Param param = (Param) o;
            return name.equals(param.name);
        }

        @Override
        public int hashCode() {
            return name.hashCode();
        }
    }
}