package eu.eudat.queryable.jpa.hibernatequeryablelist; import eu.eudat.queryable.QueryableList; import eu.eudat.queryable.exceptions.NotSingleResultException; import eu.eudat.queryable.jpa.predicates.NestedQuerySinglePredicate; import eu.eudat.queryable.jpa.predicates.OrderByPredicate; import eu.eudat.queryable.jpa.predicates.SelectPredicate; import eu.eudat.queryable.jpa.predicates.SinglePredicate; import eu.eudat.queryable.queryableentity.DataEntity; import eu.eudat.queryable.types.FieldSelectionType; import eu.eudat.queryable.types.SelectionField; import org.springframework.scheduling.annotation.Async; import javax.persistence.EntityManager; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import javax.persistence.criteria.*; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; public class QueryableHibernateList implements QueryableList { private EntityManager manager; private CriteriaQuery query; private Class tClass; private Root root; private Root nestedQueryRoot; private Subquery subquery; private List> singlePredicates = new LinkedList<>(); private List> nestedPredicates = new LinkedList<>(); private boolean distinct = false; private List> orderings = new LinkedList<>(); private List fields = new LinkedList<>(); private Integer length; private Integer offset; private Set hints; private String hint; public QueryableHibernateList(EntityManager manager, Class tClass) { this.manager = manager; this.tClass = tClass; } public QueryableHibernateList setManager(EntityManager manager) { this.manager = manager; return this; } public QueryableList withHint(String hint) { this.hint = hint; return this; } @Override public QueryableList withFields(List fields) { this.fields = fields; return this; } private QueryableList selectFields() { List rootFields = fields.stream().map(field -> this.convertFieldToPath(field)).collect(Collectors.toList()); this.query.select(this.manager.getCriteriaBuilder().tuple(rootFields.toArray(new Selection[rootFields.size()]))); return this; } private Path convertFieldToPath(String field) { if (!field.contains(".")) { Path path = this.root.get(field); path.alias(field); return path; } else { String[] fields = field.split("\\."); Path path = this.root.get(fields[0]); Join join = null; path.alias(fields[0]); for (int i = 1; i < fields.length; i++) { join = join != null ? join.join(fields[i - 1], JoinType.LEFT) : this.root.join(fields[i - 1], JoinType.LEFT) ; path = join.get(fields[i]); path.alias(String.join(".", Arrays.asList(fields).subList(0, i + 1))); } return path; } } public QueryableHibernateList setEntity(Class type) { return this; } public void initiateQueryableList(Class type) { CriteriaBuilder builder = this.manager.getCriteriaBuilder(); this.query = builder.createQuery(type); } @Override public QueryableList skip(Integer offset) { this.offset = offset; return this; } @Override public QueryableList take(Integer length) { this.length = length; return this; } public QueryableList where(SinglePredicate predicate) { this.singlePredicates.add(predicate); return this; } public QueryableList where(NestedQuerySinglePredicate predicate) { this.nestedPredicates.add(predicate); return this; } public List select(SelectPredicate predicate) { return this.toList().stream().map(item -> predicate.applySelection(item)).collect(Collectors.toList()); } public CompletableFuture> selectAsync(SelectPredicate predicate) { return this.toListAsync().thenApplyAsync(items -> items.stream().map(item -> predicate.applySelection(item)).collect(Collectors.toList())); } public QueryableList distinct() { this.distinct = true; return this; } public QueryableList orderBy(OrderByPredicate predicate) { this.orderings.add(predicate); return this; } public Long count() { CriteriaBuilder criteriaBuilder = this.manager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); this.root = criteriaQuery.from(tClass); if (distinct) criteriaQuery.select(criteriaBuilder.countDistinct(this.root.get("id"))); else criteriaQuery.select(criteriaBuilder.count(this.root)); criteriaQuery.where(this.generateWherePredicates(this.singlePredicates, this.root, this.nestedPredicates, this.nestedQueryRoot)); //if (distinct) criteriaQuery.distinct(true); return this.manager.createQuery(criteriaQuery).getSingleResult(); } @Async public CompletableFuture countAsync() { CriteriaBuilder criteriaBuilder = this.manager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Long.class); this.root = criteriaQuery.from(tClass); if (distinct) criteriaQuery.select(criteriaBuilder.countDistinct(this.root.get("id"))); else criteriaQuery.select(criteriaBuilder.count(this.root)); criteriaQuery.where(this.generateWherePredicates(this.singlePredicates, this.root, this.nestedPredicates, this.nestedQueryRoot)); //if (distinct) criteriaQuery.distinct(true); return CompletableFuture.supplyAsync(() -> this.manager.createQuery(criteriaQuery).getSingleResult()); } private Predicate[] generateWherePredicates(List> singlePredicates, Root root, List> nestedPredicates, Root nestedQueryRoot) { List predicates = new LinkedList<>(); predicates.addAll(Arrays.asList(this.generateSingleWherePredicates(singlePredicates, root))); predicates.addAll(Arrays.asList(this.generateNestedWherePredicates(nestedPredicates, root, nestedQueryRoot))); return predicates.toArray(new Predicate[predicates.size()]); } private Predicate[] generateSingleWherePredicates(List> singlePredicates, Root root) { List predicates = new LinkedList<>(); for (SinglePredicate singlePredicate : singlePredicates) { predicates.add(singlePredicate.applyPredicate(this.manager.getCriteriaBuilder(), root)); } return predicates.toArray(new Predicate[predicates.size()]); } private Predicate[] generateNestedWherePredicates(List> nestedPredicates, Root root, Root nestedQueryRoot) { List predicates = new LinkedList<>(); for (NestedQuerySinglePredicate singlePredicate : nestedPredicates) { predicates.add(singlePredicate.applyPredicate(this.manager.getCriteriaBuilder(), root, nestedQueryRoot)); } return predicates.toArray(new Predicate[predicates.size()]); } private Order[] generateOrderPredicates(List> orderByPredicates, Root root) { List predicates = new LinkedList<>(); for (OrderByPredicate orderPredicate : orderByPredicates) { predicates.add(orderPredicate.applyPredicate(this.manager.getCriteriaBuilder(), root)); } return predicates.toArray(new Order[predicates.size()]); } public List toList() { CriteriaBuilder builder = this.manager.getCriteriaBuilder(); if (!this.fields.isEmpty()) this.query = builder.createTupleQuery(); else this.query = builder.createQuery(this.tClass); this.root = this.query.from(this.tClass); this.query.where(this.generateWherePredicates(this.singlePredicates, this.root, this.nestedPredicates, this.nestedQueryRoot)); if (!this.orderings.isEmpty()) this.query.orderBy(this.generateOrderPredicates(this.orderings, this.root)); if (!this.fields.isEmpty()) this.selectFields(); if (distinct) this.query.distinct(true); if (!this.fields.isEmpty()) return this.toListWithFields(); else return this.toListWithOutFields(); } private List toListWithFields() { List results = this.manager.createQuery(query).getResultList(); Map> groupedResults = results.stream() .collect(Collectors.groupingBy(x -> x.get("id"))); return results.stream().map(x -> { try { return (T) this.tClass.newInstance().buildFromTuple(groupedResults.get(x.get("id")), ""); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return null; }).collect(Collectors.toList()); } private List toListWithOutFields() { TypedQuery typedQuery = this.manager.createQuery(this.query); if (this.offset != null) typedQuery.setFirstResult(this.offset); if (this.length != null) typedQuery.setMaxResults(this.length); if (this.hint != null) { List ids = typedQuery.getResultList().stream().map(item -> item.getKeys()).collect(Collectors.toList()); if (ids != null && !ids.isEmpty()) typedQuery = queryWithHint(ids); } return typedQuery.getResultList(); } @Async public CompletableFuture> toListAsync() { CriteriaBuilder builder = this.manager.getCriteriaBuilder(); if (!this.fields.isEmpty()) this.query = builder.createTupleQuery(); else this.query = builder.createQuery(this.tClass); this.root = this.query.from(this.tClass); this.query.where(this.generateWherePredicates(this.singlePredicates, this.root, this.nestedPredicates, this.nestedQueryRoot)); if (!this.orderings.isEmpty()) this.query.orderBy(this.generateOrderPredicates(this.orderings, this.root)); if (!this.fields.isEmpty()) this.selectFields(); if (distinct) this.query.distinct(true); if (!this.fields.isEmpty()) return this.toListAsyncWithFields(); else return this.toListAsyncWithOutFields(); } private CompletableFuture> toListAsyncWithFields() { List results = this.manager.createQuery(query).getResultList(); Map> groupedResults = results.stream() .collect(Collectors.groupingBy(x -> x.get("id"))); return CompletableFuture.supplyAsync(() -> results.stream().map(x -> { try { return (T) this.tClass.newInstance().buildFromTuple(groupedResults.get(x.get("id")), ""); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return null; }).collect(Collectors.toList())); } private CompletableFuture> toListAsyncWithOutFields() { TypedQuery typedQuery = this.manager.createQuery(this.query); if (this.offset != null) typedQuery.setFirstResult(this.offset); if (this.length != null) typedQuery.setMaxResults(this.length); return CompletableFuture.supplyAsync(() -> { if (this.hint != null) { List ids = typedQuery.getResultList().stream().map(item -> item.getKeys()).collect(Collectors.toList()); if (ids != null && !ids.isEmpty()) return queryWithHint(ids).getResultList(); } return typedQuery.getResultList(); }); } public T getSingle() { CriteriaBuilder builder = this.manager.getCriteriaBuilder(); if (!this.fields.isEmpty()) this.query = builder.createTupleQuery(); else this.query = builder.createQuery(this.tClass); this.root = this.query.from(this.tClass); this.query.where(this.generateWherePredicates(this.singlePredicates, this.root, this.nestedPredicates, this.nestedQueryRoot)); if (!this.fields.isEmpty()) this.selectFields(); TypedQuery typedQuery = this.manager.createQuery(this.query); if (this.hint != null) typedQuery.setHint("javax.persistence.fetchgraph", this.manager.getEntityGraph(this.hint)); return typedQuery.getSingleResult(); } @Async public CompletableFuture getSingleAsync() { CriteriaBuilder builder = this.manager.getCriteriaBuilder(); if (!this.fields.isEmpty()) this.query = builder.createTupleQuery(); else this.query = builder.createQuery(this.tClass); this.root = this.query.from(this.tClass); this.query.where(this.generateWherePredicates(this.singlePredicates, this.root, this.nestedPredicates, this.nestedQueryRoot)); if (!this.fields.isEmpty()) this.selectFields(); TypedQuery typedQuery = this.manager.createQuery(this.query); if (this.hint != null) typedQuery.setHint("javax.persistence.fetchgraph", this.manager.getEntityGraph(this.hint)); return CompletableFuture.supplyAsync(() -> typedQuery.getSingleResult()); } public T getSingleOrDefault() { CriteriaBuilder builder = this.manager.getCriteriaBuilder(); if (!this.fields.isEmpty()) this.query = builder.createTupleQuery(); else this.query = builder.createQuery(this.tClass); this.root = this.query.from(this.tClass); this.query.where(this.generateWherePredicates(this.singlePredicates, this.root, this.nestedPredicates, this.nestedQueryRoot)); if (!this.fields.isEmpty()) this.selectFields(); TypedQuery typedQuery = this.manager.createQuery(this.query); if (this.hint != null) typedQuery.setHint("javax.persistence.fetchgraph", this.manager.getEntityGraph(this.hint)); List results = typedQuery.getResultList(); if (results.size() == 0) return null; if (results.size() == 1) return results.get(0); else throw new NotSingleResultException("Query returned more than one items"); } @Async public CompletableFuture getSingleOrDefaultAsync() { CriteriaBuilder builder = this.manager.getCriteriaBuilder(); if (!this.fields.isEmpty()) this.query = builder.createTupleQuery(); else this.query = builder.createQuery(this.tClass); this.root = this.query.from(this.tClass); this.query.where(this.generateWherePredicates(this.singlePredicates, this.root, this.nestedPredicates, this.nestedQueryRoot)); if (!this.fields.isEmpty()) this.selectFields(); TypedQuery typedQuery = this.manager.createQuery(this.query); if (this.hint != null) typedQuery.setHint("javax.persistence.fetchgraph", this.manager.getEntityGraph(this.hint)); return CompletableFuture.supplyAsync(() -> typedQuery.getResultList()).thenApply(list -> { if (list.size() == 0) return null; if (list.size() == 1) return list.get(0); else throw new NotSingleResultException("Query returned more than one items"); }); } private TypedQuery queryWithHint(List ids) { CriteriaBuilder criteriaBuilder = this.manager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(tClass); Root criteriaRoot = criteriaQuery.from(this.tClass); criteriaQuery.where(criteriaRoot.get("id").in(ids)); if (!this.orderings.isEmpty()) criteriaQuery.orderBy(this.generateOrderPredicates(this.orderings, criteriaRoot)); TypedQuery typedQuery = this.manager.createQuery(criteriaQuery); typedQuery.setHint("javax.persistence.fetchgraph", this.manager.getEntityGraph(this.hint)); return typedQuery; } @Override public Subquery subQuery(SinglePredicate predicate, List fields) { Subquery subquery = this.manager.getCriteriaBuilder().createQuery().subquery(this.tClass); this.nestedQueryRoot = subquery.from(this.tClass); subquery.where(predicate.applyPredicate(this.manager.getCriteriaBuilder(), this.nestedQueryRoot)); subquery.select(this.nestedQueryRoot.get(fields.get(0).getField())); return subquery; } @Override public Subquery subQuery(NestedQuerySinglePredicate predicate, List fields) { Subquery subquery = this.manager.getCriteriaBuilder().createQuery().subquery(this.tClass); this.nestedQueryRoot = subquery.from(this.tClass); subquery.where(predicate.applyPredicate(this.manager.getCriteriaBuilder(), this.root, this.nestedQueryRoot)); subquery.select(this.nestedQueryRoot.get(fields.get(0).getField())); return subquery; } @Override public Subquery subQueryCount(SinglePredicate predicate, List fields) { Subquery subquery = this.manager.getCriteriaBuilder().createQuery().subquery(Long.class); this.nestedQueryRoot = subquery.from(this.tClass); subquery.where(predicate.applyPredicate(this.manager.getCriteriaBuilder(), this.nestedQueryRoot)); subquery.select(this.manager.getCriteriaBuilder().count(this.nestedQueryRoot.get(fields.get(0).getField()))); return subquery; } @Override public Subquery subQueryCount(NestedQuerySinglePredicate predicate, List fields) { Subquery subquery = this.manager.getCriteriaBuilder().createQuery().subquery(Long.class); this.nestedQueryRoot = subquery.from(this.tClass); subquery.where(predicate.applyPredicate(this.manager.getCriteriaBuilder(), this.root, this.nestedQueryRoot)); subquery.select(this.manager.getCriteriaBuilder().count(this.nestedQueryRoot.get(fields.get(0).getField()))); return subquery; } @Override public Subquery subQueryMax(SinglePredicate predicate, List fields, Class uClass) { Subquery subquery = this.manager.getCriteriaBuilder().createQuery().subquery(uClass); this.nestedQueryRoot = subquery.from(this.tClass); subquery.where(predicate.applyPredicate(this.manager.getCriteriaBuilder(), this.nestedQueryRoot)); if (fields.get(0).getType() == FieldSelectionType.FIELD) subquery.select(this.manager.getCriteriaBuilder().greatest(this.nestedQueryRoot.get(fields.get(0).getField()))); else if (fields.get(0).getType() == FieldSelectionType.COMPOSITE_FIELD) { subquery.select(this.manager.getCriteriaBuilder().greatest(this.nestedQueryRoot.get(fields.get(0).getField().split(":")[0]).get(fields.get(0).getField().split(":")[1]))); } return subquery; } @Override public Subquery subQueryMax(NestedQuerySinglePredicate predicate, List fields, Class uClass) { //Subquery subquery = this.manager.getCriteriaBuilder().createQuery().subquery(uClass); //this.nestedQueryRoot = subquery.from(this.tClass); subquery.where(predicate.applyPredicate(this.manager.getCriteriaBuilder(), this.root, this.nestedQueryRoot)); if (fields.get(0).getType() == FieldSelectionType.FIELD) subquery.select(this.manager.getCriteriaBuilder().greatest(this.nestedQueryRoot.get(fields.get(0).getField()))); else if (fields.get(0).getType() == FieldSelectionType.COMPOSITE_FIELD) { subquery.select(this.manager.getCriteriaBuilder().greatest(this.nestedQueryRoot.get(fields.get(0).getField().split(":")[0]).get(fields.get(0).getField().split(":")[1]))); } return subquery; } public QueryableList initSubQuery(Class uClass) { this.subquery = this.manager.getCriteriaBuilder().createQuery().subquery(uClass); this.nestedQueryRoot = subquery.from(this.tClass); return this; } }