package info.elexis.server.core.connector.elexis.services;

import static info.elexis.server.core.connector.elexis.internal.ElexisEntityManager.*;

import java.util.List;
import java.util.Optional;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.SingularAttribute;

import org.eclipse.persistence.jpa.JpaQuery;

import info.elexis.server.core.connector.elexis.internal.ElexisEntityManager;
import info.elexis.server.core.connector.elexis.jpa.model.annotated.AbstractDBObject;
import info.elexis.server.core.connector.elexis.jpa.model.annotated.AbstractDBObjectIdDeleted;
import info.elexis.server.core.connector.elexis.jpa.model.annotated.AbstractDBObjectIdDeleted_;

/**
 * This class tries to resemble the Query class known by Elexis user by
 * employing JPA CriteriaQueries.
 * 
 * 
 * @param <T>
 */
public class JPAQuery<T extends AbstractDBObject> {

	public static enum QUERY {
		LIKE, EQUALS, LESS_OR_EQUAL, GREATER, NOT_LIKE, NOT_EQUALS
	};

	private CriteriaBuilder cb;
	private CriteriaQuery<T> cq;
	private Root<T> root;
	private TypedQuery<T> query;

	private Predicate predicate;
	private boolean includeDeleted;
	
	private final Class<T> clazz;
	

	public JPAQuery(Class<T> clazz) {
		this(clazz, false);
	}

	public JPAQuery(Class<T> clazz, boolean includeDeleted) {
		cb = ElexisEntityManager.getCriteriaBuilder();
		cq = cb.createQuery(clazz);
		root = cq.from(clazz);
		this.clazz = clazz;
		this.includeDeleted = includeDeleted;
	}

	public void add(@SuppressWarnings("rawtypes") SingularAttribute attribute, QUERY qt, Object object) {
		Predicate predIn = derivePredicate(attribute, qt, object);

		if (predicate == null) {
			predicate = predIn;
		} else {
			predicate = cb.and(predicate, predIn);
		}
	}

	public void add(SingularAttribute<?, Boolean> attribute, QUERY qt, boolean bool) {
		Predicate predIn = derivePredicate(attribute, qt, bool);

		if (predicate == null) {
			predicate = predIn;
		} else {
			predicate = cb.and(predicate, predIn);
		}
	}

	public void addLikeNormalized(@SuppressWarnings("rawtypes") SingularAttribute attribute, String value) {
		Predicate predIn = cb.like(cb.lower(root.get(attribute)), value.toString().toLowerCase());
		if (predicate == null) {
			predicate = predIn;
		} else {
			predicate = cb.and(predicate, predIn);
		}
	}

	public void or(@SuppressWarnings("rawtypes") SingularAttribute attribute, QUERY qt, String string) {
		Predicate predIn = derivePredicate(attribute, qt, string);

		if (predicate == null) {
			predicate = predIn;
		} else {
			predicate = cb.or(predicate, predIn);
		}
	}

	@SuppressWarnings("unchecked")
	private Predicate derivePredicate(@SuppressWarnings("rawtypes") SingularAttribute attribute, QUERY qt,
			Object value) {
		switch (qt) {
		case LIKE:
			return cb.like(root.get(attribute), value.toString());
		case EQUALS:
			return cb.equal(root.get(attribute), value);
		case LESS_OR_EQUAL:
			Path<Integer> pathLE = root.get(attribute);
			return cb.le(pathLE, Integer.parseInt(value.toString()));
		case GREATER:
			Path<Integer> pathG = root.get(attribute);
			return cb.gt(pathG, Integer.parseInt(value.toString()));
		case NOT_LIKE:
			return cb.not(cb.like(root.get(attribute), value.toString()));
		case NOT_EQUALS:
			return cb.not(cb.equal(root.get(attribute), value));
		default:
			throw new IllegalArgumentException();
		}
	}

	public List<T> execute() {
		if (!includeDeleted) {
			if(AbstractDBObjectIdDeleted.class.isAssignableFrom(clazz)) {
				add(AbstractDBObjectIdDeleted_.deleted, QUERY.EQUALS, false);
			}
		}
		cq = cq.where(predicate);
		query = createEntityManager().createQuery(cq);
		return query.getResultList();
	}

	@Override
	public String toString() {
		if (query != null) {
			// will only print SQL string after execute()
			return query.unwrap(JpaQuery.class).getDatabaseQuery().getSQLString();
		}
		return super.toString();
	}

	/**
	 * @return the element if the respective list contains exactly one element
	 */
	public Optional<T> executeGetSingleResult() {
		List<T> result = execute();
		if (result !=  null && result.size() == 1) {
			return Optional.of(result.get(0));
		}
		return Optional.empty();
	}

}

@@ -192,16 +192,56 @@
 ional;%0A%0A
+import javax.persistence.EntityManager;%0A
 import j
@@ -3684,16 +3684,17 @@
  %7B%0A%09%09%09if
+ 
 (Abstrac
@@ -3745,16 +3745,16 @@
 azz)) %7B%0A
-
 %09%09%09%09add(
@@ -3854,37 +3854,84 @@
 ;%0A%09%09
-query = createEntityManager()
+EntityManager entityManager = createEntityManager();%0A%09%09query = entityManager
 .cre
@@ -3944,16 +3944,25 @@
 ry(cq);%0A
+%09%09try %7B%0A%09
 %09%09return
@@ -3981,24 +3981,68 @@
 sultList();%0A
+%09%09%7D finally %7B%0A%09%09%09entityManager.close();%0A%09%09%7D%0A
 %09%7D%0A%0A%09@Overri

Fix possible memory leak in JPAQuery