blob: 5a5d311bc9e32ae5e42a22ab3c7a0559e1316cb9 [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 javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.metamodel.Bindable;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
import javax.persistence.metamodel.Type.PersistenceType;
import org.apache.openjpa.kernel.exps.ExpressionFactory;
import org.apache.openjpa.kernel.exps.Value;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.persistence.meta.Members;
import org.apache.openjpa.persistence.meta.Members.Member;
/**
* Path is an expression representing a persistent attribute traversed from a parent path.
* The type of the path is the type of the persistent attribute.
* If the persistent attribute is bindable, then further path can be traversed from this path.
*
* @author Pinaki Poddar
* @author Fay Wang
*
* @param <Z> the type of the parent path
* @param <X> the type of this path
*/
class PathImpl<Z,X> extends ExpressionImpl<X> implements Path<X> {
protected final PathImpl<?,Z> _parent;
protected final Members.Member<? super Z,?> _member;
private boolean isEmbedded = false;
private PathImpl<?,?> _correlatedPath;
/**
* Protected constructor use by root path which neither represent a member nor has a parent.
*/
protected PathImpl(Class<X> cls) {
super(cls);
_parent = null;
_member = null;
}
/**
* Create a path from the given parent representing the given member.
*
* @param parent the path from which this path needs to be constructed. Must not be null.
* @param member the persistent property that represents this path.
* @param cls denotes the type expressed by this path.
*/
public PathImpl(PathImpl<?,Z> parent, Members.Member<? super Z, ?> member, Class<X> cls) {
super(cls);
_parent = parent;
if (_parent.isEmbedded) {
FieldMetaData fmd = getEmbeddedFieldMetaData(member.fmd);
_member = new Members.SingularAttributeImpl(member.owner, fmd);
} else {
_member = member;
}
isEmbedded = _member.fmd.isElementCollection() ? _member.fmd.getElement().isEmbedded() :
_member.fmd.isEmbedded();
}
/**
* Gets the bindable object that corresponds to the path expression.
*
* @throws IllegalArgumentException if this path is not bindable
*/
public Bindable<X> getModel() {
if (_member instanceof Bindable<?> == false) {
throw new IllegalArgumentException(this + " represents a basic path and not a bindable");
}
return (Bindable<X>)_member;
}
/**
* Gets the parent of this path or null if this path is the root.
*/
public final Path<Z> getParentPath() {
return _parent;
}
/**
* Gets the path that originates this traversal. Can be itself if this path is the root.
*/
public PathImpl<?,?> getInnermostParentPath() {
return (_parent == null) ? this : _parent.getInnermostParentPath();
}
/**
* Gets the field that may have been embedded inside the given field.
* For example, a given primary key field which is using an embedded class as a complex primary key.
* @param fmd a given field
* @return the embedded field or the given field itself
*/
protected FieldMetaData getEmbeddedFieldMetaData(FieldMetaData fmd) {
Members.Member<?,?> member = getInnermostMember(_parent,_member);
ClassMetaData embeddedMeta = member.fmd.isElementCollection()
? member.fmd.getElement().getEmbeddedMetaData()
: member.fmd.getEmbeddedMetaData();
return (embeddedMeta != null) ? embeddedMeta.getField(fmd.getName()) : fmd;
}
protected Members.Member<?,?> getInnermostMember(PathImpl<?,?> parent, Members.Member<?,?> member) {
return member != null ? member : getInnermostMember(parent._parent, parent._member);
}
/**
* Makes this path correlated to the given path.
*/
public void setCorrelatedPath(PathImpl<?,?> correlatedPath) {
_correlatedPath = correlatedPath;
}
/**
* Gets the path correlated to this path, if any.
*/
public PathImpl<?,?> getCorrelatedPath() {
return _correlatedPath;
}
/**
* Affirms if this path is correlated to another path.
*/
public boolean isCorrelated() {
return _correlatedPath != null;
}
/**
* Convert this path to a kernel path.
*/
@Override
public Value toValue(ExpressionFactory factory, CriteriaQueryImpl<?> q) {
if (q.isRegistered(this))
return q.getRegisteredValue(this);
org.apache.openjpa.kernel.exps.Path path = null;
SubqueryImpl<?> subquery = q.getDelegator();
boolean allowNull = _parent == null ? false : _parent instanceof Join
&& ((Join<?,?>)_parent).getJoinType() != JoinType.INNER;
PathImpl<?,?> corrJoin = getCorrelatedJoin(this);
PathImpl<?,?> corrRoot = getCorrelatedRoot(subquery);
if (_parent != null && q.isRegistered(_parent)) {
path = factory.newPath(q.getRegisteredVariable(_parent));
path.setSchemaAlias(q.getAlias(_parent));
path.get(_member.fmd, allowNull);
} else if (_parent != null && _parent._correlatedPath != null && q.isRegistered(_parent._correlatedPath)){
path = factory.newPath(q.getRegisteredVariable(_parent._correlatedPath));
path.setSchemaAlias(q.getAlias(_parent._correlatedPath));
path.get(_member.fmd, allowNull);
} else if (corrJoin != null || corrRoot != null) {
org.apache.openjpa.kernel.exps.Subquery subQ = subquery.getSubQ();
path = factory.newPath(subQ);
path.setMetaData(subQ.getMetaData());
path.setSchemaAlias(q.getAlias(_parent));
traversePath(_parent, path, _member.fmd);
} else if (_parent != null) {
Value val = _parent.toValue(factory, q);
if (val instanceof org.apache.openjpa.kernel.exps.Path) {
path = (org.apache.openjpa.kernel.exps.Path)val;
path.get(_member.fmd, allowNull);
} else {
val.setAlias(q.getAlias(this));
return val;
}
} else if (_parent == null) {
path = factory.newPath();
path.setMetaData(q.getMetamodel().getRepository().getCachedMetaData(getJavaType()));
}
if (_member != null && !_member.isCollection()) {
path.setImplicitType(getJavaType());
}
path.setAlias(q.getAlias(this));
return path;
}
public PathImpl<?,?> getCorrelatedRoot(SubqueryImpl<?> subquery) {
if (subquery == null)
return null;
PathImpl<?,?> root = getInnermostParentPath();
if (subquery.getRoots() != null && subquery.getRoots().contains(this))
return root;
return null;
}
public PathImpl<?,?> getCorrelatedJoin(PathImpl<?,?> path) {
if (path._correlatedPath != null)
return path._correlatedPath;
if (path._parent == null)
return null;
return getCorrelatedJoin(path._parent);
}
/**
* Affirms if this receiver occurs in the roots of the given subquery.
*/
public boolean inSubquery(SubqueryImpl<?> subquery) {
return subquery != null && (subquery.getRoots() == null ? false : subquery.getRoots().contains(this));
}
protected void traversePath(PathImpl<?,?> parent, org.apache.openjpa.kernel.exps.Path path, FieldMetaData fmd) {
boolean allowNull = parent == null ? false : parent instanceof Join
&& ((Join<?,?>)parent).getJoinType() != JoinType.INNER;
FieldMetaData fmd1 = parent._member == null ? null : parent._member.fmd;
PathImpl<?,?> parent1 = parent._parent;
if (parent1 == null || parent1.getCorrelatedPath() != null) {
if (fmd != null)
path.get(fmd, allowNull);
return;
}
traversePath(parent1, path, fmd1);
if (fmd != null)
path.get(fmd, allowNull);
}
/**
* Gets a new path that represents the given single-valued attribute from this path.
*/
public <Y> Path<Y> get(SingularAttribute<? super X, Y> attr) {
if (getType() != attr.getDeclaringType()) {
attr = (SingularAttribute)((ManagedType)getType()).getAttribute(attr.getName());
}
return new PathImpl<X,Y>(this, (Members.SingularAttributeImpl<? super X, Y>)attr, attr.getJavaType());
}
/**
* Gets a new path that represents the given multi-valued attribute from this path.
*/
public <E, C extends java.util.Collection<E>> Expression<C> get(PluralAttribute<X, C, E> coll) {
if (getType() != coll.getDeclaringType()) {
coll = (PluralAttribute)((ManagedType)getType()).getAttribute(coll.getName());
}
return new PathImpl<X,C>(this, (Members.PluralAttributeImpl<? super X, C, E>)coll, coll.getJavaType());
}
/**
* Gets a new path that represents the given map-valued attribute from this path.
*/
public <K, V, M extends java.util.Map<K, V>> Expression<M> get(MapAttribute<X, K, V> map) {
if (getType() != map.getDeclaringType()) {
map = (MapAttribute)((ManagedType)getType()).getAttribute(map.getName());
}
return new PathImpl<X,M>(this, (Members.MapAttributeImpl<? super X,K,V>)map, (Class<M>)map.getJavaType());
}
/**
* Gets a new path that represents the attribute of the given name from this path.
*
* @exception IllegalArgumentException if this path represents a basic attribute that is can not be traversed
* further.
*/
public <Y> Path<Y> get(String attName) {
Type<?> type = this.getType();
if (type.getPersistenceType() == PersistenceType.BASIC) {
throw new IllegalArgumentException(this + " is a basic path and can not be navigated to " + attName);
}
Members.Member<? super X, Y> next = (Members.Member<? super X, Y>)
((ManagedType<? super X>)type).getAttribute(attName);
return new PathImpl<X,Y>(this, next, next.getJavaType());
}
public Type<?> getType() {
return _member.getType();
}
@SuppressWarnings("unchecked")
public Member<? extends Z, X> getMember() {
return (Member<? extends Z, X>) _member;
}
/**
* Get the type() expression corresponding to this path.
*/
public Expression<Class<? extends X>> type() {
return new Expressions.Type<Class<? extends X>>(this);
}
public StringBuilder asValue(AliasContext q) {
StringBuilder buffer = new StringBuilder();
if (_parent != null) {
Value var = q.getRegisteredVariable(_parent);
buffer.append(var != null ? var.getName() : _parent.asValue(q)).append(".");
}
if (_member != null) {
buffer.append(_member.fmd.getName());
}
return buffer;
}
public StringBuilder asVariable(AliasContext q) {
Value var = q.getRegisteredVariable(this);
return asValue(q).append(" ").append(var == null ? "?" : var.getName());
}
}