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

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

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

import io.crnk.core.engine.internal.utils.PreconditionUtil;
import io.crnk.core.queryspec.Direction;
import io.crnk.core.queryspec.FilterOperator;
import io.crnk.jpa.internal.query.JoinRegistry;
import io.crnk.jpa.internal.query.MetaComputedAttribute;
import io.crnk.jpa.internal.query.QueryUtil;
import io.crnk.jpa.internal.query.backend.JpaQueryBackend;
import io.crnk.jpa.meta.MetaEntity;
import io.crnk.jpa.query.criteria.JpaCriteriaExpressionFactory;
import io.crnk.meta.model.MetaAttribute;
import io.crnk.meta.model.MetaAttributePath;
import io.crnk.meta.model.MetaDataObject;
import io.crnk.meta.model.MetaKey;

import javax.persistence.EntityManager;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

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

	protected CriteriaBuilder cb;
	private CriteriaQuery criteriaQuery;
	private JoinRegistry, Expression> joinHelper;
	private From root;

	private Root parentFrom;

	private JpaCriteriaQueryImpl queryImpl;

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

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

		if (parentMeta != null) {
			parentFrom = criteriaQuery.from(parentMeta.getImplementationClass());
			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 = (MetaEntity) parentAttr.getParent();
		MetaKey primaryKey = parentEntity.getPrimaryKey();
		List elements = primaryKey.getElements();
		PreconditionUtil.assertFalse("composite primary keys not supported yet", elements.size() != 1);
		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);
		}
	}

	@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);

		return handle(expression, operator, value);

	}

	@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 {
			PreconditionUtil.verify(operator == FilterOperator.LE, "unexpected operator %s", operator);
			return cb.lessThanOrEqualTo(expression, (Comparable) value);
		}
	}

	@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 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, 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);
		}
	}
}