GenericHbmDAO.java

package org.europa.together.application;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import java.sql.Timestamp;
import java.util.Date;
import org.europa.together.business.GenericDAO;
import org.europa.together.business.JsonTools;
import org.europa.together.business.Logger;
import org.europa.together.domain.LogLevel;
import org.europa.together.domain.JpaPagination;
import org.europa.together.exceptions.DAOException;
import org.europa.together.exceptions.JsonProcessingException;
import org.europa.together.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

/**
 * Abstract implementation of Domain Access Object (DAO) Pattern.
 *
 * @param <T> as template.
 * @param <PK> as PrimaryKey
 */
@SuppressWarnings("unchecked")
@Repository
@Transactional
public abstract class GenericHbmDAO<T, PK extends Serializable>
        implements GenericDAO<T, PK> {

    private static final long serialVersionUID = 2L;
    private static final Logger LOGGER = new LogbackLogger(GenericHbmDAO.class);

    /**
     * JPA Entity Manager for Transactions.
     */
    @PersistenceContext
    @SuppressWarnings("checkstyle:visibilitymodifier")
    public transient EntityManager mainEntityManagerFactory;

    @Autowired
    private JsonTools<T> jsonTools;

    private final Class<T> genericType;

    /**
     * Constructor.
     */
    public GenericHbmDAO() {
        ParameterizedType clazz = (ParameterizedType) getClass().getGenericSuperclass();
        genericType = (Class<T>) clazz.getActualTypeArguments()[0];
        LOGGER.log("instance class", LogLevel.INFO);
    }

    @Override
    public boolean create(final T object) {
        boolean success = false;
        if (object != null) {
            mainEntityManagerFactory.persist(object);
            success = true;
        }
        return success;
    }

    @Override
    public void delete(final PK id)
            throws DAOException {
        T foundObject = find(id);
        if (foundObject != null) {
            mainEntityManagerFactory.remove(foundObject);
        } else {
            throw new DAOException("delete:" + id.toString());
        }
    }

    @Override
    public void update(final PK id, final T object)
            throws DAOException {
        if (object != null && this.find(id) != null) {
            mainEntityManagerFactory.merge(object);
            LOGGER.log("DAO (" + object.getClass().getSimpleName() + ") update",
                    LogLevel.TRACE);
        } else {
            String message = "update faild. -> ";
            if (id != null) {
                message += id.toString();
            }
            if (object != null) {
                message += " : " + object.toString();
            }
            throw new DAOException(message);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public long countAllElements() {
        CriteriaBuilder builder = mainEntityManagerFactory.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(genericType);
        // create Criteria
        query.from(genericType);
        return mainEntityManagerFactory.createQuery(query).getResultList().size();
    }

    @Override
    @Transactional(readOnly = true)
    public List<T> listAllElements(final JpaPagination pivotElement) {
        LOGGER.log("GenericDAO: " + pivotElement.toString(), LogLevel.DEBUG);
        CriteriaBuilder builder = mainEntityManagerFactory.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(genericType);
        Root<T> root = query.from(genericType);
        int limit = pivotElement.getPageSize();
        // skip pagination
        int countedResults = mainEntityManagerFactory.createQuery(query).getResultList().size();
        if (limit == 0 || limit >= countedResults) {
            limit = countedResults;
        }
        // order the result set
        if (pivotElement.getSorting().equals(JpaPagination.ORDER_ASC)) {
            query.orderBy(builder.asc(root.get(pivotElement.getPrimaryKey())));
        } else {
            query.orderBy(builder.desc(root.get(pivotElement.getPrimaryKey())));
        }
        if (!StringUtils.isEmpty(pivotElement.getAdditionalOrdering())) {
            query.orderBy(builder.asc(root.get(pivotElement.getAdditionalOrdering())));
        }
        // put everything together
        List<Predicate> filters = calculatePredicates(builder, root, pivotElement);
        if (!filters.isEmpty()) {
            query.where(filters.toArray(new Predicate[filters.size()]));
        }
        List<T> results = mainEntityManagerFactory.createQuery(query)
                .setMaxResults(limit).getResultList();
        return results;
    }

    @Override
    @Transactional(readOnly = true)
    public PK getPrimaryKeyOfObject(final T object) {
        PK returnVal = null;
        if (object != null) {
            returnVal = (PK) mainEntityManagerFactory.getEntityManagerFactory()
                    .getPersistenceUnitUtil().getIdentifier(object);
            LOGGER.log("DAO (" + object.getClass().getSimpleName() + ") IDX="
                    + returnVal, LogLevel.TRACE);
        } else {
            LOGGER.log("getPrimaryKeyOfObject() : Object is null.", LogLevel.WARN);
        }
        return returnVal;
    }

    @Override
    public String serializeAsJson(final T object)
            throws JsonProcessingException {
        return jsonTools.serializeAsJsonObject(object);
    }

    @Override
    public T deserializeJsonAsObject(final String json, final Class<T> object)
            throws JsonProcessingException, ClassNotFoundException {
        return jsonTools.deserializeJsonAsObject(json, object);
    }

    @Override
    public List<T> deserializeJsonAsList(final String json)
            throws JsonProcessingException, ClassNotFoundException {
        return jsonTools.deserializeJsonAsList(json);
    }

    @Override
    @Transactional(readOnly = true)
    public T find(final PK id) {
        T retVal = mainEntityManagerFactory.find(genericType, id);
        return retVal;
    }

    //### ######################################################################
    private List<Predicate> calculatePredicates(final CriteriaBuilder builder, final Root<T> root,
            final JpaPagination pivotElement) {
        List<Predicate> filters = new ArrayList<>();
        // get the break element
        if (!StringUtils.isEmpty(pivotElement.getPageBreak())) {
            if (pivotElement.getPaging().equals(JpaPagination.PAGING_FOREWARD)) {
                filters.add(builder.greaterThanOrEqualTo(root.get(pivotElement.getPrimaryKey()),
                        pivotElement.getPageBreak()
                ));
            } else {
                filters.add(builder.lessThanOrEqualTo(root.get(pivotElement.getPrimaryKey()),
                        pivotElement.getPageBreak()
                ));
            }
        }
        if (!pivotElement.getFilterStringCriteria().isEmpty()) {
            for (Map.Entry<String, String> entry
                    : pivotElement.getFilterStringCriteria().entrySet()) {
                filters.add(
                        builder.equal(root.get(entry.getKey()), entry.getValue()));
            }
        } else {
            LOGGER.log("No String based filters are set.", LogLevel.DEBUG);
        }
        if (!pivotElement.getFilterBooleanCriteria().isEmpty()) {
            for (Map.Entry<String, Boolean> entry
                    : pivotElement.getFilterBooleanCriteria().entrySet()) {
                filters.add(
                        builder.equal(root.get(entry.getKey()), entry.getValue()));
            }
        } else {
            LOGGER.log("No Boolean based filters are set.", LogLevel.DEBUG);
        }
        if (!pivotElement.getFilterIntegerCriteria().isEmpty()) {
            for (Map.Entry<String, Integer> entry
                    : pivotElement.getFilterIntegerCriteria().entrySet()) {
                filters.add(
                        builder.equal(root.get(entry.getKey()), entry.getValue()));
            }
        } else {
            LOGGER.log("No Integer based filters are set.", LogLevel.DEBUG);
        }
        if (!pivotElement.getFilterFloatCriteria().isEmpty()) {
            for (Map.Entry<String, Float> entry
                    : pivotElement.getFilterFloatCriteria().entrySet()) {
                filters.add(
                        builder.equal(root.get(entry.getKey()), entry.getValue()));
            }
        } else {
            LOGGER.log("No Integer based filters are set.", LogLevel.DEBUG);
        }
        if (!pivotElement.getFilterDateCriteria().isEmpty()) {
            for (Map.Entry<String, Date> entry
                    : pivotElement.getFilterDateCriteria().entrySet()) {
                filters.add(
                        builder.equal(root.get(entry.getKey()),
                                Timestamp.from(entry.getValue().toInstant())));
            }
        } else {
            LOGGER.log("No Date based filters are set.", LogLevel.DEBUG);
        }
        return filters;
    }
}