JpaTools.java

package sk.iway.iwcm.system.jpa;

import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.jpa.querydef.CompoundExpressionImpl;
import org.eclipse.persistence.internal.jpa.querydef.PathImpl;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.jpa.JpaHelper;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReportQuery;
import org.eclipse.persistence.queries.ReportQueryResult;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import sk.iway.iwcm.Constants;
import sk.iway.iwcm.InitServlet;
import sk.iway.iwcm.Logger;
import sk.iway.iwcm.SetCharacterEncodingFilter;
import sk.iway.iwcm.common.CloudToolsForCore;
import sk.iway.iwcm.database.DataSource;
import sk.iway.iwcm.database.JpaDB;
import sk.iway.iwcm.utils.Pair;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.Predicate;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 *  JpaTools.java - podporne nastroje pre JPA
 *
 *@Title        webjet7
 *@Company      Interway s.r.o. (www.interway.sk)
 *@Copyright    Interway s.r.o. (c) 2001-2010
 *@author       $Author: rusho $
 *@version      $Revision: 1.3 $
 *@created      Date: 14.4.2010 15:33:13
 *@modified     $Date: 2004/08/16 06:26:11 $
 */
public class JpaTools
{
	/**
	 * Vrati EntityManager pre zadany nazov DB spojenia (v povodnom JPA to je persistenceUnit)
	 *
	 * @param dbName - nazov DB spojenia
	 * @return EntityManager alebo null, ak pre DB spojenie neexistuje EntityManagerFactory
	 */
	public static EntityManager getEntityManager(String dbName)
	{
		return SetCharacterEncodingFilter.getEntityManager(dbName);
	}

	/**
	 * Vrati EntityManager pre defaultny nazov DB spojenia ("iwcm")
	 * @return an @EntityManager
	 */
	public static EntityManager getEntityManager()
	{
		return getEntityManager("iwcm");
	}


	/**
	 * Vrati JpaEntityManager pre zadany nazov DB spojenia.
	 *
	 * JpaEntityManager je EclipseLink-ove rozsirenie EntityManager-a, ktoreho funkcionalita je rozsirena oproti JPA specifikacii.
	 * Metoda skonvertuje EntityManager, vrateny metodou getEntityManager(dbName).
	 * @param dbName
	 * @return
	 */
	public static JpaEntityManager getEclipseLinkEntityManager(String dbName)
	{
		return JpaHelper.getEntityManager(getEntityManager(dbName));
	}

	/**
	 * Vrati JpaEntityManager pre defaultny nazov DB spojenia ("iwcm")
	 *
	 * JpaEntityManager je EclipseLink-ove rozsirenie EntityManager-a, ktoreho funkcionalita je rozsirena oproti JPA specifikacii.
	 * Metoda skonvertuje EntityManager, vrateny metodou getEntityManager().
	 * @return
	 */
	public static JpaEntityManager getEclipseLinkEntityManager()
	{
		return JpaHelper.getEntityManager(getEntityManager());
	}

	/**
	 * Vrati JpaEntityManager pre nazov DB spojenia ktory je nastaveny v danej classe cez anotaciu DataSource
	 *
	 * JpaEntityManager je EclipseLink-ove rozsirenie EntityManager-a, ktoreho funkcionalita je rozsirena oproti JPA specifikacii.
	 * Metoda skonvertuje EntityManager, vrateny metodou getEntityManager().
	 * @return
	 */
	public static JpaEntityManager getEclipseLinkEntityManager(Class<?> clazz)
	{
		String dbName = "iwcm";
		if (clazz.isAnnotationPresent(DataSource.class)) {

			Annotation annotation = clazz.getAnnotation(DataSource.class);
			DataSource dataSource = (DataSource) annotation;
			dbName = dataSource.name();
		}
		return JpaHelper.getEntityManager(getEntityManager(dbName));
	}

	public static List<String> getJpaClassNames(String rootUrl)
	{
		List<String> foundFiles = new ArrayList<>();

		String basePackage = rootUrl;
		if (basePackage.startsWith("/WEB-INF/classes/")) basePackage = basePackage.substring(17);
		basePackage = basePackage.replace('/', '.');

		Logger.debug(JpaTools.class, "getJpaClassNames.basePackage="+basePackage);

		ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
		scanner.addIncludeFilter(new AnnotationTypeFilter(javax.persistence.Entity.class));
		scanner.addIncludeFilter(new AnnotationTypeFilter(javax.persistence.Converter.class));
		for (BeanDefinition bd : scanner.findCandidateComponents(basePackage))
		{
			Logger.debug(JpaTools.class, "JPA: found class "+bd.getBeanClassName());
			foundFiles.add(bd.getBeanClassName());
		}

		return foundFiles;
	}

	/*
	public static List<String> getJpaClassNames(String rootUrl)
	{
		List<String> foundFiles = new ArrayList<String>();
		if (rootUrl.endsWith("/")== false)
			rootUrl = rootUrl + "/";

		File rootDir = new File(Tools.getRealPath(rootUrl));
		File files[] = rootDir.listFiles();
		if(files==null)
			return foundFiles;

		File myFile;
		int i;
		for (i=0; i<files.length; i++)
		{
			myFile = files[i];
			if (myFile.isDirectory())
			{
				foundFiles.addAll(getJpaClassNames(rootUrl + myFile.getName() + "/"));
			}
			else
			{
				//nacitanie suboru
				String filePath = rootUrl + myFile.getName();
				filePath = filePath.replace('\\', '/');
				if (filePath.startsWith("/WEB-INF/classes/") && (filePath.endsWith("Entity.class") || filePath.endsWith("Bean.class") || filePath.endsWith("/Media.class")) && filePath.endsWith("ActionBean.class")==false)
				{
					filePath = filePath.substring(17,filePath.length()-6).replace("/", ".");
					foundFiles.add(filePath);
					Logger.debug(JpaTools.class, "JPA: adding class: " + filePath);
				}
				else if (filePath.startsWith("/WEB-INF/classes/") && filePath.endsWith("JpaInfo.class"))
				{
					filePath = filePath.substring(17,filePath.length()-6).replace("/", ".");
					Class<? extends JpaInfo> jpaInfoClass = null;
					try
					{
						jpaInfoClass= ReflectUtil.findClass(filePath);
					} catch (ClassNotFoundException e)
					{

						sk.iway.iwcm.Logger.error(e);
					}//Class.forName(filePath)

					if (jpaInfoClass!=null && !jpaInfoClass.isInterface())
					{
						try
						{
							JpaInfo jpaInfo = jpaInfoClass.newInstance();
							List<String> foundClasses = jpaInfo.getJpaClasses();
							foundFiles.addAll(foundClasses);
							Logger.debug(JpaTools.class, "JPA: adding classes: " + foundClasses);
						}
						catch (InstantiationException | IllegalAccessException e)
						{
							sk.iway.iwcm.Logger.error(e);
						}
					}
					foundFiles.add(filePath);
					Logger.debug(JpaTools.class, "JPA: adding class: " + filePath);
				}
			}
		}

		return foundFiles;
	}*/


    public static <T> List<T> findByMatchingProperty(Class<T> clazz, String propertyName, Object propertyValue)
    {
        JpaEntityManager em = null;
        try{
			em = JpaTools.getEclipseLinkEntityManager(clazz);
            ExpressionBuilder builder = new ExpressionBuilder();
            ReadAllQuery dbQuery = new ReadAllQuery(clazz, builder);
            Expression expr = builder.get(propertyName).equal(propertyValue);
            expr = applyDomainId(expr,builder,clazz);
            dbQuery.setSelectionCriteria(expr);

            Query query = em.createQuery(dbQuery);
            query.setHint("javax.persistence.cache.storeMode", "REFRESH");
            List<T> records = JpaDB.getResultList(query);
            return records;
        }catch (Exception e) {
            sk.iway.iwcm.Logger.error(e);
        }finally{
            if (em != null) em.close();
        }
        throw new IllegalStateException("Query did not complete regularly");
    }

	public static <T> T findFirstByMatchingProperty(Class<T> clazz, String propertyName, Object propertyValue)
	{
		JpaEntityManager em = null;
		try{
			em = JpaTools.getEclipseLinkEntityManager(clazz);

			ExpressionBuilder builder = new ExpressionBuilder();
			ReadAllQuery dbQuery = new ReadAllQuery(clazz, builder);
			Expression expr = builder.get(propertyName).equal(propertyValue);
            expr = applyDomainId(expr,builder,clazz);
			dbQuery.setSelectionCriteria(expr);

			Query query = em.createQuery(dbQuery);
			query.setHint("javax.persistence.cache.storeMode", "REFRESH");
			@SuppressWarnings("unchecked")
			List<T> records = query.setMaxResults(1).getResultList();
			if (records == null || records.size() == 0)
				return null;
			return records.get(0);
		}catch (Exception e) {
			sk.iway.iwcm.Logger.error(e);
		}finally{
			if (em != null) em.close();
		}
		throw new IllegalStateException("Query did not complete normally");
	}

	@SuppressWarnings("all")
	@SafeVarargs
	public static <T> List<T> findByProperties(Class<T> clazz, Pair<String, ? extends Object>...properties)
	{
		JpaEntityManager em = null;
		try{
			em = JpaTools.getEclipseLinkEntityManager(clazz);
			ExpressionBuilder builder = new ExpressionBuilder();
			ReadAllQuery dbQuery = new ReadAllQuery(clazz, builder);
			Expression expr = propertiesToExpression(builder, properties);
            expr = applyDomainId(expr,builder,clazz);
			dbQuery.setSelectionCriteria(expr);

			Query query = em.createQuery(dbQuery);
			List<T> records = JpaDB.getResultList(query);

			return records;
		}catch (Exception e) {
			sk.iway.iwcm.Logger.error(e);
		}finally{
			//em.close sa nesmie volat, pretoze to zavre threadLocal EM a dalej potom padne na inom citani, em zatvori SetCharacterEncodingFilter
			//em.close();
		}
		throw new IllegalStateException("Query did not complete normally");
	}

    public static <T> PaginatedBean<T> findPaginatedAndSortedByProperties(Class<T> clazz, Expression filter, int page, int pageSize, String sortField, JpaSortOrderEnum sortOrder)
    {
        PaginatedBean<T> paginatedBean = new PaginatedBean<>();
        ExpressionBuilder builder = new ExpressionBuilder();
        ReadAllQuery dbQuery = new ReadAllQuery(clazz, builder);

        if (sortField != null && sortOrder != null) {
            if (sortOrder.equals(JpaSortOrderEnum.ASC) && !sortField.equals("")) {
                dbQuery.addAscendingOrdering(sortField);
            }
            if (sortOrder.equals(JpaSortOrderEnum.DESC) && !sortField.equals("")) {
                dbQuery.addDescendingOrdering(sortField);
            }
        }

        filter = applyDomainId(filter,builder,clazz);
        dbQuery.setSelectionCriteria(filter);
        JpaEntityManager em = JpaTools.getEclipseLinkEntityManager(clazz);

        try{
            Query query = em.createQuery(dbQuery);

            ReportQuery totalNumberQuery = new ReportQuery(clazz, filter);
//            totalNumberQuery.addCount();
			totalNumberQuery.addCount("COUNT", builder.distinct());
            ReportQueryResult res = (ReportQueryResult) em.createQuery(totalNumberQuery).getSingleResult();
            int total = (int) res.getByIndex(0);

            if (page < 1) page = 1;
//            if (pageSize < 1) pageSize = DEFAULT_COURSES_LIST_PAGE_SIZE;
            if (pageSize < 1) pageSize = 10;
            int offset = pageSize * (page-1);
            paginatedBean.setPage(page);
            paginatedBean.setPageSize(pageSize);
            paginatedBean.setTotal(total);

            query.setFirstResult(offset);
            query.setMaxResults(pageSize);
            List<T> records = JpaDB.getResultList(query);
            paginatedBean.setData(records);

            return paginatedBean;
        } catch (Exception e) {
            sk.iway.iwcm.Logger.error(e);
        } finally{
            em.close();
        }
        throw new IllegalStateException("Query did not complete normally");
    }

	@SuppressWarnings("unchecked")
	@SafeVarargs
	public static <T> T findFirstByProperties(Class<T> clazz, Pair<String, ? extends Object>...properties)
	{
		JpaEntityManager em = null;
		try{
			em = JpaTools.getEclipseLinkEntityManager(clazz);
			ExpressionBuilder builder = new ExpressionBuilder();
			ReadAllQuery dbQuery = new ReadAllQuery(clazz, builder);
			Expression expr = propertiesToExpression(builder, properties);
            expr = applyDomainId(expr,builder,clazz);
			dbQuery.setSelectionCriteria(expr);

			Query query = em.createQuery(dbQuery);
			List<T> records = query.setMaxResults(1).getResultList();
			if (records.size() == 0)
				return null;
			return records.get(0);
		}catch (Exception e) {
			sk.iway.iwcm.Logger.error(e);
		}finally{
			if (em != null) em.close();
		}
		throw new IllegalStateException("Query did not complete normally");
	}

	public static <T> List<T> findBy(Class<T> clazz, Condition... conditions)
	{
		return findBy(clazz, (Integer) null, conditions);
	}

	@SuppressWarnings("unchecked")
	public static <T> List<T> findBy(Class<T> clazz, Integer maxRows, Condition... conditions)
	{
		JpaEntityManager em = null;
		try {
			em = JpaTools.getEclipseLinkEntityManager(clazz);
			ExpressionBuilder object = new ExpressionBuilder();
			ReadAllQuery query = new ReadAllQuery(clazz, object);
			Expression expr = null;
			for (Condition condition : conditions)
			{
				if (null == condition) continue;
				Expression cond = condition.applyTo(object);
				expr = (null == cond) ? expr : (null == expr) ? cond : expr.and(cond);
			}
            expr = applyDomainId(expr,object,clazz);
			query.setSelectionCriteria(expr);
			if (null != maxRows) query.setMaxRows(maxRows.intValue());
			return em.createQuery(query).getResultList();
		} catch (Exception e) {
			sk.iway.iwcm.Logger.error(e);
		} finally {
			if (em != null) em.close();
		}
		throw new IllegalStateException("Query did not complete normally");
	}


	public static <T> void batchDelete(Class<T> clazz, int...ids)
	{
		JpaEntityManager em = JpaTools.getEclipseLinkEntityManager(clazz);
		try
		{
			em.getTransaction().begin();
			for (int id : ids)
			{
				T toBeDeleted = em.getReference(clazz, covertPkToNumber(clazz, id));
				if(!canDeleteDomain(toBeDeleted.getClass(),toBeDeleted))
				{
					return;
				}
				em.remove(toBeDeleted);
			}

			em.getTransaction().commit();
		}
		catch(Exception e)
		{
			sk.iway.iwcm.Logger.error(e);
		}
		finally
		{
			em.close();
		}
	}

	/**
	 * Vymaze podla ID jeden objekt
	 * @param clazz
	 * @param id
	 * @return true - ak vykonalo OK a false ak nastala vynimka
	 */
	public static <T> boolean  delete(Class <T> clazz,int id)
	{
		boolean result=false;
		JpaEntityManager em = JpaTools.getEclipseLinkEntityManager(clazz);
		try
		{
			em.getTransaction().begin();
			T toBeDeleted = em.getReference(clazz, covertPkToNumber(clazz, id));
			if(!canDeleteDomain(toBeDeleted.getClass(),toBeDeleted))
			{
				return result;
			}
			em.remove(toBeDeleted);
			em.getTransaction().commit();
			result = true;
		}
		catch (Exception e)
		{
			sk.iway.iwcm.Logger.error(e);
		}
		finally
		{
			em.close();
		}

		return result;
	}

    /** Nepovolime zmazat zaznam mimo aktualnej domeny
     *
     * @param clazz
     * @param o
     * @return
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
	@SuppressWarnings({"rawtypes", "unchecked"})
	private static boolean canDeleteDomain(Class clazz, Object o) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
    {
        if((InitServlet.isTypeCloud() || Constants.getBoolean("enableStaticFilesExternalDir")) && Constants.getString("jpaFilterByDomainIdBeanList").indexOf(clazz.getName()) != -1)
        {
            Method method = clazz.getMethod("getDomainId");
            int domainIdDelete = (Integer) method.invoke(o);
            if(CloudToolsForCore.getDomainId() != domainIdDelete)
            {
                Logger.debug(JpaTools.class, "Pokus o zmazanie zaznamu mimo domainId (" + domainIdDelete + "). Spravna domainId = " + CloudToolsForCore.getDomainId());
                return false;
            }
        }
        return true;
    }

	public static <T> List<T> getAll(Class<T> clazz)
	{
		JpaEntityManager em = JpaTools.getEclipseLinkEntityManager(clazz);
        ExpressionBuilder object = new ExpressionBuilder();
		ReadAllQuery dbQuery = new ReadAllQuery(clazz, object);
        Expression expr = applyDomainId(null, object, clazz);
        dbQuery.setSelectionCriteria(expr);
		Query query = em.createQuery(dbQuery);
		query.setHint("javax.persistence.cache.storeMode", "REFRESH");
		List<T> records = JpaDB.getResultList(query);

		return records;
	}

	/**
	 * Urci, ci dane meno datasource je urcene pre JPA, je to len iwcm, alebo nazov obsahujuci jpa (je to takto kvoli rychlosti startu WJ a optimalizacii pamate)
	 * @param name
	 * @return
	 */
	public static boolean isJPADatasource(String name)
	{
		if ("iwcm".equals(name) || name.indexOf("jpa")!=-1) return true;
		return false;
	}

	/**
	 * Podmienka na vyhladanie objektov pomocou metody "findBy".
	 */
	public interface Condition
	{
		/**
		 * Vytvori podmienku pre testovany objekt.
		 * Vysledna hodnota "null" znamena: tuto podmienku neberte do uvahy (cize automaticky ju splna kazdy objekt).
		 *
		 * @param object  vyraz vyjadrujuci testovany objekt
		 * @return        kriterium pre testovany objekt, alebo null
		 */
		public Expression applyTo(Expression object);
	}

	@SuppressWarnings("unchecked")
	public static Expression propertiesToExpression(ExpressionBuilder builder, Pair<String, ? extends Object>...properties) {
	    Expression  expr = null;
        for (Pair<String, ? extends Object> property : properties)
        {
            Expression nestedProperty = null;
            for (String subProperty : property.first.split("\\.")) {
                nestedProperty = (nestedProperty == null) ? builder.get(subProperty) : nestedProperty.get(subProperty);
            }
			if (nestedProperty != null) {
				Expression newCondition = nestedProperty.equal(property.second);
				expr = (expr == null) ? newCondition : expr.and(newCondition) ;
			}
        }
        return expr;
    }

	@SuppressWarnings("rawtypes")
    private static Expression applyDomainId(Expression exprParam,ExpressionBuilder builderParam,Class clazz)
    {
        Expression expr = exprParam;
        if((InitServlet.isTypeCloud() || Constants.getBoolean("enableStaticFilesExternalDir")) && Constants.getString("jpaFilterByDomainIdBeanList").indexOf(clazz.getName()) != -1)
        {

            ExpressionBuilder builder = builderParam;
            if(expr == null)
            {
                return builder.get("domainId").equal(CloudToolsForCore.getDomainId());
            }
            return  expr.and(builder.get("domainId").equal(CloudToolsForCore.getDomainId()));
        }
        return expr;
	 }

	 /**
	  * Ziska PK ako Long pre repository, alebo Integer pre standardne JPA
	  * @param <T>
	  * @param clazz
	  * @param id
	  * @return
	  */
	 public static <T> Number covertPkToNumber(Class<T> clazz, int id) {
		Number n = null;
		//musime cez String, pretoze volanie isInstance z nejakeho dovodu vracalo false
		if (clazz.getSuperclass().toString().contains("ActiveRecordRepository")) n = Long.valueOf(id);
		else n = Integer.valueOf(id);
		return n;
	 }

	 /**
	  * Removes predicate for field name from predicates list
	  * @param name
	  * @param predicates
	  */
	 public static void removePredicateWithName(String name, List<Predicate> predicates) {
		try {
			//remove predicates with name groupId
			List<Predicate> predicatesToRemove = new ArrayList<>();
			for (Predicate p : predicates) {
				if (p instanceof CompoundExpressionImpl) {
					CompoundExpressionImpl predicate = (CompoundExpressionImpl) p;
					if (predicate.getChildExpressions().size()>0 && predicate.getChildExpressions().get(0) instanceof PathImpl) {
						@SuppressWarnings("rawtypes")
						PathImpl path = (PathImpl) predicate.getChildExpressions().get(0);
						if (path.getCurrentNode().getName().equals(name)) {
							predicatesToRemove.add(predicate);
						}
					}
				}
			}
			predicates.removeAll(predicatesToRemove);
		} catch (Exception e) {
			Logger.error(e);
		}
	}
}