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

io.crnk.jpa.internal.query.AbstractQueryExecutorImpl Maven / Gradle / Ivy

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

import io.crnk.core.engine.internal.utils.ClassUtils;
import io.crnk.jpa.query.JpaQueryExecutor;
import io.crnk.meta.model.MetaAttributePath;
import io.crnk.meta.model.MetaDataObject;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.*;

public abstract class AbstractQueryExecutorImpl implements JpaQueryExecutor {

	private static final String ENTITY_GRAPH_BUILDER_IMPL = "io.crnk.jpa.internal.query.EntityGraphBuilderImpl";

	protected int offset = 0;

	protected int limit = -1;

	protected boolean cached = false;

	protected EntityManager em;

	protected int numAutoSelections;

	protected Set fetchPaths = new HashSet<>();

	protected MetaDataObject meta;

	protected Map selectionBindings;

	public AbstractQueryExecutorImpl(EntityManager em, MetaDataObject meta, int numAutoSelections,
									 Map selectionBindings) {
		this.em = em;
		this.meta = meta;
		this.numAutoSelections = numAutoSelections;
		this.selectionBindings = selectionBindings;
	}

	protected static List enforceDistinct(List list) {
		HashSet distinctSet = new HashSet<>();
		ArrayList distinctResult = new ArrayList<>();
		for (Object obj : list) {
			Object[] values = (Object[]) obj;
			TupleElement tuple = new TupleElement(values);
			if (!distinctSet.contains(tuple)) {
				distinctSet.add(tuple);
				distinctResult.add(values);
			}
		}

		return distinctResult;
	}

	@Override
	public JpaQueryExecutor fetch(List attrPath) {
		// include path an all prefix paths
		MetaAttributePath path = meta.resolvePath(attrPath);
		for (int i = 1; i <= path.length(); i++) {
			fetchPaths.add(path.subPath(0, i));
		}
		return this;
	}

	@Override
	public JpaQueryExecutor setCached(boolean cached) {
		this.cached = cached;
		return this;
	}

	@Override
	public JpaQueryExecutor setOffset(int offset) {
		this.offset = offset;
		return this;
	}

	@Override
	public JpaQueryExecutor setLimit(int limit) {
		this.limit = limit;
		return this;
	}

	@Override
	public int getLimit() {
		return limit;
	}

	@Override
	public JpaQueryExecutor setWindow(int offset, int limit) {
		this.offset = offset;
		this.limit = limit;
		return this;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List getResultList() {
		List list = executeQuery();
		// due to sorting & distinct we have a multiselect even
		// if we are only interested in the entites.
		List resultList;
		if (isCompoundSelection()) {
			List entityList = new ArrayList<>();
			for (Object obj : list) {
				Object[] values = (Object[]) obj;
				entityList.add((T) values[0]);
			}
			resultList = entityList;
		} else {
			resultList = (List) list;
		}
		return resultList;
	}

	protected abstract boolean isCompoundSelection();

	@Override
	public T getUniqueResult(boolean nullable) {
		List list = getResultList();
		if (list.size() > 1) {
			throw new IllegalStateException("query does not return unique value, " + list.size() + " results returned");
		}
		if (!list.isEmpty()) {
			return list.get(0);
		} else if (nullable) {
			return null;
		} else {
			throw new IllegalStateException("no result found");
		}
	}

	protected List truncateTuples(List list, int numToRemove) {
		ArrayList truncatedList = new ArrayList<>();
		for (Object obj : list) {
			Object[] tuple = (Object[]) obj;
			Object[] truncatedTuple = new Object[tuple.length - numToRemove];
			System.arraycopy(tuple, 0, truncatedTuple, 0, truncatedTuple.length);
			truncatedList.add(truncatedTuple);
		}
		return truncatedList;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Class getEntityClass() {
		return (Class) meta.getImplementationClass();
	}

	protected void applyFetchPaths(Query criteriaQuery) {
		if (!fetchPaths.isEmpty()) {
			EntityGraphBuilder builder;

			// avoid compile-time dependency to support old JPA implementation as long
			// as no fetch paths are used
			ClassLoader classLoader = getClass().getClassLoader();
			Class builderClass = ClassUtils.loadClass(classLoader, ENTITY_GRAPH_BUILDER_IMPL);
			builder = (EntityGraphBuilder) ClassUtils.newInstance(builderClass);

			Class entityClass = getEntityClass();
			builder.build(em, criteriaQuery, entityClass, fetchPaths);
		}
	}

	public abstract Query getTypedQuery();

	protected Query setupQuery(Query typedQuery) {
		// apply graph control
		if (!fetchPaths.isEmpty()) {
			applyFetchPaths(typedQuery);
		}

		// control Hibernate query caching
		if (cached) {
			typedQuery.setHint("org.hibernate.cacheable", Boolean.TRUE);
		}

		if (limit > 0) {
			typedQuery.setMaxResults(limit);
		}
		typedQuery.setFirstResult(offset);
		return typedQuery;
	}

	@SuppressWarnings("rawtypes")
	public List executeQuery() {
		Query typedQuery = getTypedQuery();

		setupQuery(typedQuery);

		// query execution
		List resultList = typedQuery.getResultList();

		// post processing (distinct and tuples => views)
		if (isCompoundSelection() && isDistinct() && hasManyRootsFetchesOrJoins()) {
			resultList = enforceDistinct(resultList);
		}

		if (numAutoSelections > 0) {
			resultList = truncateTuples(resultList, numAutoSelections);
		}

		return resultList;
	}

	protected abstract boolean hasManyRootsFetchesOrJoins();

	protected abstract boolean isDistinct();

	static class TupleElement {

		private Object[] data;

		private int hashCode;

		TupleElement(Object[] data) {
			this.data = data;
			this.hashCode = Arrays.hashCode(data);
		}

		@Override
		public int hashCode() {
			return hashCode;
		}

		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof TupleElement)) {
				return false;
			}
			TupleElement tuple = (TupleElement) obj;
			return tuple.hashCode == hashCode && Arrays.equals(data, tuple.data);
		}
	}

}