All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.katharsis.jpa.internal.query.backend.criteria.JpaCriteriaQueryBackend Maven / Gradle / Ivy

There is a newer version: 3.0.2
Show newest version
package io.katharsis.jpa.internal.query.backend.criteria;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.MapJoin;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;

import io.katharsis.jpa.internal.meta.MetaAttribute;
import io.katharsis.jpa.internal.meta.MetaAttributePath;
import io.katharsis.jpa.internal.meta.MetaEntity;
import io.katharsis.jpa.internal.meta.MetaKey;
import io.katharsis.jpa.internal.query.JoinRegistry;
import io.katharsis.jpa.internal.query.MetaComputedAttribute;
import io.katharsis.jpa.internal.query.QueryUtil;
import io.katharsis.jpa.internal.query.backend.JpaQueryBackend;
import io.katharsis.jpa.query.criteria.JpaCriteriaExpressionFactory;
import io.katharsis.queryspec.Direction;
import io.katharsis.queryspec.FilterOperator;

public class JpaCriteriaQueryBackend implements JpaQueryBackend, Order, Predicate, Expression> {

	private CriteriaQuery criteriaQuery;

	private JoinRegistry, Expression> joinHelper;

	protected CriteriaBuilder cb;

	private From root;

	private Root parentFrom;

	private JpaCriteriaQueryImpl queryImpl;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public JpaCriteriaQueryBackend(JpaCriteriaQueryImpl query, EntityManager em, Class clazz, Class parentEntityClass,
			MetaAttribute parentAttr, boolean parentIdSelection) {
		this.queryImpl = query;

		cb = em.getCriteriaBuilder();
		criteriaQuery = (CriteriaQuery) cb.createQuery();

		if (parentEntityClass != null) {
			parentFrom = criteriaQuery.from(parentEntityClass);
			root = parentFrom.join(parentAttr.getName());
			joinHelper = new JoinRegistry<>(this, query);
			joinHelper.putJoin(new MetaAttributePath(), root);
			
			if(parentIdSelection){
				Expression parentIdExpr = getParentIdExpression(parentAttr);
				criteriaQuery.multiselect((List)Arrays.asList(parentIdExpr, root));
			}else{
				criteriaQuery.select(root);
			}
		}
		else {
			root = criteriaQuery.from(clazz);
			joinHelper = new JoinRegistry<>(this, query);
			joinHelper.putJoin(new MetaAttributePath(), root);
			criteriaQuery.select(root);
		}
	}

	private Expression getParentIdExpression(MetaAttribute parentAttr){
		MetaEntity parentEntity = parentAttr.getParent().asEntity();
		MetaKey primaryKey = parentEntity.getPrimaryKey();
		List elements = primaryKey.getElements();
		if (elements.size() != 1) {
			throw new UnsupportedOperationException("composite primary keys not supported yet");
		}
		MetaAttribute primaryKeyAttr = elements.get(0);
		return parentFrom.get(primaryKeyAttr.getName());
	}
	
	@Override
	public Expression getAttribute(MetaAttributePath attrPath) {
		return joinHelper.getEntityAttribute(attrPath);
	}

	@Override
	public void addPredicate(Predicate predicate) {
		Predicate restriction = criteriaQuery.getRestriction();
		if (restriction != null) {
			criteriaQuery.where(restriction, predicate);
		}
		else {
			criteriaQuery.where(predicate);
		}
	}

	@Override
	public From getRoot() {
		return root;
	}

	@Override
	public void setOrder(List list) {
		criteriaQuery.orderBy(list);
	}

	@Override
	public List getOrderList() {
		return new ArrayList<>(criteriaQuery.getOrderList());
	}

	@Override
	public Order newSort(Expression expr, Direction dir) {
		if (dir == Direction.ASC) {
			return cb.asc(expr);
		}
		else {
			return cb.desc(expr);
		}
	}

	public CriteriaBuilder getCriteriaBuilder() {
		return cb;
	}

	@Override
	public void distinct() {
		criteriaQuery.distinct(true);
	}

	public CriteriaQuery getCriteriaQuery() {
		return criteriaQuery;
	}

	@Override
	public void addParentPredicate(MetaAttribute primaryKeyAttr) {
		List parentIds = queryImpl.getParentIds();
		Path parentIdPath = parentFrom.get(primaryKeyAttr.getName());
		addPredicate(parentIdPath.in(new ArrayList(parentIds)));
	}

	@Override
	public boolean hasManyRootsFetchesOrJoins() {
		return QueryUtil.hasManyRootsFetchesOrJoins(criteriaQuery);
	}

	@Override
	public void addSelection(Expression expression, String name) {
		Selection selection = criteriaQuery.getSelection();

		List> newSelection = new ArrayList<>();
		if (selection != null) {
			if (selection.isCompoundSelection()) {
				newSelection.addAll(selection.getCompoundSelectionItems());
			}
			else {
				newSelection.add(selection);
			}
		}
		newSelection.add(expression);
		criteriaQuery.multiselect(newSelection);
	}

	@Override
	public Expression getExpression(Order order) {
		return order.getExpression();
	}

	@Override
	public boolean containsRelation(Expression expression) {
		return QueryUtil.containsRelation(expression);
	}

	public Predicate ilike(Expression expr, String val) {
		return cb.like(cb.lower(expr), val.toLowerCase());
	}

	private Predicate negateIfNeeded(Predicate p, FilterOperator fc) {
		if (fc.equals(FilterOperator.NEQ))
			return cb.not(p);
		return p;
	}

	@Override
	public Predicate buildPredicate(FilterOperator operator, MetaAttributePath attrPath, Object value) {
		Expression attr = getAttribute(attrPath);
		return buildPredicate(operator, attr, value);
	}

	@SuppressWarnings({ "rawtypes" })
	public Predicate buildPredicate(FilterOperator operator, Expression expressionObj, Object value) {
		Expression expression = expressionObj;

		expression = handleConversions(expression, operator);

		if (expression instanceof Predicate && expression.getJavaType() == Boolean.class && operator == FilterOperator.EQ) {
			return handleBoolean(expression, value);
		}

		return handle(expression, operator, value);

	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private Predicate handleBoolean(Expression expression, Object value) {
		if (value.equals(Boolean.TRUE)) {
			return (Predicate) expression;
		}
		else {
			return cb.not(expression);
		}
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private Predicate handle(Expression expression, FilterOperator operator, Object value) {
		if (operator == FilterOperator.EQ || operator == FilterOperator.NEQ) {
			return handleEquals(expression, operator, value);
		}
		else if (operator == FilterOperator.LIKE) {
			return ilike(expression, value.toString());
		}
		else if (operator == FilterOperator.GT) {
			return cb.greaterThan(expression, (Comparable) value);
		}
		else if (operator == FilterOperator.LT) {
			return cb.lessThan(expression, (Comparable) value);
		}
		else if (operator == FilterOperator.GE) {
			return cb.greaterThanOrEqualTo(expression, (Comparable) value);
		}
		else if (operator == FilterOperator.LE) {
			return cb.lessThanOrEqualTo(expression, (Comparable) value);
		}
		else {
			throw new IllegalStateException("unexpected operator " + operator);
		}

	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private Predicate handleEquals(Expression expression, FilterOperator operator, Object value) {
		if (value instanceof List) {
			Predicate p = expression.in(((List) value).toArray());
			return negateIfNeeded(p, operator);
		}
		else if (Collection.class.isAssignableFrom(expression.getJavaType())) {
			Predicate p = cb.literal(value).in(expression);
			return negateIfNeeded(p, operator);
		}
		else if (expression instanceof MapJoin) {
			Predicate p = cb.literal(value).in(((MapJoin) expression).value());
			return negateIfNeeded(p, operator);
		}
		else if (value == null) {
			return negateIfNeeded(cb.isNull(expression), operator);
		}
		return negateIfNeeded(cb.equal(expression, value), operator);
	}

	private Expression handleConversions(Expression expression, FilterOperator operator) {
		// convert to String for LIKE operators
		if (expression.getJavaType() != String.class && (operator == FilterOperator.LIKE)) {
			return expression.as(String.class);
		}
		else {
			return expression;
		}
	}

	@Override
	public Predicate and(List predicates) {
		return cb.and(predicates.toArray(new Predicate[predicates.size()]));
	}

	@Override
	public Predicate not(Predicate predicate) {
		return cb.not(predicate);
	}

	@Override
	public Predicate or(List predicates) {
		return cb.or(predicates.toArray(new Predicate[predicates.size()]));
	}

	@Override
	public Expression joinMapValue(Expression currentCriteriaPath, MetaAttribute pathElement, Object key) {
		MapJoin mapJoin = ((From) currentCriteriaPath).joinMap(pathElement.getName(),
				JoinType.LEFT);
		Predicate mapJoinCondition = cb.equal(mapJoin.key(), key);
		Predicate nullCondition = cb.isNull(mapJoin.key());
		addPredicate(cb.or(mapJoinCondition, nullCondition));
		return mapJoin;
	}

	@Override
	public Expression joinMapValues(Expression currentCriteriaPath, MetaAttribute mapPathElement) {
		MapJoin mapJoin = ((From) currentCriteriaPath).joinMap(mapPathElement.getName(),
				JoinType.LEFT);
		return mapJoin.value();
	}

	@Override
	public Expression joinMapKey(Expression currentCriteriaPath, MetaAttribute mapPathElement) {
		MapJoin mapJoin = ((From) currentCriteriaPath).joinMap(mapPathElement.getName(),
				JoinType.LEFT);
		return mapJoin.key();
	}

	@Override
	public Class getJavaElementType(Expression currentCriteriaPath) {
		return currentCriteriaPath.getJavaType();
	}

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public Expression getAttribute(Expression currentCriteriaPath, MetaAttribute pathElement) {
		if (pathElement instanceof MetaComputedAttribute) {
			MetaComputedAttribute projAttr = (MetaComputedAttribute) pathElement;
			JpaCriteriaExpressionFactory expressionFactory = (JpaCriteriaExpressionFactory) queryImpl.getComputedAttrs()
					.get(projAttr);

			From from = (From) currentCriteriaPath;
			return expressionFactory.getExpression(from, getCriteriaQuery());
		}
		else {
			return ((Path) currentCriteriaPath).get(pathElement.getName());
		}
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Expression joinSubType(Expression currentCriteriaPath, Class entityType) {
		return cb.treat((Path) currentCriteriaPath, (Class) entityType);
	}

	@SuppressWarnings("unchecked")
	@Override
	public From doJoin(MetaAttribute targetAttr, JoinType joinType, From parent) {
		if (targetAttr instanceof MetaComputedAttribute) {
			MetaComputedAttribute projAttr = (MetaComputedAttribute) targetAttr;
			@SuppressWarnings("rawtypes")
			JpaCriteriaExpressionFactory expressionFactory = (JpaCriteriaExpressionFactory) queryImpl.getComputedAttrs()
					.get(projAttr);

			return (From) expressionFactory.getExpression(parent, getCriteriaQuery());
		}
		else {
			return parent.join(targetAttr.getName(), joinType);
		}
	}
}