| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.openjpa.persistence.criteria; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.persistence.Tuple; |
| import javax.persistence.TupleElement; |
| import javax.persistence.criteria.CompoundSelection; |
| import javax.persistence.criteria.Expression; |
| import javax.persistence.criteria.Fetch; |
| import javax.persistence.criteria.Join; |
| import javax.persistence.criteria.JoinType; |
| import javax.persistence.criteria.Order; |
| import javax.persistence.criteria.Root; |
| import javax.persistence.criteria.Selection; |
| import javax.persistence.metamodel.Type.PersistenceType; |
| |
| import org.apache.openjpa.kernel.FillStrategy; |
| import org.apache.openjpa.kernel.QueryOperations; |
| import org.apache.openjpa.kernel.ResultShape; |
| import org.apache.openjpa.kernel.exps.AbstractExpressionBuilder; |
| import org.apache.openjpa.kernel.exps.ExpressionFactory; |
| import org.apache.openjpa.kernel.exps.QueryExpressions; |
| import org.apache.openjpa.kernel.exps.Value; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.persistence.TupleFactory; |
| import org.apache.openjpa.persistence.TupleImpl; |
| import org.apache.openjpa.persistence.meta.AbstractManagedType; |
| import org.apache.openjpa.persistence.meta.Members; |
| import org.apache.openjpa.persistence.meta.MetamodelImpl; |
| |
| /** |
| * Converts expressions of a CriteriaQuery to kernel Expression. |
| * |
| * |
| * @author Pinaki Poddar |
| * @author Fay Wang |
| * |
| * @since 2.0.0 |
| */ |
| class CriteriaExpressionBuilder { |
| |
| public QueryExpressions getQueryExpressions(ExpressionFactory factory, CriteriaQueryImpl<?> q) { |
| QueryExpressions exps = new QueryExpressions(); |
| exps.setContexts(q.getContexts()); |
| |
| evalAccessPaths(exps, factory, q); |
| exps.alias = null; // String |
| evalDistinct(exps, factory, q); |
| evalFetchJoin(exps, factory, q); |
| evalCrossJoinRoots(exps, factory, q); |
| evalFilter(exps, factory, q); |
| evalGrouping(exps, factory, q); |
| evalOrderingAndProjection(exps, factory, q); |
| exps.operation = QueryOperations.OP_SELECT; |
| exps.range = QueryExpressions.EMPTY_VALUES; |
| exps.resultClass = q.getResultType(); |
| exps.shape = evalResultShape(q); |
| exps.parameterTypes = q.getParameterTypes(); |
| exps.isAggregate(); |
| return exps; |
| } |
| |
| protected void evalAccessPaths(QueryExpressions exps, ExpressionFactory factory, CriteriaQueryImpl<?> q) { |
| Set<ClassMetaData> metas = new HashSet<>(); |
| MetamodelImpl metamodel = q.getMetamodel(); |
| for (Root<?> root : q.getRoots()) { |
| metas.add(((AbstractManagedType<?>)root.getModel()).meta); |
| for (Join<?,?> join : root.getJoins()) { |
| Class<?> cls = join.getAttribute().getJavaType(); |
| if (join.getAttribute().isAssociation()) { |
| ClassMetaData meta = metamodel.getRepository().getMetaData(cls, null, true); |
| PersistenceType type = MetamodelImpl.getPersistenceType(meta); |
| if (type == PersistenceType.ENTITY || type == PersistenceType.EMBEDDABLE) |
| metas.add(meta); |
| } |
| } |
| } |
| // TODO -- need to handle subqueries |
| |
| exps.accessPath = metas.toArray(new ClassMetaData[metas.size()]); |
| } |
| |
| protected void evalOrderingAndProjection(QueryExpressions exps, ExpressionFactory factory, CriteriaQueryImpl<?> q) { |
| Map<ExpressionImpl<?>, Value> exp2Vals = evalOrdering(exps, factory, q); |
| evalProjections(exps, factory, q, exp2Vals); |
| } |
| |
| /** |
| * Evaluates the ordering expressions by converting them to kernel values. |
| * Sets the ordering fields of kernel QueryExpressions. |
| * |
| * @param exps kernel QueryExpressions |
| * @param factory for kernel expressions |
| * @param q a criteria query |
| * |
| * @return map of kernel values indexed by criteria query expressions that created it. |
| * These kernel values are required to be held in a map to avoid recomputing for the |
| * same CriteriaQuery Expressions appearing in ordering terms as well as projection |
| * term. |
| * |
| */ |
| protected Map<ExpressionImpl<?>, Value> evalOrdering(QueryExpressions exps, ExpressionFactory factory, |
| CriteriaQueryImpl<?> q) { |
| List<Order> orders = q.getOrderList(); |
| MetamodelImpl model = q.getMetamodel(); |
| int ordercount = (orders == null) ? 0 : orders.size(); |
| Map<ExpressionImpl<?>, Value> exp2Vals = new HashMap<>(); |
| exps.ordering = new Value[ordercount]; |
| exps.orderingClauses = new String[ordercount]; |
| exps.orderingAliases = new String[ordercount]; |
| exps.ascending = new boolean[ordercount]; |
| for (int i = 0; i < ordercount; i++) { |
| OrderImpl order = (OrderImpl)orders.get(i); |
| ExpressionImpl<?> expr = order.getExpression(); |
| Value val = Expressions.toValue(expr, factory, q); |
| exps.ordering[i] = val; |
| String alias = expr.getAlias(); |
| exps.orderingAliases[i] = alias; |
| exps.orderingClauses[i] = ""; |
| val.setAlias(alias); |
| exps.ascending[i] = order.isAscending(); |
| exp2Vals.put(expr, val); |
| } |
| return exp2Vals; |
| } |
| |
| protected void evalGrouping(QueryExpressions exps, ExpressionFactory factory, CriteriaQueryImpl<?> q) { |
| // exps.grouping = null; // Value[] |
| // exps.groupingClauses = null; // String[] |
| List<Expression<?>> groups = q.getGroupList(); |
| MetamodelImpl model = q.getMetamodel(); |
| PredicateImpl having = q.getGroupRestriction(); |
| if (groups == null) |
| return; |
| int groupByCount = groups.size(); |
| exps.grouping = new Value[groupByCount]; |
| for (int i = 0; i < groupByCount; i++) { |
| Expression<?> groupBy = groups.get(i); |
| exps.grouping[i] = Expressions.toValue((ExpressionImpl<?>)groupBy, factory, q);; |
| } |
| |
| exps.having = having == null ? null : having.toKernelExpression(factory, q); |
| } |
| |
| protected void evalDistinct(QueryExpressions exps, ExpressionFactory factory, CriteriaQueryImpl<?> q) { |
| exps.distinct = q.isDistinct() ? QueryExpressions.DISTINCT_TRUE | QueryExpressions.DISTINCT_AUTO |
| : QueryExpressions.DISTINCT_FALSE; |
| } |
| |
| protected void evalCrossJoinRoots(QueryExpressions exps, ExpressionFactory factory, CriteriaQueryImpl<?> q) { |
| Set<Root<?>> roots = q.getRoots(); |
| SubqueryImpl<?> subQuery = q.getDelegator(); |
| if (subQuery == null || subQuery.getCorrelatedJoins().isEmpty()) { |
| q.assertRoot(); |
| if (roots.size() > 1) { // cross join |
| for (Root<?> root : roots) { |
| String alias = q.getAlias(root); |
| Value var = factory.newBoundVariable(alias, AbstractExpressionBuilder.TYPE_OBJECT); |
| var.setMetaData(((AbstractManagedType<?>)root.getModel()).meta); |
| q.registerRoot(root, var); |
| } |
| } |
| } |
| } |
| |
| protected void evalFilter(QueryExpressions exps, ExpressionFactory factory, CriteriaQueryImpl<?> q) { |
| Set<Root<?>> roots = q.getRoots(); |
| MetamodelImpl model = q.getMetamodel(); |
| PredicateImpl where = q.getRestriction(); |
| SubqueryImpl<?> subQuery = q.getDelegator(); |
| org.apache.openjpa.kernel.exps.Expression filter = null; |
| if (subQuery == null || subQuery.getCorrelatedJoins().isEmpty()) |
| q.assertRoot(); |
| |
| for (Root<?> root : roots) { |
| for (Join<?, ?> join : root.getJoins()) { |
| filter = Expressions.and(factory, |
| ((ExpressionImpl<?>)join).toKernelExpression(factory, q), filter); |
| } |
| ((RootImpl<?>)root).addToContext(factory, model, q); |
| } |
| if (subQuery != null) { |
| Set<Join<?,?>> corrJoins = subQuery.getCorrelatedJoins(); |
| for (Join<?,?> corrJoin : corrJoins) { |
| filter = Expressions.and(factory, ((ExpressionImpl<?>)corrJoin) |
| .toKernelExpression(factory, q), filter); |
| } |
| } |
| |
| if (where != null) { |
| filter = Expressions.and(factory, where.toKernelExpression(factory, q), filter); |
| } |
| if (filter == null) { |
| filter = factory.emptyExpression(); |
| } |
| exps.filter = filter; |
| } |
| |
| protected void evalProjections(QueryExpressions exps, ExpressionFactory factory, CriteriaQueryImpl<?> q, |
| Map<ExpressionImpl<?>, Value> exp2Vals) { |
| List<Selection<?>> selections = q.getSelectionList(); |
| MetamodelImpl model = q.getMetamodel(); |
| if (q.isDefaultProjection()) { |
| exps.projections = new Value[0]; |
| return ; |
| } |
| exps.projections = new Value[selections.size()]; |
| List<Value> projections = new ArrayList<>(); |
| List<String> aliases = new ArrayList<>(); |
| List<String> clauses = new ArrayList<>(); |
| getProjections(exps, selections, projections, aliases, clauses, factory, q, model, exp2Vals); |
| exps.projections = projections.toArray(new Value[projections.size()]); |
| exps.projectionAliases = aliases.toArray(new String[aliases.size()]); |
| exps.projectionClauses = clauses.toArray(new String[clauses.size()]); |
| } |
| |
| /** |
| * Scans the projection terms to populate the kernel QueryExpressions projection clauses |
| * and aliases. |
| * |
| * @param exps |
| * @param selections |
| * @param projections list of kernel values for projections |
| * @param aliases list of kernel projection aliases |
| * @param clauses list of kernel projection clauses |
| * @param factory for kernel expressions |
| * @param q a Criteria Query |
| * @param model of domain entities |
| * @param exp2Vals the evaluated kernel values indexed by the Criteria Expressions |
| */ |
| private void getProjections(QueryExpressions exps, List<Selection<?>> selections, |
| List<Value> projections, List<String> aliases, List<String> clauses, |
| ExpressionFactory factory, CriteriaQueryImpl<?> q, MetamodelImpl model, |
| Map<ExpressionImpl<?>, Value> exp2Vals) { |
| |
| if (selections.size() == 0 && q.getDelegator() != null) { // this is subquery |
| Root<?> r = q.getRoot(); |
| selections = new ArrayList<>(1); |
| selections.add(r); |
| } |
| for (Selection<?> s : selections) { |
| if (s.isCompoundSelection()) { |
| getProjections(exps, s.getCompoundSelectionItems(), projections, aliases, |
| clauses, factory, q, model, exp2Vals); |
| } else { |
| Value val = (exp2Vals != null && exp2Vals.containsKey(s) |
| ? exp2Vals.get(s) : ((ExpressionImpl<?>)s).toValue(factory, q)); |
| String alias = s.getAlias(); |
| val.setAlias(alias); |
| projections.add(val); |
| aliases.add(alias); |
| clauses.add(alias); |
| } |
| } |
| } |
| |
| protected void evalFetchJoin(QueryExpressions exps, ExpressionFactory factory, CriteriaQueryImpl<?> q) { |
| List<String> iPaths = new ArrayList<>(); |
| List<String> oPaths = new ArrayList<>(); |
| Set<Root<?>> roots = q.getRoots(); |
| for (Root root : roots) { |
| Set<Fetch> fetches = root.getFetches(); |
| if (fetches == null) |
| continue; |
| for (Fetch<?,?> fetch : fetches) { |
| String fPath = ((Members.Member<?, ?>)fetch.getAttribute()).fmd.getFullName(false); |
| oPaths.add(fPath); |
| if (fetch.getJoinType() == JoinType.INNER) { |
| iPaths.add(fPath); |
| } |
| } |
| } |
| if (!iPaths.isEmpty()) { |
| exps.fetchInnerPaths = iPaths.toArray(new String[iPaths.size()]); |
| } |
| if (!oPaths.isEmpty()) { |
| exps.fetchPaths = oPaths.toArray(new String[oPaths.size()]); |
| } |
| } |
| |
| // =================================================================================== |
| // Result Shape processing |
| // =================================================================================== |
| |
| /** |
| * Gets the shape of a selection item. Creates the shape if necessary. |
| * |
| * @param q the original query |
| * @param parent the parent shape that nests this given selection |
| * @param s the selection term for which a result shape to be computed |
| */ |
| ResultShape<?> getShape(CriteriaQueryImpl<?> q, ResultShape<?> parent, Selection<?> s) { |
| ResultShape<?> result = null; |
| Class<?> type = s.getJavaType(); |
| if (type == null) |
| type = Object.class; |
| if (s.isCompoundSelection()) { |
| CompoundSelection<?> cs = (CompoundSelection<?>)s; |
| result = new ResultShape(s.getJavaType(), CompoundSelections.getFillStrategy(cs)); |
| List<Selection<?>> terms = cs.getCompoundSelectionItems(); |
| for (Selection<?> term : terms) { |
| result.nest(getShape(q, result, term)); |
| } |
| } else { |
| if (parent.getType().isArray() && q.isMultiselect()) { |
| Class<?> componentType = parent.getType().getComponentType(); |
| if (componentType == Tuple.class) { |
| result = new ResultShape(componentType, |
| new FillStrategy.Factory(new TupleFactory(s), TupleImpl.PUT), false); |
| } else { |
| result = new ResultShape(componentType, new FillStrategy.Assign(), true); |
| } |
| } else { |
| result = new ResultShape(type, new FillStrategy.Assign(), true); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Builds the result shape by creating shape for the complete result and how it nests each selection terms. |
| * The shape varies based on whether the terms were selected based on multiselect() or select(). |
| */ |
| private ResultShape<?> evalResultShape(CriteriaQueryImpl<?> q) { |
| List<Selection<?>> selections = q.getSelectionList(); |
| Class<?> resultClass = q.getResultType(); |
| ResultShape<?> result = null; |
| if (q.isMultiselect()) { |
| result = new ResultShape(resultClass, CompoundSelections.getFillStrategy(q.getSelection())); |
| for (Selection<?> term : selections) { |
| result.nest(getShape(q, result, term)); |
| } |
| } else { // not multiselect |
| FillStrategy<?> strategy = new FillStrategy.Assign(); |
| if (Tuple.class.isAssignableFrom(resultClass)) { |
| TupleFactory factory = new TupleFactory(selections.toArray(new TupleElement[selections.size()])); |
| strategy = new FillStrategy.Factory<>(factory, TupleImpl.PUT); |
| } |
| result = new ResultShape(resultClass, strategy); |
| if (q.getSelectionList() == null) { |
| return result; |
| } |
| if (q.getSelectionList().size() == 1) { |
| result = getShape(q, result, q.getSelectionList().get(0)); |
| } else { |
| for (Selection<?> term : q.getSelectionList()) { |
| result.nest(getShape(q, result, term)); |
| } |
| } |
| } |
| |
| return result; |
| } |
| } |