blob: 778ca72938652043d55449a41b79dfe334fdff14 [file] [log] [blame]
/*
* 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.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Fetch;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import javax.persistence.criteria.Subquery;
import javax.persistence.metamodel.EntityType;
import org.apache.openjpa.kernel.exps.Context;
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.lib.util.Localizer;
import org.apache.openjpa.lib.util.OrderedMap;
import org.apache.openjpa.persistence.meta.MetamodelImpl;
import org.apache.openjpa.persistence.meta.Types;
/**
* Criteria query implementation.
*
* Collects clauses of criteria query (e.g. select projections, from/join,
* where conditions, order by).
* Eventually translates these clauses to a similar form of Expression tree
* that can be interpreted and executed against a data store by OpenJPA kernel.
*
* @author Pinaki Poddar
* @author Fay Wang
*
* @since 2.0.0
*/
class CriteriaQueryImpl<T> implements OpenJPACriteriaQuery<T>, AliasContext {
private static final Localizer _loc = Localizer.forPackage(CriteriaQueryImpl.class);
private final MetamodelImpl _model;
private Set<Root<?>> _roots;
private PredicateImpl _where;
private List<Order> _orders;
private OrderedMap<Object, Class<?>> _params = new OrderedMap<>();
private Selection<? extends T> _selection;
private List<Selection<?>> _selections;
private List<Expression<?>> _groups;
private PredicateImpl _having;
private List<Subquery<?>> _subqueries;
private boolean _distinct;
private final SubqueryImpl<?> _delegator;
private final Class<T> _resultClass;
private boolean _compiled;
// AliasContext
private int aliasCount = 0;
private static String ALIAS_BASE = "autoAlias";
private Map<Selection<?>,Value> _variables = new HashMap<>();
private Map<Selection<?>,Value> _values = new HashMap<>();
private Map<Selection<?>,String> _aliases = null;
private Map<Selection<?>,Value> _rootVariables = new HashMap<>();
// SubqueryContext
private ThreadLocal<Stack<Context>> _contexts = new ThreadLocal<Stack<Context>>(){
@Override
protected Stack<Context> initialValue() {
return new Stack<>();
}
};
public CriteriaQueryImpl(MetamodelImpl model, Class<T> resultClass) {
this._model = model;
this._resultClass = resultClass;
this._delegator = null;
_aliases = new HashMap<>();
}
/**
* Used by a subquery to delegate to this receiver.
*
* @param model the metamodel defines the scope of all persistent entity references.
* @param delegator the subquery which will delegate to this receiver.
*/
CriteriaQueryImpl(MetamodelImpl model, SubqueryImpl<T> delegator, OrderedMap<Object, Class<?>> params) {
this._model = model;
this._resultClass = delegator.getJavaType();
_delegator = delegator;
_aliases = getAliases();
_params = params;
}
/**
* Gets the subquery, if any, which is delegating to this receiver.
*/
SubqueryImpl<?> getDelegator() {
return _delegator;
}
/**
* Gets the metamodel which defines the scope of all persistent entity references.
*/
public MetamodelImpl getMetamodel() {
return _model;
}
/**
* Gets the stack of contexts used by this query.
*/
Stack<Context> getContexts() {
return _contexts.get();
}
/**
* Sets whether this query as distinct.
*/
@Override
public CriteriaQuery<T> distinct(boolean distinct) {
_distinct = distinct;
return this;
}
/**
* Gets the list of ordering elements.
*
* @return Empty list if there is no ordering elements.
* The returned list if mutable but mutation has no impact on this query.
*/
@Override
public List<Order> getOrderList() {
return Expressions.returnCopy(_orders);
}
/**
* Return the selection of the query
* @return the item to be returned in the query result
*/
@Override
public Selection<T> getSelection() {
return (Selection<T>)_selection;
}
/**
* Specify the items that are to be returned in the query result.
* Replaces the previously specified selection(s), if any.
*
* The type of the result of the query execution depends on
* the specification of the criteria query object as well as the
* arguments to the multiselect method as follows:
*
* If the type of the criteria query is CriteriaQuery<Tuple>,
* a Tuple object corresponding to the arguments of the
* multiselect method will be instantiated and returned for
* each row that results from the query execution.
*
* If the type of the criteria query is CriteriaQuery<X> for
* some user-defined class X, then the arguments to the
* multiselect method will be passed to the X constructor and
* an instance of type X will be returned for each row.
* The IllegalStateException will be thrown if a constructor
* for the given argument types does not exist.
*
* If the type of the criteria query is CriteriaQuery<X[]> for
* some class X, an instance of type X[] will be returned for
* each row. The elements of the array will correspond to the
* arguments of the multiselect method. The
* IllegalStateException will be thrown if the arguments to the
* multiselect method are not of type X.
*
* If the type of the criteria query is CriteriaQuery<Object>,
* and only a single argument is passed to the multiselect
* method, an instance of type Object will be returned for
* each row.
*
* If the type of the criteria query is CriteriaQuery<Object>,
* and more than one argument is passed to the multiselect
* method, an instance of type Object[] will be instantiated
* and returned for each row. The elements of the array will
* correspond to the arguments to the multiselect method.
*
* @param selections expressions specifying the items that
* are returned in the query result
* @return the modified query
*/
@Override
public CriteriaQuery<T> multiselect(Selection<?>... selections) {
_selections = Arrays.asList(selections); // do not telescope
_selection = new CompoundSelections.MultiSelection(_resultClass, selections);
return this;
}
/**
* Registers the given parameter.
*/
void registerParameter(ParameterExpressionImpl<?> p) {
if (!_params.containsKey(p)) {
p.setIndex(_params.size());
_params.put(p, p.getJavaType());
}
}
@Override
public Set<ParameterExpression<?>> getParameters() {
collectParameters(new CriteriaExpressionVisitor.ParameterVisitor(this));
return (Set) _params.keySet();
}
/**
* Return the selection items of the query as a list
* @return the selection items of the query as a list
*/
public List<Selection<?>> getSelectionList() {
return Expressions.returnCopy(_selections);
}
@Override
public CriteriaQuery<T> groupBy(Expression<?>... grouping) {
if (grouping == null) {
_groups = null;
return this;
}
_groups = new ArrayList<>();
for (Expression<?> e : grouping)
_groups.add(e);
return this;
}
@Override
public CriteriaQuery<T> groupBy(List<Expression<?>> grouping) {
if (grouping == null) {
_groups = null;
return this;
}
_groups = new ArrayList<>();
for (Expression<?> e : grouping) {
_groups.add(e);
}
return this;
}
@Override
public CriteriaQuery<T> having(Expression<Boolean> restriction) {
_having = (PredicateImpl)restriction;
return this;
}
@Override
public CriteriaQuery<T> having(Predicate... restrictions) {
if (restrictions == null) {
_having = null;
return this;
}
_having = new PredicateImpl.And();
for (Predicate p : restrictions)
_having.add(p);
return this;
}
@Override
public CriteriaQuery<T> orderBy(Order... orders) {
if (orders == null) {
_orders = null;
return this;
}
_orders = new ArrayList<>();
for (Order o : orders) {
_orders.add(o);
}
return this;
}
@Override
public CriteriaQuery<T> orderBy(List<Order> orders) {
if (orders == null) {
_orders = null;
return this;
}
_orders = new ArrayList<>();
for (Order o : orders) {
_orders.add(o);
}
return this;
}
/**
* Specify the item that is to be returned in the query result.
* Replaces the previously specified selection(s), if any.
* @param selection selection specifying the item that
* is to be returned in the query result
* @return the modified query
*/
@Override
public CriteriaQuery<T> select(Selection<? extends T> selection) {
_selection = selection;
_selections = new ArrayList<>();
_selections.add(selection);
return this;
}
@Override
public CriteriaQuery<T> where(Expression<Boolean> restriction) {
invalidateCompilation();
if (restriction == null) {
_where = null;
return this;
}
_where = (PredicateImpl)restriction;
return this;
}
@Override
public CriteriaQuery<T> where(Predicate... restrictions) {
invalidateCompilation();
if (restrictions == null) {
_where = null;
return this;
}
_where = new PredicateImpl.And(restrictions);
return this;
}
@Override
public <X> Root<X> from(EntityType<X> entity) {
RootImpl<X> root = new RootImpl<>((Types.Entity<X>)entity);
addRoot(root);
return root;
}
@Override
public <X> Root<X> from(Class<X> cls) {
EntityType<X> entity = _model.entityImpl(cls);
if (entity == null)
throw new IllegalArgumentException(_loc.get("root-non-entity", cls).getMessage());
return from(entity);
}
@Override
public List<Expression<?>> getGroupList() {
return Expressions.returnCopy(_groups);
}
@Override
public PredicateImpl getGroupRestriction() {
return _having;
}
@Override
public PredicateImpl getRestriction() {
return _where;
}
@Override
public Set<Root<?>> getRoots() {
return Expressions.returnCopy(_roots);
}
public Root<?> getRoot() {
assertRoot();
return _roots.iterator().next();
}
Root<?> getRoot(boolean mustExist) {
if (mustExist) {
return getRoot();
}
return _roots == null || _roots.isEmpty() ? null : _roots.iterator().next();
}
void addRoot(RootImpl<?> root) {
if (_roots == null) {
_roots = new LinkedHashSet<>();
}
_roots.add(root);
}
/**
* Affirms if selection of this query is distinct.
*/
@Override
public boolean isDistinct() {
return _distinct;
}
@Override
public <U> Subquery<U> subquery(Class<U> type) {
if (_subqueries == null)
_subqueries = new ArrayList<>();
Subquery<U> subquery = new SubqueryImpl<>(type, this);
_subqueries.add(subquery);
return subquery;
}
/**
* Return map where key is the parameter expression itself and value is the expected type.
* Empty map if no parameter has been declared.
*/
public OrderedMap<Object, Class<?>> getParameterTypes() {
collectParameters(new CriteriaExpressionVisitor.ParameterVisitor(this));
return _params;
}
/**
* Populate a kernel expression tree by translating the components of this
* receiver with the help of the given {@link ExpressionFactory}.
*/
QueryExpressions getQueryExpressions(ExpressionFactory factory) {
Context context = new Context(null, null, null);
_contexts.get().push(context);
try {
return new CriteriaExpressionBuilder().getQueryExpressions(factory, this);
}finally{
_contexts.remove();
}
}
public void assertRoot() {
if (_roots == null || _roots.isEmpty())
throw new IllegalStateException(_loc.get("root-undefined").getMessage());
}
public void assertSelection() {
if (_selection == null && !isDefaultProjection())
throw new IllegalStateException(_loc.get("select-undefined").getMessage());
}
//
// SubqueryContext
//
void setContexts(Stack<Context> contexts) {
_contexts.set(contexts);
}
/**
* Gets either this query itself if this is not a captive query for
* a subquery. Otherwise gets the parent query of the delegating
* subquery.
*/
CriteriaQueryImpl<?> getAncestor() {
if (_delegator == null)
return this;
AbstractQuery<?> parent = _delegator.getParent();
if (parent instanceof CriteriaQueryImpl)
return (CriteriaQueryImpl<?>)parent;
// parent is a SubqueryImpl
return ((SubqueryImpl<?>)parent).getDelegate().getAncestor();
}
public Map<Selection<?>,String> getAliases() {
CriteriaQueryImpl<?> c = getAncestor();
if (c._aliases == null)
c._aliases = new HashMap<>();
return c._aliases;
}
/**
* Gets the current context.
*/
Context ctx() {
Stack<Context> ctxt = _contexts.get();
return ctxt == null || ctxt.isEmpty() ? null : ctxt.peek();
}
//
// AliasContext management
//
/**
* Gets the alias of the given node. Creates an automatic alias, if necessary.
*/
@Override
public String getAlias(Selection<?> selection) {
String alias = selection.getAlias();
if (alias != null) {
_aliases.put(selection, alias);
return alias;
}
alias = ALIAS_BASE + (++aliasCount);
while (_aliases.containsValue(alias))
alias = ALIAS_BASE + (++aliasCount);
((SelectionImpl<?>)selection).setAutoAlias(alias);
_aliases.put(selection, alias);
return _aliases.get(selection);
}
/**
* Register the given variable of given path value against the given node.
* If the given node has no alias then an alias is set to the given node.
* If the variable or the path has non-null alias, then that alias must
* be equal to the alias of the given node. Otherwise, the node alias is set
* on the variable and path.
*/
@Override
public void registerVariable(Selection<?> node, Value var, Value path) {
if (isRegistered(node)) {
return;
//throw new RuntimeException(node + " is already bound");
}
if (!var.isVariable())
throw new RuntimeException(var.getClass() + " is not a variable");
if (var.getPath() != path)
throw new RuntimeException(var + " does not match given " + path + " Variable path is " + var.getPath());
String alias = getAlias(node);
if (!alias.equals(var.getAlias())) {
if (var.getAlias() == null)
var.setAlias(alias);
else
throw new RuntimeException("Variable alias " + var.getAlias() +
" does not match expected selection alias " + alias);
}
if (!alias.equals(path.getAlias())) {
if (path.getAlias() == null)
path.setAlias(alias);
else
throw new RuntimeException("Path alias " + path.getAlias() +
" does not match expected selection alias " + alias);
}
_variables.put(node, var);
_values.put(node, path);
_aliases.put(node, alias);
// add to context
ctx().addSchema(alias, var.getMetaData());
ctx().addVariable(alias, var);
}
@Override
public boolean isRegistered(Selection<?> selection) {
if (_variables.containsKey(selection))
return true;
SubqueryImpl<?> delegator = getDelegator();
return (delegator == null) ? false : getDelegatorParent().isRegistered(selection);
}
@Override
public Value getRegisteredVariable(Selection<?> selection) {
Value var = _variables.get(selection);
if (var != null)
return var;
SubqueryImpl<?> delegator = getDelegator();
return (delegator == null) ? null : getDelegatorParent().getRegisteredVariable(selection);
}
@Override
public Value getRegisteredValue(Selection<?> selection) {
Value val = _values.get(selection);
if (val != null)
return val;
SubqueryImpl<?> delegator = getDelegator();
return (delegator == null) ? null : getDelegatorParent().getRegisteredValue(selection);
}
/**
* Registers a variable for the given root expression.
* A root expression is registered only for cross join.
* @param root
* @param var
*/
void registerRoot(Root<?> root, Value var) {
if (var == null || !var.isVariable())
throw new IllegalArgumentException("Attempt to register non-variable " + var);
_rootVariables.put(root, var);
String alias = var.getName();
ctx().addSchema(alias, var.getMetaData());
ctx().addVariable(alias, var);
}
/**
* Gets the registered variable for the given root.
*/
@Override
public Value getRegisteredRootVariable(Root<?> root) {
Value var = _rootVariables.get(root);
if (var != null)
return var;
SubqueryImpl<?> delegator = getDelegator();
return (delegator == null) ? null : getDelegatorParent().getRegisteredRootVariable(root);
}
CriteriaQueryImpl<?> getDelegatorParent() {
AbstractQuery<?> parent = _delegator.getParent();
if (parent instanceof CriteriaQueryImpl)
return ((CriteriaQueryImpl<?>)parent);
// parent is a SubqueryImpl
return ((SubqueryImpl<?>)parent).getDelegate();
}
@Override
public Class<T> getResultType() {
return _resultClass;
}
@Override
public CriteriaQuery<T> multiselect(List<Selection<?>> list) {
return multiselect(list.toArray(new Selection<?>[list.size()]));
}
boolean isMultiselect() {
return _selection instanceof CompoundSelections.MultiSelection;
}
protected boolean isDefaultProjection() {
if (_selections == null) {
return getRoots().size() == 1
&& (getRoot().getModel().getJavaType() == _resultClass ||
_resultClass == Object.class);
}
if (_selections.size() != 1) {
return false;
}
Selection<?> sel = _selections.get(0);
if (!getRoots().isEmpty() && sel == getRoot()) {
return true;
}
if ((sel instanceof From<?,?>) && ((From<?,?>)sel).isCorrelated()) {
return true;
}
return false;
}
void invalidateCompilation() {
_compiled = false;
_params.clear();
}
/**
* Compiles to verify that at least one root is defined, a selection term is present
* and, most importantly, collects all the parameters so that they can be bound to
* the executable query.
*/
@Override
public OpenJPACriteriaQuery<T> compile() {
if (_compiled)
return this;
assertRoot();
assertSelection();
collectParameters(new CriteriaExpressionVisitor.ParameterVisitor(this));
_compiled = true;
return this;
}
private void collectParameters(CriteriaExpressionVisitor visitor) {
if (_compiled)
return;
if (_where != null) {
_where.acceptVisit(visitor);
}
if (_having != null) {
_having.acceptVisit(visitor);
}
if (_subqueries != null) {
for (Subquery<?> subq : _subqueries) {
((SubqueryImpl<?>)subq).getDelegate().collectParameters(visitor);
}
}
}
/**
* Gets the string representation of the query.
*/
@Override
public String toCQL() {
StringBuilder buffer = new StringBuilder();
render(buffer, _roots, null);
return buffer.toString().trim();
}
void render(StringBuilder buffer, Set<Root<?>> roots, List<Join<?,?>> correlatedJoins) {
buffer.append("SELECT ");
if (isDistinct()) buffer.append(" DISTINCT ");
buffer.append(_selection != null ? ((CriteriaExpression)_selection).asProjection(this) : "*");
buffer.append(" FROM ");
renderRoots(buffer, roots);
renderJoins(buffer, correlatedJoins);
if (_where != null) {
buffer.append(" WHERE ").append(_where.asValue(this));
}
renderList(buffer, " ORDER BY ", getOrderList());
renderList(buffer, " GROUP BY ", getGroupList());
if (_having != null) {
buffer.append(" HAVING ");
buffer.append(_having.asValue(this));
}
}
private void renderList(StringBuilder buffer, String clause, Collection<?> coll) {
if (coll == null || coll.isEmpty())
return;
buffer.append(clause);
for (Iterator<?> i = coll.iterator(); i.hasNext(); ) {
buffer.append(((CriteriaExpression)i.next()).asValue(this));
if (i.hasNext()) buffer.append(", ");
}
}
private void renderJoins(StringBuilder buffer, Collection<Join<?,?>> joins) {
if (joins == null) return;
for (Join j : joins) {
buffer.append(((CriteriaExpression)j).asVariable(this)).append(" ");
renderJoins(buffer, j.getJoins());
renderFetches(buffer, j.getFetches());
}
}
private void renderRoots(StringBuilder buffer, Collection<Root<?>> roots) {
if (roots == null) return;
int i = 0;
for (Root r : roots) {
buffer.append(((ExpressionImpl<?>)r).asVariable(this));
if (++i != roots.size()) buffer.append(", ");
renderJoins(buffer, r.getJoins());
renderFetches(buffer, r.getFetches());
}
}
private void renderFetches(StringBuilder buffer, Set<Fetch> fetches) {
if (fetches == null) return;
for (Fetch j : fetches) {
buffer.append(((ExpressionImpl<?>)j).asValue(this)).append(" ");
}
}
/**
* Returns a JPQL-like string, if this receiver is populated. Otherwise
* returns <code>Object.toString()</code>.
*/
@Override
public String toString() {
try {
return toCQL();
} catch (Throwable t) {
return super.toString();
}
}
@Override
public boolean equals(Object other) {
if(other == null) {
return false;
}
if (toString().equals(other.toString()))
return true;
return false;
}
}