RepositoryBaseImpl.java

package com.tradecloud.repository.base.impl;

import com.tradecloud.authentication.MultiTenantUtil;
import com.tradecloud.common.externalreference.ExternalReference;
import com.tradecloud.domain.common.IntegratedPersistenceBase;
import com.tradecloud.domain.common.IntegratedStaticDataEntityBase;
import com.tradecloud.domain.exception.BaseBusinessException;
import com.tradecloud.domain.exception.InvalidEntityException;
import com.tradecloud.domain.infrastructure.persistence.CriteriaBuilder;
import com.tradecloud.domain.model.organisationalunit.OrganisationalUnit;
import com.tradecloud.domain.search.SearchParams;
import com.tradecloud.repository.SearchMetaParams;
import com.tradecloud.repository.base.RepositoryBase;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.criterion.*;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.loader.criteria.CriteriaJoinWalker;
import org.hibernate.loader.criteria.CriteriaQueryTranslator;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public class RepositoryBaseImpl<T, S> extends RepositorySearchBaseImpl<T, S> implements RepositoryBase<T, S> {

    private static final long serialVersionUID = 1L;

    protected static final String LIKE = "%";

    private Class<T> persistentClass;

    private static Logger log = Logger.getLogger(RepositoryBaseImpl.class);

    @SuppressWarnings("unchecked")
    public RepositoryBaseImpl() {
        this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public RepositoryBaseImpl(final Class<T> persistentClass) {
        super();
        this.persistentClass = persistentClass;
    }

    @Override
    public long count(String tableName) {
        return ((BigInteger) getCurrentSession().createSQLQuery("select count(*) from " + tableName)
                .uniqueResult()).longValue();
    }

    @Override
    public long count() {
        return (Long) getCurrentSession()
                .createQuery("select count(*) from " + getPersistentClass().getSimpleName()).uniqueResult();
    }

    @Override
    public List<T> findAll() {
        return (List<T>) loadAll(persistentClass);
    }

    @Override
    public void saveOrUpdate(final T entity) {
        getCurrentSession().saveOrUpdate(entity);
    }

    @Override
    public void save(final T entity) {
        getCurrentSession().persist(entity);
    }

    @Override
    public void persist(T entity) {
        getCurrentSession().persist(entity);
    }

    @Override
    public Serializable save2(T entity) {
        return getCurrentSession().save(entity);
    }

    @Override
    public T merge(final T entity) {
        return (T) getCurrentSession().merge(entity);
    }

    @Override
    public void delete(T entity) {
        try {
            getCurrentSession().delete(entity);
            getCurrentSession().flush();
        } catch (Exception e) {
            if (entity != null) {
                handleDeleteConstraint(entity.getClass().getSimpleName(), e);
            }
            throw e;
        }
    }

    @Override
    public void update(T entity) {
        getCurrentSession().update(entity);
    }

    @Override
    public T retrieve(Long id) {
        //log.debug("Finding entity. Class '" + persistentClass + "'. Id '" + id + "'");
        return (T) getCurrentSession().get(persistentClass, id);
    }

    @Override
    public T retrieve(String code) {
        // log.info("In find by code. Searching for " +
        // persistentClass.getName() + " with code:" + code);
        List<T> results = (List<T>) findByNamedParam("from " + persistentClass.getName() + " x where x.code = :code", "code", code);
        if (results.size() > 1) {
            log.warn("Found " + results.size() + " " + persistentClass.getName() + "s with code '" + code + "'. Returning first one");
        }
        if (!results.isEmpty()) {
            return results.iterator().next();
        }
        return null;
    }

    @Override
    public T retrieve(ExternalReference externalReference) {
        String simpleName = persistentClass.getSimpleName();
        return getT(externalReference, simpleName);
    }

    protected T getT(ExternalReference externalReference, String simpleName) {
        StringBuilder sb = new StringBuilder("select t1 FROM " + simpleName + " t1")
                .append("  join  t1.externalReferences as er where ")
                .append(" er.integratedSystem=:integratedSystem")
                .append(" and  er.referenceValue=:referenceValue");
        Query query = getSessionCustom().createQuery(sb.toString());
        query.setParameter("integratedSystem", externalReference.getIntegratedSystem());
        query.setParameter("referenceValue", externalReference.getReferenceValue());
        List<T> results = query.list();
        return filterResult(externalReference, results);
    }

    protected T filterResult(ExternalReference externalReference, List<T> results) {
        if (results.size() > 1) {
            return failDueToDuplicateResults(externalReference, results);
        } else if (results.size() == 1) {
            return results.iterator().next();
        } else {
            return null;
        }
    }

    protected T failDueToDuplicateResults(ExternalReference externalReference, List<T> results) {
        throw new BaseBusinessException("External reference not unique. Count=" + results.size() + ". Type=" + persistentClass.getSimpleName() +
                ". System=" + externalReference.getIntegratedSystem().getName() + ". Reference=" + externalReference.getReferenceValue() + ".");
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> findAll(SearchParams searchParams) {
        DetachedCriteria criteria = DetachedCriteria.forClass(persistentClass);
        CriteriaBuilder.applySearchParams(criteria, searchParams);
        @SuppressWarnings("unchecked")
        List<T> results = criteria.getExecutableCriteria(getSessionCustom()).list();
        return results;
    }

    @Override
    public long count(S search) {
        DetachedCriteria criteria = DetachedCriteria.forClass(persistentClass);
        // just do a "select count(*)" rather than a full table retrieve!
        criteria.setProjection(Projections.rowCount());
        List<Long> results = criteria.getExecutableCriteria(getSessionCustom()).list();
        // will always have a result of the num of rows
        if (results.isEmpty()) {
            return 0;
        } else {
            return results.get(0);
        }
    }

    @Override
    public void flush() {
        getCurrentSession().flush();
    }

    protected Class<T> getPersistentClass() {
        return this.persistentClass;
    }

    protected void setPersistentClass(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    @Override
    public void evict(final T entity) {
        getCurrentSession().evict(entity);
    }

    /**
     * Validates that the supplied object is not null. If it is, we throw an exception and specify the name of the object.
     *
     * @param object the object to check for null
     * @param name   the name of the field represented by the object, this will be used in the exception for information
     * @throws IllegalArgumentException if object is null
     */
    protected void validateNotNull(Object object, String name) {
        if (object == null) {
            throw new IllegalArgumentException("Must supply " + name);
        }
    }

    protected List<T> getExecutableCriteriaList(DetachedCriteria detachedCriteria, SearchMetaParams searchMetaParams) {
        StopWatch sw = new StopWatch();
        sw.start();

        Criteria criteria = applyMetaDataCriteria(detachedCriteria, searchMetaParams);

        @SuppressWarnings("unchecked")
        List<T> results = criteria.list();

        sw.stop();
        log.debug("Found " + results.size() + " results for list. Class=" + persistentClass
                + ". Meta params=" + searchMetaParams + " Time=" + sw.getTime());
        return results;
    }

    protected Criteria applyMetaDataCriteria(DetachedCriteria detachedCriteria, SearchMetaParams searchMetaParams) {
        if (searchMetaParams != null && searchMetaParams.getOrderBy() != null) {
            detachedCriteria.addOrder(CriteriaBuilder.createOrdering(searchMetaParams.getOrderBy(), searchMetaParams.isAsc()));
        }

        Criteria criteria = detachedCriteria.getExecutableCriteria(getSessionCustom());

        if (searchMetaParams != null && searchMetaParams.isPaged()) {
            criteria.setFirstResult(searchMetaParams.getRowIndex());
            criteria.setMaxResults(searchMetaParams.getRowCount());
        }
        return criteria;
    }

    /**
     * Method guarantees result ordering. Method not placed in RepositoryBase interface.
     *
     * @param detachedCriteria
     * @param searchMetaParams
     * @param orderByField
     * @param asc
     * @return
     */
    public List<T> getExecutableCriteriaList(DetachedCriteria detachedCriteria, SearchMetaParams searchMetaParams,
                                             String orderByField, boolean asc) {

        detachedCriteria.addOrder(CriteriaBuilder.createOrdering(orderByField, asc));

        Criteria criteria = detachedCriteria.getExecutableCriteria(getSessionCustom());

        if (searchMetaParams != null) {
            criteria.setFirstResult(searchMetaParams.getRowIndex());
            criteria.setMaxResults(searchMetaParams.getRowCount());
        }

        @SuppressWarnings("unchecked")
        List<T> results = criteria.list();
        log.debug("Found " + results.size() + " results for list. Class=" + persistentClass + ". Meta params=" + searchMetaParams);
        return results;
    }

    protected long getExecutableCriteriaCount(DetachedCriteria detachedCriteria) {
        Projection projection = Projections.rowCount();
        return getExecutableCriteriaCount(detachedCriteria, projection);
    }

    protected long getExecutableCriteriaCount(DetachedCriteria detachedCriteria, Projection projection) {
        StopWatch sw = new StopWatch();
        sw.start();
        // Got duplicate search results unless this was added. Think it is related to org units
        // detachedCriteria.setResultTransformer(CriteriaOperation.DISTINCT_ROOT_ENTITY);
        detachedCriteria.setProjection(projection);
        long count = (Long) detachedCriteria.getExecutableCriteria(getSessionCustom()).list().get(0);
        sw.stop();
        log.debug("Found " + count + " results for count. Class=" + persistentClass + ". Time=" + sw.getTime());
        return count;
    }

    protected long getExecutableCriteriaCount(Criteria criteria) {
        // Got duplicate search results unless this was added. Think it is related to org units
        // detachedCriteria.setResultTransformer(CriteriaOperation.DISTINCT_ROOT_ENTITY);
        criteria.setProjection(Projections.rowCount());
        long count = (Long) criteria.list().get(0);
        log.debug("Found " + count + " results for count. Class=" + persistentClass);
        return count;
    }

    @Override
    public T getById(Long id) {
        return (T) getCurrentSession().get(persistentClass, id);
    }

    @Override
    public List<T> search(S search) {
        throw new NotImplementedException(persistentClass.getSimpleName() + " search");
    }

    /**
     * Default method that prevents use in the case this is not used correctly. Designed to be overridden by sub-classes that use the
     * {@link #search(Object, SearchMetaParams)} method to specify the specific search criteria
     **/
    @Override
    protected Collection<CriteriaValue> mapFieldsToValues(S search) {
        throw new UnsupportedOperationException("Classes that do not override the mapFieldsToValues(SearchBase search) method are not allowed to"
                + " execute the createQuery(SearchBase search, String selectPrefix, String tableName) method");
    }

    /**
     * Utility method. Also centralise the slightly bad cast.
     */
    protected List<?> findByNamedQueryAndNamedParam(String query, String paramName, Object value) {
        return (List<?>) getNamedQueryAndNamedParam(query, paramName, value);
    }

    protected List<?> findByNamedQueryAndNamedParam(String query, String paramName, Object... value) {
        return (List<?>) getNamedQueryAndNamedParam(query, paramName, StringUtils.collectionToCommaDelimitedString(Arrays.asList(value)));
    }

    protected List<?> findByNamedQueryAndNamedParam(String query, String paramName, List<?> value) {
        return (List<?>) getNamedQueryAndNamedParamList(query, paramName, value);
    }

    /**
     * Utility method. Also centralise the slightly bad cast.
     */
    protected List<?> findByNamedQueryAndNamedParam(String query, String paramName[], Object value[]) {
        return (List<?>) getNamedQueryAndNamedParam(query, paramName, value);
    }

    protected static void initQueryParams(Query query, SearchMetaParams searchMetaParams) {
        if (searchMetaParams != null) {
            query.setFirstResult(searchMetaParams.getRowIndex());
            query.setMaxResults(searchMetaParams.getRowCount());
        }
    }

    private void validateExternalReference(List<Serializable> list, Serializable id, String externalReferenceValue) {
        if (id == null) {
            if (!list.isEmpty()) {
                throw new InvalidEntityException("Duplicate external reference " + externalReferenceValue);
            }
        } else {
            Set clean = new HashSet<>(list);

            if (clean.size() > 1) {
                throw new InvalidEntityException("Duplicate external reference " + externalReferenceValue);
            } else if (clean.size() == 1) {
                if (!clean.iterator().next().equals(id)) {
                    throw new InvalidEntityException("Duplicate external reference " + externalReferenceValue);
                }
            }
        }
    }

    /**
     * Putting this here because it is common to a few places.
     *
     * @param <X>
     * @param c
     * @param externalReferences
     * @param id
     */
    protected <X extends IntegratedPersistenceBase> void validatePBExternalReferences(Class<X> c, Collection<ExternalReference> externalReferences,
                                                                                      Serializable id) {
        for (ExternalReference externalReference : externalReferences) {
            DetachedCriteria criteria = DetachedCriteria.forClass(c);
            criteria.setProjection(Projections.property("id"));
            criteria.add(Restrictions.eq("active", true));
            criteria.createCriteria("externalReferences")
                    .add(Restrictions.eq("integratedSystem", externalReference.getIntegratedSystem()))
                    .add(Restrictions.eq("referenceValue", externalReference.getReferenceValue()));

            List<Serializable> list = criteria.getExecutableCriteria(getSessionCustom()).list();
            validateExternalReference(list, id, externalReference.getReferenceValue());
        }
    }

    protected <X extends IntegratedStaticDataEntityBase> void validateISDEBExternalReferenceList(Class<X> c,
                                                                                                 Collection<ExternalReference> externalReferences,
                                                                                                 Serializable id) {
        for (ExternalReference externalReference : externalReferences) {
            DetachedCriteria criteria = DetachedCriteria.forClass(c);
            criteria.setProjection(Projections.property("code"));
            criteria.add(Restrictions.eq("active", true));
            DetachedCriteria extRefCrit = criteria.createCriteria("externalReferenceWrapper");
            extRefCrit.createCriteria("externalReferences")
                    .add(Restrictions.eq("integratedSystem", externalReference.getIntegratedSystem()))
                    .add(Restrictions.eq("referenceValue", externalReference.getReferenceValue()));

            List<Serializable> list = criteria.getExecutableCriteria(getSessionCustom()).list();
            validateExternalReference(list, id, externalReference.getReferenceValue());
        }
    }

    protected Set<OrganisationalUnit> getUserOrganisationalUnits() {
        Set<OrganisationalUnit> organisationalUnits = MultiTenantUtil.getActiveUser().getOrganisationalUnits();
        if (CollectionUtils.isEmpty(organisationalUnits) && CollectionUtils.isEmpty(MultiTenantUtil.getActiveUser().getOrganisationalUnit())) {
            throw new IllegalStateException("filter by org is true, but organisational unit is not set");
        }

        if (CollectionUtils.isNotEmpty(organisationalUnits))
            return organisationalUnits;

        if (CollectionUtils.isNotEmpty(MultiTenantUtil.getActiveUser().getOrganisationalUnit()))
            return MultiTenantUtil.getActiveUser().getOrganisationalUnit();

        throw new IllegalStateException("filter by org is true, but organisational unit is not set");
    }

    protected String getOrgIdsAsString(Collection<OrganisationalUnit> userOrganisationalUnits) {
        List<String> orgIdString = userOrganisationalUnits.stream()
                .map(organisationalUnit -> organisationalUnit.getId().toString()).collect(Collectors.toList());
        return org.apache.commons.lang3.StringUtils.join(orgIdString, ",");
    }

    protected String getOrgIdsAsCode(Collection<OrganisationalUnit> userOrganisationalUnits) {
        List<String> orgIdString = userOrganisationalUnits.stream()
                .map(organisationalUnit -> "'" + organisationalUnit.getCode() + "'").collect(Collectors.toList());
        return org.apache.commons.lang3.StringUtils.join(orgIdString, ",");
    }

    protected <T> List<T> findAll(SearchParams searchParams, DetachedCriteria detachedCriteria) {

        Order resultOrdering = searchParams.isDescending() == true ? Order.desc(searchParams.getOrderBy()) : Order.asc(searchParams.getOrderBy());
        DetachedCriteria criteria = detachedCriteria.addOrder(resultOrdering);

        // Check if we are only searching for active entities
        if (searchParams.isActiveOnly()) {
            criteria.add(Restrictions.eq("active", true));
        }
        @SuppressWarnings("unchecked")
        List<T> results = criteria.getExecutableCriteria(getSessionCustom()).list();

        return results;
    }

    @Override
    public String criteriaToSQLQuery(DetachedCriteria criteria) {
        CriteriaImpl criteriaImpl = (CriteriaImpl) criteria.getExecutableCriteria(getCurrentSession());
        return criteriaToSQLQuery(criteriaImpl);
    }

    @Override
    public String criteriaToSQLQuery(org.hibernate.query.Query query) {
        String hqlQueryString = query.unwrap(Query.class).getQueryString();
        //#hql to sql
        //String hqlQueryString = sb.toString();
        ASTQueryTranslatorFactory queryTranslatorFactory = new ASTQueryTranslatorFactory();
        SessionImplementor hibernateSession = getCurrentSession().getSession().unwrap(SessionImplementor.class);
        QueryTranslator queryTranslator = queryTranslatorFactory.createQueryTranslator("", hqlQueryString, java.util.Collections.EMPTY_MAP, hibernateSession.getFactory(), null);
        queryTranslator.compile(java.util.Collections.EMPTY_MAP, false);
        return queryTranslator.getSQLString();
    }

    public String criteriaToSQLQuery(Criteria criteria) {
        CriteriaImpl criteriaImpl = (CriteriaImpl) criteria;
        SessionImplementor session = (SessionImplementor) criteriaImpl.getSession();
        SessionFactoryImplementor factory = session.getFactory();
        CriteriaQueryTranslator translator = new CriteriaQueryTranslator(factory, criteriaImpl, criteriaImpl.getEntityOrClassName(),
                CriteriaQueryTranslator.ROOT_SQL_ALIAS);
        String[] implementors = factory.getImplementors(criteriaImpl.getEntityOrClassName());

        CriteriaJoinWalker walker = new CriteriaJoinWalker((OuterJoinLoadable) factory.getEntityPersister(implementors[0]),
                translator,
                factory,
                criteriaImpl,
                criteriaImpl.getEntityOrClassName(),
                session.getLoadQueryInfluencers());

        String sql = walker.getSQLString();
        return sql;
    }

    public static String toLIKE(String s) {
        return LIKE + s + LIKE;
    }
}