GeneralRepositoryImpl.java

package com.tradecloud.repository.impl;

import com.tradecloud.common.externalreference.ExternalReference;
import com.tradecloud.common.externalreference.IntegratedSystem;
import com.tradecloud.domain.common.IntegratedPersistenceBase;
import com.tradecloud.domain.costing.clean.CostedOrder;
import com.tradecloud.domain.infrastructure.persistence.CriteriaBuilder;
import com.tradecloud.domain.party.ServiceProvider;
import com.tradecloud.domain.place.NamedPlace;
import com.tradecloud.domain.search.SearchParams;
import com.tradecloud.repository.GeneralRepository;
import com.tradecloud.repository.SearchMetaParams;
import com.tradecloud.repository.base.impl.BaseHibernateDaoSupport;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.jdbc.ReturningWork;
import org.hibernate.query.NativeQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * Default implementation of the {@code GeneralRepository} interface.
 */
@Repository(value = "generalRepository")
@Primary
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public class GeneralRepositoryImpl extends BaseHibernateDaoSupport implements GeneralRepository, Serializable {

    private static final long serialVersionUID = 1L;
    private static final Logger log = Logger.getLogger(GeneralRepositoryImpl.class);

    @Autowired
    private CacheManager cacheManager;

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findAll(Class<T> c) {
        StopWatch sw = new StopWatch();
        sw.start();
        List<T> results = (List<T>) loadAll(c);
        sw.stop();
        log.debug("Found " + results.size() + " results. Class=" + c + ". Time=" + sw.getTime());
        return results;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findAll(Class<T> c, SearchParams searchParams) {
        StopWatch sw = new StopWatch();
        sw.start();
        Order resultOrdering = searchParams.isDescending() == true ? Order.desc(searchParams.getOrderBy()) : Order.asc(searchParams.getOrderBy());
        DetachedCriteria criteria = DetachedCriteria.forClass(c).addOrder(resultOrdering);

        // Check if we are only searching for active entities
        if (searchParams.isActiveOnly()) {
            criteria.add(Restrictions.eq("active", true));
        }
        if (c.equals(ServiceProvider.class)) {
            criteria.add(Restrictions.eq("costCompareOnly", searchParams.isCostCompareOnly()));
        }
        @SuppressWarnings("unchecked")
        List<T> results = criteria.getExecutableCriteria(getSessionCustom()).list();
        sw.stop();
        log.debug("Found " + results.size() + " results. Class=" + c + ". Time=" + sw.getTime());
        return results;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findAllNotActive(Class<T> c, SearchParams searchParams) {
        Order resultOrdering = searchParams.isDescending() == true ? Order.desc(searchParams.getOrderBy()) : Order.asc(searchParams.getOrderBy());
        DetachedCriteria criteria = DetachedCriteria.forClass(c).addOrder(resultOrdering);
        criteria.add(Restrictions.eq("active", false));
        if (c.equals(ServiceProvider.class)) {
            criteria.add(Restrictions.eq("costCompareOnly", searchParams.isCostCompareOnly()));
        }
        @SuppressWarnings("unchecked")
        List<T> results = criteria.getExecutableCriteria(getSessionCustom()).list();
        return results;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findAllActive(Class<T> c) {
        DetachedCriteria criteria = DetachedCriteria.forClass(c);
        criteria.add(Restrictions.eq("active", true));
        @SuppressWarnings("unchecked")
        List<T> results = criteria.getExecutableCriteria(getSessionCustom()).list();
        return results;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> int count(Class<T> c) {
        return findAll(c).size();
    }

    @Override
    public <T> long count(Class<T> c, Boolean active) {
        DetachedCriteria criteria = DetachedCriteria.forClass(c);

        // Check if we are only searching for active entities
        if (active != null) {
            criteria.add(Restrictions.eq("active", active));
        }
        long count = getExecutableCriteriaCount(criteria);
        log.debug("Found " + count + " results. Class=" + c + ".");
        return count;
    }

    @Override
    public Object save(final Object entity) {
        // TODO. What is the point of returning the passed in entity?
        getCurrentSession().save(entity);
        return entity;
    }

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

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

    }

    @Override
    public void deleteNoFlush(Object entity) {
        try {
            getCurrentSession().delete(entity);

        } catch (Exception e) {
            if (entity != null) {
                handleDeleteConstraint(entity.getClass().getSimpleName(), e);
            }
            throw e;
        }

    }

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

    @Override
    public void refresh(Object entity) {
        getCurrentSession().refresh(entity);
    }

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

    @Override
    public void evict(Object entity) {
        if (entity != null) {
            getCurrentSession().evict(entity);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T retrieve(Class<T> c, Long id) {
        return (T) getCurrentSession().get(c, id);
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T getObject(Class<T> c, Serializable id) {
        return (T) getCurrentSession().get(c, id);
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T retrieve(Class<T> c, String code) {
        return (T) getCurrentSession().load(c, code);
    }

    /**
     * @deprecated Turns out the reference on all the entity is really EXTERNAL
     * reference. Be careful using this.
     */
    @Deprecated
    @Override
    @Transactional(readOnly = true)
    public <T> T findByReference(Class<T> c, String reference) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.eq("reference", reference)).uniqueResult();
        return result;
    }

    @Transactional(readOnly = true)
    public <T> T findByElcReference(Class<T> c, String reference) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.ilike("orderReference", reference)).uniqueResult();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T findByExternalReference(Class<T> c, String externalReference) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.eq("externalReference", externalReference)).uniqueResult();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findByExternalReferenceList(Class<T> c, String externalReference) {
        @SuppressWarnings("unchecked")
        List<T> result = getCurrentSession().createCriteria(c).add(Restrictions.eq("externalReference", externalReference)).list();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T findByName(Class<T> c, String name) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.eq("name", name)).uniqueResult();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findByNameList(Class<T> c, String name) {
        @SuppressWarnings("unchecked")
        List<T> result = getCurrentSession().createCriteria(c).add(Restrictions.eq("name", name).ignoreCase()).list();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T findByNameAndReferenceIgnoreCase(Class<T> c, String name, String externalReference) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.eq("name", name).ignoreCase())
                .add(Restrictions.eq("externalReference", externalReference).ignoreCase())
                .uniqueResult();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> findByNameAndReferenceIgnoreCaseList(Class<T> c, String name, String externalReference) {
        @SuppressWarnings("unchecked")
        List<T> result = getCurrentSession().createCriteria(c).add(Restrictions.eq("name", name).ignoreCase())
                .add(Restrictions.eq("externalReference", externalReference).ignoreCase())
                .list();

        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T findByNumberAndReferenceIgnoreCase(Class<T> c, String number, String reference) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.eq("number", number).ignoreCase())
                .add(Restrictions.eq("reference", reference).ignoreCase())
                .uniqueResult();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T findByNumberIgnoreCase(Class<T> c, String number) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.eq("number", number).ignoreCase())
                .uniqueResult();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T findByNumber(Class<T> c, BigInteger number) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.eq("number", number))
                .uniqueResult();
        return result;
    }

    @Override
    public <T> List<T> findAllByNumberIgnoreCase(Class<T> entityClass, String number) {
        javax.persistence.criteria.CriteriaBuilder cb = getSessionCustom().getCriteriaBuilder();
        CriteriaQuery<T> query = cb.createQuery(entityClass);
        Root<T> root = query.from(entityClass);
        Predicate condition = cb.equal(cb.lower(root.get("number")), number.toLowerCase());
        query.select(root).where(condition);
        return getSessionCustom().createQuery(query).getResultList();
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T findByCode(Class<T> c, String code) {
        @SuppressWarnings("unchecked")
        T result = (T) getCurrentSession().createCriteria(c).add(Restrictions.eq("code", code)).uniqueResult();
        return result;
    }

    @Override
    public BigInteger nextValue(String sequence) {
        SQLQuery sequenceResult
                = getCurrentSession().createSQLQuery("select nextval('" + sequence + "')");
        return (BigInteger) sequenceResult.uniqueResult();
    }

    @Override
    public Connection getConnection() {
        @SuppressWarnings("deprecation")
        Connection connection = getCurrentSession().getSessionFactory().openSession().doReturningWork(new ReturningWork<Connection>() {
            @Override
            public Connection execute(Connection connection) throws SQLException {
                return connection;
            }
        });
        return connection;
    }

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

    @Override
    public void initialize(final Object entity) {
        initialize(entity);
    }

    protected <T> List<T> getExecutableCriteriaList(DetachedCriteria detachedCriteria, Class<T> c, SearchMetaParams searchMetaParams) {
        // The class parameter is not strictly needed, but it will ensure you have don't have the wrong type of list.

        if (searchMetaParams != null && searchMetaParams.getOrderBy() != null) {
            detachedCriteria.addOrder(CriteriaBuilder.createOrdering(searchMetaParams.getOrderBy(), searchMetaParams.isAsc()));
        }

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

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

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

    protected <T> List<T> getExecutableCriteriaList(DetachedCriteria criteria, Class<T> c) {
        // The class parameter is not strictly needed, but it will ensure you have don't have the wrong type of list.
        return (List<T>) criteria.getExecutableCriteria(getSessionCustom()).list();
    }

    protected long getExecutableCriteriaCount(DetachedCriteria detachedCriteria) {
        detachedCriteria.setProjection(Projections.rowCount());
        long count = (Long) detachedCriteria.getExecutableCriteria(getSessionCustom()).list().get(0);
        log.debug("Found " + count + " results for count.");
        return count;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> T retrieve(Class<T> c, ExternalReference externalReference) {
        DetachedCriteria criteria = DetachedCriteria.forClass(c);
        criteria.add(Restrictions.eq("active", true));
        DetachedCriteria eventCrit = criteria.createCriteria("externalReferences")
                .add(Restrictions.eq("integratedSystem", externalReference.getIntegratedSystem()))
                .add(Restrictions.eq("referenceValue", externalReference.getReferenceValue()));
        eventCrit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        T result = (T) criteria.getExecutableCriteria(getSessionCustom()).uniqueResult();
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public <T> List<T> getAllUsedServiceProvidersByInvoices() {
        String queryString = "from ServiceProvider sp where sp.id "
                + "in (select spi.serviceProvider.id from ServiceProviderInvoice spi) order by sp.name asc";
        Query query = getCurrentSession().createQuery(queryString);
        return (List<T>) query.list();
    }

    @Override
    public List<Object[]> executeSql(String s, Map<String, Object> params) {
        NativeQuery sqlQuery = getCurrentSession().createSQLQuery(s);
        if (params != null && !params.isEmpty()) {
            Set<String> strings = params.keySet();
            for (String key : strings) {
                sqlQuery.setParameter(key, params.get(key));
            }
        }
        return sqlQuery.list();
    }

    @Override
    public void executeInsertSql(String s) {
        getCurrentSession().createSQLQuery(s).executeUpdate();
    }

    @Override
    public <T> void batchSave(List<T> entities) {
        for (Object o : entities) {
            getCurrentSession().persist(o);
        }
    }

    @Override
    public <T> List batchMerge(List<T> entities) {
        for (Object o : entities) {
            o = getCurrentSession().merge(o);
        }
        return entities;
    }

    @Override
    public Session getCurrentSession() {
        return super.getCurrentSession();
    }

    @Override
    @Transactional(readOnly = true)
    public <T extends IntegratedPersistenceBase> String findReferenceByIntegratedSystem(Class<T> c,
                                                                                        IntegratedSystem integratedSystem) {
        DetachedCriteria criteria = DetachedCriteria.forClass(c);
        criteria.createAlias("externalReferences", "extRefs");
        criteria.add(Restrictions.eq("active", true));
        criteria.add(Restrictions.eq("extRefs.integratedSystem", integratedSystem));
        criteria.setProjection(Projections.property("extRefs.referenceValue"));
        return (String) criteria.getExecutableCriteria(getSessionCustom()).uniqueResult();
    }

    @Override
    public List<CostedOrder> findCostedOrders(Set<Long> purchaseOrderIds) {
        StringBuilder stringBuilder = new StringBuilder(" select co from CostedOrder  co,PurchaseOrder  po  where ")
                .append(" po.number=co.number and po.orderReference=co.reference ")
                .append(" and  po.id in (:purchaseOrderIds) ");
        return getCurrentSession().createQuery(stringBuilder.toString()).setParameterList("purchaseOrderIds", purchaseOrderIds).list();
    }

    @Override
    public void evictAllCache() {
        cacheManager.getCacheNames()
                .forEach(n -> cacheManager.getCache(n).clear());

    }

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

    public Optional<NamedPlace> findNamedPlaceRegExp(String externalReference) {
        String cleanedReference = preprocessReference(externalReference);

        List<NamedPlace> results = getCurrentSession()
                .createQuery(
                        "FROM NamedPlace np " +
                                "WHERE LOWER(FUNCTION('regexp_replace', np.name, '\\s+', '', 'g')) " +
                                "LIKE LOWER(FUNCTION('regexp_replace', :searchTerm, '\\s+', '', 'g'))",
                        NamedPlace.class)
                .setParameter("searchTerm", cleanedReference)
                .getResultList();

        return results.stream().findFirst();
    }

    private String preprocessReference(String reference) {
        return reference == null ? "" : reference.strip().toLowerCase().replaceAll("\\s+", "");
    }
}