CAY-1959 Chainable API for SelectQuery

* merge method for PrefetchTreeNode for more comprehensive prefetch subtree 
merging
* using the new merge in SelectQuery as well

UPGRADE NOTES:

The most visible side effect of this change is that Property.xyzPrefetch() 
method will now return
a root of the prefetch tree that can merged into the query instead of a leaf 
that can't.

So in an unlikely case someone did 
MyEntity.MY_PROPERTY.disjoint().setSemantics(JOINT), the last part in this chain
is not longer going to work.


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/4d8b2e1a
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/4d8b2e1a
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/4d8b2e1a

Branch: refs/heads/CAY-1946
Commit: 4d8b2e1a6a6db08bd68ffab227381eb981111893
Parents: fb8660e
Author: aadamchik <aadamc...@apache.org>
Authored: Sun Nov 9 21:20:21 2014 +0300
Committer: aadamchik <aadamc...@apache.org>
Committed: Sun Nov 9 21:24:50 2014 +0300

----------------------------------------------------------------------
 .../cayenne/query/PrefetchTreeNodeTest.java     |  169 ++-
 .../java/org/apache/cayenne/exp/Property.java   |  812 ++++++-------
 .../apache/cayenne/query/BaseQueryMetadata.java |  839 ++++++-------
 .../apache/cayenne/query/PrefetchTreeNode.java  | 1106 +++++++++---------
 .../org/apache/cayenne/query/SelectQuery.java   |    7 +-
 ...ataContextDisjointByIdPrefetch_ExtrasIT.java |    1 +
 6 files changed, 1546 insertions(+), 1388 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/4d8b2e1a/cayenne-client/src/test/java/org/apache/cayenne/query/PrefetchTreeNodeTest.java
----------------------------------------------------------------------
diff --git 
a/cayenne-client/src/test/java/org/apache/cayenne/query/PrefetchTreeNodeTest.java
 
b/cayenne-client/src/test/java/org/apache/cayenne/query/PrefetchTreeNodeTest.java
index 90d4169..1dee8a9 100644
--- 
a/cayenne-client/src/test/java/org/apache/cayenne/query/PrefetchTreeNodeTest.java
+++ 
b/cayenne-client/src/test/java/org/apache/cayenne/query/PrefetchTreeNodeTest.java
@@ -18,51 +18,142 @@
  ****************************************************************/
 package org.apache.cayenne.query;
 
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.remote.hessian.service.HessianUtil;
-import org.junit.Test;
-
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.remote.hessian.service.HessianUtil;
+import org.junit.Test;
 
 public class PrefetchTreeNodeTest {
 
-    @Test
-    public void testTreeSerializationWithHessian() throws Exception {
-        PrefetchTreeNode n1 = new PrefetchTreeNode();
-        PrefetchTreeNode n2 = n1.addPath("abc");
-
-        PrefetchTreeNode nc1 = (PrefetchTreeNode) 
HessianUtil.cloneViaClientServerSerialization(n1,
-                new EntityResolver());
-        assertNotNull(nc1);
-
-        PrefetchTreeNode nc2 = nc1.getNode("abc");
-        assertNotNull(nc2);
-        assertNotSame(nc2, n2);
-        assertSame(nc1, nc2.getParent());
-        assertEquals("abc", nc2.getName());
-    }
-
-    @Test
-    public void testSubtreeSerializationWithHessian() throws Exception {
-        PrefetchTreeNode n1 = new PrefetchTreeNode();
-        PrefetchTreeNode n2 = n1.addPath("abc");
-        PrefetchTreeNode n3 = n2.addPath("xyz");
-
-        // test that substree was serialized as independent tree, instead of
-        // sucking
-        PrefetchTreeNode nc2 = (PrefetchTreeNode) 
HessianUtil.cloneViaClientServerSerialization(n2,
-                new EntityResolver());
-        assertNotNull(nc2);
-        assertNull(nc2.getParent());
-
-        PrefetchTreeNode nc3 = nc2.getNode("xyz");
-        assertNotNull(nc3);
-        assertNotSame(nc3, n3);
-        assertSame(nc2, nc3.getParent());
-        assertEquals("xyz", nc3.getName());
-    }
+       @Test
+       public void testTreeSerializationWithHessian() throws Exception {
+               PrefetchTreeNode n1 = new PrefetchTreeNode();
+               PrefetchTreeNode n2 = n1.addPath("abc");
+
+               PrefetchTreeNode nc1 = (PrefetchTreeNode) 
HessianUtil.cloneViaClientServerSerialization(n1,
+                               new EntityResolver());
+               assertNotNull(nc1);
+
+               PrefetchTreeNode nc2 = nc1.getNode("abc");
+               assertNotNull(nc2);
+               assertNotSame(nc2, n2);
+               assertSame(nc1, nc2.getParent());
+               assertEquals("abc", nc2.getName());
+       }
+
+       @Test
+       public void testSubtreeSerializationWithHessian() throws Exception {
+               PrefetchTreeNode n1 = new PrefetchTreeNode();
+               PrefetchTreeNode n2 = n1.addPath("abc");
+               PrefetchTreeNode n3 = n2.addPath("xyz");
+
+               // test that substree was serialized as independent tree, 
instead of
+               // sucking
+               PrefetchTreeNode nc2 = (PrefetchTreeNode) 
HessianUtil.cloneViaClientServerSerialization(n2,
+                               new EntityResolver());
+               assertNotNull(nc2);
+               assertNull(nc2.getParent());
+
+               PrefetchTreeNode nc3 = nc2.getNode("xyz");
+               assertNotNull(nc3);
+               assertNotSame(nc3, n3);
+               assertSame(nc2, nc3.getParent());
+               assertEquals("xyz", nc3.getName());
+       }
+
+       @Test
+       public void testMerge() {
+               PrefetchTreeNode original = new PrefetchTreeNode();
+               original.addPath("a").setPhantom(true);
+               
original.addPath("a.b").setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+               original.addPath("a.b").setPhantom(false);
+               
original.addPath("c").setSemantics(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
+               original.addPath("c").setPhantom(false);
+               
original.addPath("f").setSemantics(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
+               original.addPath("f").setPhantom(false);
+
+               PrefetchTreeNode toMerge = new PrefetchTreeNode();
+               toMerge.addPath("a").setPhantom(false);
+               
toMerge.addPath("a.b").setSemantics(PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
+               
toMerge.addPath("d.e").setSemantics(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
+               toMerge.addPath("d.e").setPhantom(false);
+               
toMerge.addPath("c").setSemantics(PrefetchTreeNode.UNDEFINED_SEMANTICS);
+
+               original.merge(toMerge);
+
+               assertSame(original, original.getRoot());
+               assertEquals(4, original.getChildren().size());
+
+               PrefetchTreeNode mergedA = original.getChild("a");
+               assertEquals(1, mergedA.getChildren().size());
+               assertFalse("Phantom flag wasn't turned off", 
mergedA.isPhantom());
+               assertEquals(PrefetchTreeNode.UNDEFINED_SEMANTICS, 
mergedA.getSemantics());
+
+               PrefetchTreeNode mergedB = mergedA.getChild("b");
+               assertEquals(0, mergedB.getChildren().size());
+               assertFalse(mergedB.isPhantom());
+               assertEquals("Semantics was't merged", 
PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS,
+                               mergedB.getSemantics());
+
+               PrefetchTreeNode mergedC = original.getChild("c");
+               assertEquals(0, mergedC.getChildren().size());
+               assertFalse(mergedC.isPhantom());
+               assertEquals("Semantics was overridden to undefined", 
PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS,
+                               mergedC.getSemantics());
+
+               PrefetchTreeNode mergedD = original.getChild("d");
+               assertEquals(1, mergedD.getChildren().size());
+               assertTrue(mergedD.isPhantom());
+               assertEquals(PrefetchTreeNode.UNDEFINED_SEMANTICS, 
mergedD.getSemantics());
+               assertNotSame("Merged node wasn't cloned", 
toMerge.getChild("d"), mergedD);
+
+               PrefetchTreeNode mergedE = mergedD.getChild("e");
+               assertEquals(0, mergedE.getChildren().size());
+               assertFalse(mergedE.isPhantom());
+               assertEquals(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS, 
mergedE.getSemantics());
+
+               PrefetchTreeNode mergedF = original.getChild("f");
+               assertEquals(0, mergedF.getChildren().size());
+               assertFalse(mergedF.isPhantom());
+               assertEquals(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS, 
mergedF.getSemantics());
+       }
+
+       @Test
+       public void testMerge_NonRoot() {
+               PrefetchTreeNode original = new PrefetchTreeNode();
+               original.addPath("a").setPhantom(true);
+               
original.addPath("a.b").setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+               original.addPath("a.b").setPhantom(false);
+
+               PrefetchTreeNode toMerge = new PrefetchTreeNode(null, "a.b.c");
+               toMerge.setPhantom(false);
+               
toMerge.setSemantics(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
+
+               original.merge(toMerge);
+
+               assertSame(original, original.getRoot());
+               assertEquals(1, original.getChildren().size());
+
+               PrefetchTreeNode mergedA = original.getChild("a");
+               assertEquals(1, mergedA.getChildren().size());
+               assertTrue(mergedA.isPhantom());
+               assertEquals(PrefetchTreeNode.UNDEFINED_SEMANTICS, 
mergedA.getSemantics());
+
+               PrefetchTreeNode mergedB = mergedA.getChild("b");
+               assertEquals(1, mergedB.getChildren().size());
+               assertFalse(mergedB.isPhantom());
+               assertEquals(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS, 
mergedB.getSemantics());
+
+               PrefetchTreeNode mergedC = mergedB.getChild("c");
+               assertEquals(0, mergedC.getChildren().size());
+               assertFalse(mergedC.isPhantom());
+               assertEquals(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS, 
mergedC.getSemantics());
+       }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/4d8b2e1a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
index 999b346..48f1549 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
@@ -48,405 +48,417 @@ import org.apache.cayenne.reflect.PropertyUtils;
  */
 public class Property<E> {
 
-    /**
-     * Name of the property in the object
-     */
-    private final String name;
-
-    /**
-     * Constructs a new property with the given name.
-     */
-    public Property(String name) {
-        this.name = name;
-    }
-
-    /**
-     * @return Name of the property in the object.
-     */
-    public String getName() {
-        return name;
-    }
-
-    @Override
-    public int hashCode() {
-        return getName().hashCode();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        return obj instanceof Property && ((Property<?>) 
obj).getName().equals(getName());
-    }
-
-    /**
-     * @return Constructs a property path by appending the argument to the
-     *         existing property separated by a dot
-     */
-    public Property<Object> dot(String property) {
-        return new Property<Object>(getName() + "." + property);
-    }
-
-    /**
-     * @return Constructs a property path by appending the argument to the
-     *         existing property separated by a dot
-     */
-    public <T> Property<T> dot(Property<T> property) {
-        return new Property<T>(getName() + "." + property.getName());
-    }
-
-    /**
-     * Returns a version of this property that represents an OUTER join. It is
-     * up to caller to ensure that the property corresponds to a relationship,
-     * as "outer" attributes make no sense.
-     */
-    public Property<E> outer() {
-        return isOuter() ? this: new Property<E>(name + "+"); 
-    }
-    
-    private boolean isOuter() {
-        return name.endsWith("+");
-    }
-
-    /**
-     * @return An expression representing null.
-     */
-    public Expression isNull() {
-        return ExpressionFactory.matchExp(getName(), null);
-    }
-
-    /**
-     * @return An expression representing a non-null value.
-     */
-    public Expression isNotNull() {
-        return ExpressionFactory.matchExp(getName(), null).notExp();
-    }
-
-    /**
-     * @return An expression representing equality to TRUE.
-     */
-    public Expression isTrue() {
-        return ExpressionFactory.matchExp(getName(), Boolean.TRUE);
-    }
-
-    /**
-     * @return An expression representing equality to FALSE.
-     */
-    public Expression isFalse() {
-        return ExpressionFactory.matchExp(getName(), Boolean.FALSE);
-    }
-
-    /**
-     * @return An expression representing equality to a value.
-     */
-    public Expression eq(E value) {
-        return ExpressionFactory.matchExp(getName(), value);
-    }
-
-    /**
-     * @return An expression representing equality between two attributes
-     *         (columns).
-     */
-    public Expression eq(Property<?> value) {
-        return ExpressionFactory.matchExp(getName(), new 
ASTObjPath(value.getName()));
-    }
-
-    /**
-     * @return An expression representing inequality to a value.
-     */
-    public Expression ne(E value) {
-        return ExpressionFactory.noMatchExp(getName(), value);
-    }
-
-    /**
-     * @return An expression representing inequality between two attributes
-     *         (columns).
-     */
-    public Expression ne(Property<?> value) {
-        return ExpressionFactory.noMatchExp(getName(), new 
ASTObjPath(value.getName()));
-    }
-
-    /**
-     * @return An expression for a Database "Like" query.
-     */
-    public Expression like(E value) {
-        return ExpressionFactory.likeExp(getName(), value);
-    }
-
-    /**
-     * @return An expression for a case insensitive "Like" query.
-     */
-    public Expression likeInsensitive(E value) {
-        return ExpressionFactory.likeIgnoreCaseExp(getName(), value);
-    }
-
-    /**
-     * @return An expression for a Database "NOT LIKE" query.
-     */
-    public Expression nlike(E value) {
-        return ExpressionFactory.notLikeExp(getName(), value);
-    }
-
-    /**
-     * @return An expression for a case insensitive "NOT LIKE" query.
-     */
-    public Expression nlikeInsensitive(E value) {
-        return ExpressionFactory.notLikeIgnoreCaseExp(getName(), value);
-    }
-
-    /**
-     * @return An expression checking for objects between a lower and upper
-     *         bound inclusive
-     * 
-     * @param lower
-     *            The lower bound.
-     * @param upper
-     *            The upper bound.
-     */
-    public Expression between(E lower, E upper) {
-        return ExpressionFactory.betweenExp(getName(), lower, upper);
-    }
-
-    /**
-     * @return An expression for finding objects with values in the given set.
-     */
-    public Expression in(E firstValue, E... moreValues) {
-
-        int moreValuesLength = moreValues != null ? moreValues.length : 0;
-
-        Object[] values = new Object[moreValuesLength + 1];
-        values[0] = firstValue;
-
-        if (moreValuesLength > 0) {
-            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
-        }
-
-        return ExpressionFactory.inExp(getName(), values);
-    }
-
-    /**
-     * @return An expression for finding objects with values not in the given
-     *         set.
-     */
-    public Expression nin(E firstValue, E... moreValues) {
-
-        int moreValuesLength = moreValues != null ? moreValues.length : 0;
-
-        Object[] values = new Object[moreValuesLength + 1];
-        values[0] = firstValue;
-
-        if (moreValuesLength > 0) {
-            System.arraycopy(moreValues, 0, values, 1, moreValuesLength);
-        }
-
-        return ExpressionFactory.notInExp(getName(), values);
-    }
-
-    /**
-     * @return An expression for finding objects with values in the given set.
-     */
-    public Expression in(Collection<E> values) {
-        return ExpressionFactory.inExp(getName(), values);
-    }
-
-    /**
-     * @return An expression for finding objects with values not in the given
-     *         set.
-     */
-    public Expression nin(Collection<E> values) {
-        return ExpressionFactory.notInExp(getName(), values);
-    }
-
-    /**
-     * @return A greater than Expression.
-     */
-    public Expression gt(E value) {
-        return ExpressionFactory.greaterExp(getName(), value);
-    }
-
-    /**
-     * @return Represents a greater than relationship between two attributes
-     *         (columns).
-     */
-    public Expression gt(Property<?> value) {
-        return ExpressionFactory.greaterExp(getName(), new 
ASTObjPath(value.getName()));
-    }
-
-    /**
-     * @return A greater than or equal to Expression.
-     */
-    public Expression gte(E value) {
-        return ExpressionFactory.greaterOrEqualExp(getName(), value);
-    }
-
-    /**
-     * @return Represents a greater than or equal relationship between two
-     *         attributes (columns).
-     */
-    public Expression gte(Property<?> value) {
-        return ExpressionFactory.greaterOrEqualExp(getName(), new 
ASTObjPath(value.getName()));
-    }
-
-    /**
-     * @return A less than Expression.
-     */
-    public Expression lt(E value) {
-        return ExpressionFactory.lessExp(getName(), value);
-    }
-
-    /**
-     * @return Represents a less than relationship between two attributes
-     *         (columns).
-     */
-    public Expression lt(Property<?> value) {
-        return ExpressionFactory.lessExp(getName(), new 
ASTObjPath(value.getName()));
-    }
-
-    /**
-     * @return A less than or equal to Expression.
-     */
-    public Expression lte(E value) {
-        return ExpressionFactory.lessOrEqualExp(getName(), value);
-    }
-
-    /**
-     * @return Represents a less than or equal relationship between two
-     *         attributes (columns).
-     */
-    public Expression lte(Property<?> value) {
-        return ExpressionFactory.lessOrEqualExp(getName(), new 
ASTObjPath(value.getName()));
-    }
-
-    /**
-     * @return Ascending sort orderings on this property.
-     */
-    public Ordering asc() {
-        return new Ordering(getName(), SortOrder.ASCENDING);
-    }
-
-    /**
-     * @return Ascending sort orderings on this property.
-     */
-    public List<Ordering> ascs() {
-        List<Ordering> result = new ArrayList<Ordering>(1);
-        result.add(asc());
-        return result;
-    }
-
-    /**
-     * @return Ascending case insensitive sort orderings on this property.
-     */
-    public Ordering ascInsensitive() {
-        return new Ordering(getName(), SortOrder.ASCENDING_INSENSITIVE);
-    }
-
-    /**
-     * @return Ascending case insensitive sort orderings on this property.
-     */
-    public List<Ordering> ascInsensitives() {
-        List<Ordering> result = new ArrayList<Ordering>(1);
-        result.add(ascInsensitive());
-        return result;
-    }
-
-    /**
-     * @return Descending sort orderings on this property.
-     */
-    public Ordering desc() {
-        return new Ordering(getName(), SortOrder.DESCENDING);
-    }
-
-    /**
-     * @return Descending sort orderings on this property.
-     */
-    public List<Ordering> descs() {
-        List<Ordering> result = new ArrayList<Ordering>(1);
-        result.add(desc());
-        return result;
-    }
-
-    /**
-     * @return Descending case insensitive sort orderings on this property.
-     */
-    public Ordering descInsensitive() {
-        return new Ordering(getName(), SortOrder.DESCENDING_INSENSITIVE);
-    }
-
-    /**
-     * @return Descending case insensitive sort orderings on this property.
-     */
-    public List<Ordering> descInsensitives() {
-        List<Ordering> result = new ArrayList<Ordering>(1);
-        result.add(descInsensitive());
-        return result;
-    }
-
-    public PrefetchTreeNode joint() {
-        PrefetchTreeNode node = prefetch();
-        node.setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
-        return node;
-    }
-
-    public PrefetchTreeNode disjoint() {
-        PrefetchTreeNode node = prefetch();
-        node.setSemantics(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
-        return node;
-    }
-
-    public PrefetchTreeNode disjointById() {
-        PrefetchTreeNode node = prefetch();
-        node.setSemantics(PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
-        return node;
-    }
-
-    PrefetchTreeNode prefetch() {
-
-        // TODO: not very efficient - we are creating a prefetch that
-        // SelectQuery would throw away and recreate...
-        PrefetchTreeNode root = new PrefetchTreeNode();
-        PrefetchTreeNode node = root.addPath(name);
-        node.setPhantom(false);
-        return node;
-    }
-
-    /**
-     * Extracts property value from an object using JavaBean-compatible
-     * introspection with one addition - a property can be a dot-separated
-     * property name path.
-     */
-    @SuppressWarnings("unchecked")
-    public E getFrom(Object bean) {
-        return (E) PropertyUtils.getProperty(bean, getName());
-    }
-
-    /**
-     * Extracts property value from a collection of objects using
-     * JavaBean-compatible introspection with one addition - a property can be 
a
-     * dot-separated property name path.
-     */
-    public List<E> getFromAll(Collection<?> beans) {
-        List<E> result = new ArrayList<E>(beans.size());
-        for (Object bean : beans) {
-            result.add(getFrom(bean));
-        }
-        return result;
-    }
-
-    /**
-     * Sets a property value in 'obj' using JavaBean-compatible introspection
-     * with one addition - a property can be a dot-separated property name 
path.
-     */
-    public void setIn(Object bean, E value) {
-        PropertyUtils.setProperty(bean, getName(), value);
-    }
-
-    /**
-     * Sets a property value in a collection of objects using
-     * JavaBean-compatible introspection with one addition - a property can be 
a
-     * dot-separated property name path.
-     */
-    public void setInAll(Collection<?> beans, E value) {
-        for (Object bean : beans) {
-            setIn(bean, value);
-        }
-    }
+       /**
+        * Name of the property in the object
+        */
+       private final String name;
+
+       /**
+        * Constructs a new property with the given name.
+        */
+       public Property(String name) {
+               this.name = name;
+       }
+
+       /**
+        * @return Name of the property in the object.
+        */
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public int hashCode() {
+               return getName().hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               return obj instanceof Property && ((Property<?>) 
obj).getName().equals(getName());
+       }
+
+       /**
+        * @return Constructs a property path by appending the argument to the
+        *         existing property separated by a dot
+        */
+       public Property<Object> dot(String property) {
+               return new Property<Object>(getName() + "." + property);
+       }
+
+       /**
+        * @return Constructs a property path by appending the argument to the
+        *         existing property separated by a dot
+        */
+       public <T> Property<T> dot(Property<T> property) {
+               return new Property<T>(getName() + "." + property.getName());
+       }
+
+       /**
+        * Returns a version of this property that represents an OUTER join. It 
is
+        * up to caller to ensure that the property corresponds to a 
relationship,
+        * as "outer" attributes make no sense.
+        */
+       public Property<E> outer() {
+               return isOuter() ? this : new Property<E>(name + "+");
+       }
+
+       private boolean isOuter() {
+               return name.endsWith("+");
+       }
+
+       /**
+        * @return An expression representing null.
+        */
+       public Expression isNull() {
+               return ExpressionFactory.matchExp(getName(), null);
+       }
+
+       /**
+        * @return An expression representing a non-null value.
+        */
+       public Expression isNotNull() {
+               return ExpressionFactory.matchExp(getName(), null).notExp();
+       }
+
+       /**
+        * @return An expression representing equality to TRUE.
+        */
+       public Expression isTrue() {
+               return ExpressionFactory.matchExp(getName(), Boolean.TRUE);
+       }
+
+       /**
+        * @return An expression representing equality to FALSE.
+        */
+       public Expression isFalse() {
+               return ExpressionFactory.matchExp(getName(), Boolean.FALSE);
+       }
+
+       /**
+        * @return An expression representing equality to a value.
+        */
+       public Expression eq(E value) {
+               return ExpressionFactory.matchExp(getName(), value);
+       }
+
+       /**
+        * @return An expression representing equality between two attributes
+        *         (columns).
+        */
+       public Expression eq(Property<?> value) {
+               return ExpressionFactory.matchExp(getName(), new 
ASTObjPath(value.getName()));
+       }
+
+       /**
+        * @return An expression representing inequality to a value.
+        */
+       public Expression ne(E value) {
+               return ExpressionFactory.noMatchExp(getName(), value);
+       }
+
+       /**
+        * @return An expression representing inequality between two attributes
+        *         (columns).
+        */
+       public Expression ne(Property<?> value) {
+               return ExpressionFactory.noMatchExp(getName(), new 
ASTObjPath(value.getName()));
+       }
+
+       /**
+        * @return An expression for a Database "Like" query.
+        */
+       public Expression like(E value) {
+               return ExpressionFactory.likeExp(getName(), value);
+       }
+
+       /**
+        * @return An expression for a case insensitive "Like" query.
+        */
+       public Expression likeInsensitive(E value) {
+               return ExpressionFactory.likeIgnoreCaseExp(getName(), value);
+       }
+
+       /**
+        * @return An expression for a Database "NOT LIKE" query.
+        */
+       public Expression nlike(E value) {
+               return ExpressionFactory.notLikeExp(getName(), value);
+       }
+
+       /**
+        * @return An expression for a case insensitive "NOT LIKE" query.
+        */
+       public Expression nlikeInsensitive(E value) {
+               return ExpressionFactory.notLikeIgnoreCaseExp(getName(), value);
+       }
+
+       /**
+        * @return An expression checking for objects between a lower and upper
+        *         bound inclusive
+        * 
+        * @param lower
+        *            The lower bound.
+        * @param upper
+        *            The upper bound.
+        */
+       public Expression between(E lower, E upper) {
+               return ExpressionFactory.betweenExp(getName(), lower, upper);
+       }
+
+       /**
+        * @return An expression for finding objects with values in the given 
set.
+        */
+       public Expression in(E firstValue, E... moreValues) {
+
+               int moreValuesLength = moreValues != null ? moreValues.length : 
0;
+
+               Object[] values = new Object[moreValuesLength + 1];
+               values[0] = firstValue;
+
+               if (moreValuesLength > 0) {
+                       System.arraycopy(moreValues, 0, values, 1, 
moreValuesLength);
+               }
+
+               return ExpressionFactory.inExp(getName(), values);
+       }
+
+       /**
+        * @return An expression for finding objects with values not in the 
given
+        *         set.
+        */
+       public Expression nin(E firstValue, E... moreValues) {
+
+               int moreValuesLength = moreValues != null ? moreValues.length : 
0;
+
+               Object[] values = new Object[moreValuesLength + 1];
+               values[0] = firstValue;
+
+               if (moreValuesLength > 0) {
+                       System.arraycopy(moreValues, 0, values, 1, 
moreValuesLength);
+               }
+
+               return ExpressionFactory.notInExp(getName(), values);
+       }
+
+       /**
+        * @return An expression for finding objects with values in the given 
set.
+        */
+       public Expression in(Collection<E> values) {
+               return ExpressionFactory.inExp(getName(), values);
+       }
+
+       /**
+        * @return An expression for finding objects with values not in the 
given
+        *         set.
+        */
+       public Expression nin(Collection<E> values) {
+               return ExpressionFactory.notInExp(getName(), values);
+       }
+
+       /**
+        * @return A greater than Expression.
+        */
+       public Expression gt(E value) {
+               return ExpressionFactory.greaterExp(getName(), value);
+       }
+
+       /**
+        * @return Represents a greater than relationship between two attributes
+        *         (columns).
+        */
+       public Expression gt(Property<?> value) {
+               return ExpressionFactory.greaterExp(getName(), new 
ASTObjPath(value.getName()));
+       }
+
+       /**
+        * @return A greater than or equal to Expression.
+        */
+       public Expression gte(E value) {
+               return ExpressionFactory.greaterOrEqualExp(getName(), value);
+       }
+
+       /**
+        * @return Represents a greater than or equal relationship between two
+        *         attributes (columns).
+        */
+       public Expression gte(Property<?> value) {
+               return ExpressionFactory.greaterOrEqualExp(getName(), new 
ASTObjPath(value.getName()));
+       }
+
+       /**
+        * @return A less than Expression.
+        */
+       public Expression lt(E value) {
+               return ExpressionFactory.lessExp(getName(), value);
+       }
+
+       /**
+        * @return Represents a less than relationship between two attributes
+        *         (columns).
+        */
+       public Expression lt(Property<?> value) {
+               return ExpressionFactory.lessExp(getName(), new 
ASTObjPath(value.getName()));
+       }
+
+       /**
+        * @return A less than or equal to Expression.
+        */
+       public Expression lte(E value) {
+               return ExpressionFactory.lessOrEqualExp(getName(), value);
+       }
+
+       /**
+        * @return Represents a less than or equal relationship between two
+        *         attributes (columns).
+        */
+       public Expression lte(Property<?> value) {
+               return ExpressionFactory.lessOrEqualExp(getName(), new 
ASTObjPath(value.getName()));
+       }
+
+       /**
+        * @return Ascending sort orderings on this property.
+        */
+       public Ordering asc() {
+               return new Ordering(getName(), SortOrder.ASCENDING);
+       }
+
+       /**
+        * @return Ascending sort orderings on this property.
+        */
+       public List<Ordering> ascs() {
+               List<Ordering> result = new ArrayList<Ordering>(1);
+               result.add(asc());
+               return result;
+       }
+
+       /**
+        * @return Ascending case insensitive sort orderings on this property.
+        */
+       public Ordering ascInsensitive() {
+               return new Ordering(getName(), SortOrder.ASCENDING_INSENSITIVE);
+       }
+
+       /**
+        * @return Ascending case insensitive sort orderings on this property.
+        */
+       public List<Ordering> ascInsensitives() {
+               List<Ordering> result = new ArrayList<Ordering>(1);
+               result.add(ascInsensitive());
+               return result;
+       }
+
+       /**
+        * @return Descending sort orderings on this property.
+        */
+       public Ordering desc() {
+               return new Ordering(getName(), SortOrder.DESCENDING);
+       }
+
+       /**
+        * @return Descending sort orderings on this property.
+        */
+       public List<Ordering> descs() {
+               List<Ordering> result = new ArrayList<Ordering>(1);
+               result.add(desc());
+               return result;
+       }
+
+       /**
+        * @return Descending case insensitive sort orderings on this property.
+        */
+       public Ordering descInsensitive() {
+               return new Ordering(getName(), 
SortOrder.DESCENDING_INSENSITIVE);
+       }
+
+       /**
+        * @return Descending case insensitive sort orderings on this property.
+        */
+       public List<Ordering> descInsensitives() {
+               List<Ordering> result = new ArrayList<Ordering>(1);
+               result.add(descInsensitive());
+               return result;
+       }
+
+       /**
+        * Returns a prefetch tree that follows this property path, potentially
+        * spanning a number of phantom nodes, and having a single leaf with 
"joint"
+        * prefetch semantics.
+        */
+       public PrefetchTreeNode joint() {
+               PrefetchTreeNode node = prefetch();
+               node.setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+               return node.getRoot();
+       }
+
+       /**
+        * Returns a prefetch tree that follows this property path, potentially
+        * spanning a number of phantom nodes, and having a single leaf with
+        * "disjoint" prefetch semantics.
+        */
+       public PrefetchTreeNode disjoint() {
+               PrefetchTreeNode node = prefetch();
+               node.setSemantics(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
+               return node.getRoot();
+       }
+
+       /**
+        * Returns a prefetch tree that follows this property path, potentially
+        * spanning a number of phantom nodes, and having a single leaf with
+        * "disjoint by id" prefetch semantics.
+        */
+       public PrefetchTreeNode disjointById() {
+               PrefetchTreeNode node = prefetch();
+               
node.setSemantics(PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
+               return node.getRoot();
+       }
+
+       PrefetchTreeNode prefetch() {
+               PrefetchTreeNode root = new PrefetchTreeNode();
+               PrefetchTreeNode node = root.addPath(name);
+               node.setPhantom(false);
+               return node;
+       }
+
+       /**
+        * Extracts property value from an object using JavaBean-compatible
+        * introspection with one addition - a property can be a dot-separated
+        * property name path.
+        */
+       @SuppressWarnings("unchecked")
+       public E getFrom(Object bean) {
+               return (E) PropertyUtils.getProperty(bean, getName());
+       }
+
+       /**
+        * Extracts property value from a collection of objects using
+        * JavaBean-compatible introspection with one addition - a property can 
be a
+        * dot-separated property name path.
+        */
+       public List<E> getFromAll(Collection<?> beans) {
+               List<E> result = new ArrayList<E>(beans.size());
+               for (Object bean : beans) {
+                       result.add(getFrom(bean));
+               }
+               return result;
+       }
+
+       /**
+        * Sets a property value in 'obj' using JavaBean-compatible 
introspection
+        * with one addition - a property can be a dot-separated property name 
path.
+        */
+       public void setIn(Object bean, E value) {
+               PropertyUtils.setProperty(bean, getName(), value);
+       }
+
+       /**
+        * Sets a property value in a collection of objects using
+        * JavaBean-compatible introspection with one addition - a property can 
be a
+        * dot-separated property name path.
+        */
+       public void setInAll(Collection<?> beans, E value) {
+               for (Object bean : beans) {
+                       setIn(bean, value);
+               }
+       }
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/4d8b2e1a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java 
b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
index d97e811..7f7c6dd 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
@@ -43,417 +43,430 @@ import org.apache.cayenne.util.XMLSerializable;
  */
 class BaseQueryMetadata implements QueryMetadata, XMLSerializable, 
Serializable {
 
-    int fetchLimit = QueryMetadata.FETCH_LIMIT_DEFAULT;
-    int fetchOffset = QueryMetadata.FETCH_OFFSET_DEFAULT;
-
-    int statementFetchSize = QueryMetadata.FETCH_OFFSET_DEFAULT;
-
-    int pageSize = QueryMetadata.PAGE_SIZE_DEFAULT;
-    boolean fetchingDataRows = QueryMetadata.FETCHING_DATA_ROWS_DEFAULT;
-    QueryCacheStrategy cacheStrategy = QueryCacheStrategy.getDefaultStrategy();
-
-    PrefetchTreeNode prefetchTree;
-    String cacheKey;
-    String[] cacheGroups;
-
-    transient List<Object> resultSetMapping;
-    transient DbEntity dbEntity;
-    transient DataMap dataMap;
-    transient Object lastRoot;
-    transient ClassDescriptor classDescriptor;
-    transient EntityResolver lastEntityResolver;
-
-    /**
-     * Copies values of another QueryMetadata object to this object.
-     */
-    void copyFromInfo(QueryMetadata info) {
-        this.lastEntityResolver = null;
-        this.lastRoot = null;
-        this.classDescriptor = null;
-        this.dbEntity = null;
-        this.dataMap = null;
-
-        this.fetchingDataRows = info.isFetchingDataRows();
-        this.fetchLimit = info.getFetchLimit();
-        this.pageSize = info.getPageSize();
-        this.cacheStrategy = info.getCacheStrategy();
-        this.cacheKey = info.getCacheKey();
-        this.cacheGroups = info.getCacheGroups();
-        this.resultSetMapping = info.getResultSetMapping();
-
-        setPrefetchTree(info.getPrefetchTree());
-    }
-
-    boolean resolve(Object root, EntityResolver resolver, String cacheKey) {
-
-        if (lastRoot != root || lastEntityResolver != resolver) {
-
-            this.cacheKey = cacheKey;
-
-            this.classDescriptor = null;
-            this.dbEntity = null;
-            this.dataMap = null;
-
-            ObjEntity entity = null;
-
-            if (root != null) {
-                if (root instanceof Class<?>) {
-                    entity = resolver.getObjEntity((Class<?>) root);
-                    if (entity == null) { // entity not found, try to resolve 
it
-                                          // with
-                        // client resolver
-                        EntityResolver clientResolver = 
resolver.getClientEntityResolver();
-                        if (clientResolver != resolver) {
-                            ObjEntity clientEntity = 
clientResolver.getObjEntity((Class<?>) root);
-
-                            if (clientEntity != null) {
-                                entity = 
resolver.getObjEntity(clientEntity.getName());
-                            }
-                        }
-                    }
-
-                    if (entity != null) {
-                        this.dbEntity = entity.getDbEntity();
-                        this.dataMap = entity.getDataMap();
-                    }
-                } else if (root instanceof ObjEntity) {
-                    entity = (ObjEntity) root;
-                    this.dbEntity = entity.getDbEntity();
-                    this.dataMap = entity.getDataMap();
-                } else if (root instanceof String) {
-                    entity = resolver.getObjEntity((String) root);
-                    if (entity != null) {
-                        this.dbEntity = entity.getDbEntity();
-                        this.dataMap = entity.getDataMap();
-                    }
-                } else if (root instanceof DbEntity) {
-                    this.dbEntity = (DbEntity) root;
-                    this.dataMap = dbEntity.getDataMap();
-                } else if (root instanceof DataMap) {
-                    this.dataMap = (DataMap) root;
-                } else if (root instanceof Persistent) {
-                    entity = resolver.getObjEntity((Persistent) root);
-                    if (entity != null) {
-                        this.dbEntity = entity.getDbEntity();
-                        this.dataMap = entity.getDataMap();
-                    }
-                }
-            }
-
-            if (entity != null) {
-                this.classDescriptor = 
resolver.getClassDescriptor(entity.getName());
-            }
-
-            this.lastRoot = root;
-            this.lastEntityResolver = resolver;
-
-            return true;
-        }
-
-        return false;
-    }
-
-    void initWithProperties(Map<String, ?> properties) {
-        // must init defaults even if properties are empty
-        if (properties == null) {
-            properties = Collections.EMPTY_MAP;
-        }
-
-        Object fetchOffset = 
properties.get(QueryMetadata.FETCH_OFFSET_PROPERTY);
-        Object fetchLimit = properties.get(QueryMetadata.FETCH_LIMIT_PROPERTY);
-        Object pageSize = properties.get(QueryMetadata.PAGE_SIZE_PROPERTY);
-        Object statementFetchSize = 
properties.get(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY);
-        Object fetchingDataRows = 
properties.get(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY);
-
-        Object cacheStrategy = 
properties.get(QueryMetadata.CACHE_STRATEGY_PROPERTY);
-
-        Object cacheGroups = 
properties.get(QueryMetadata.CACHE_GROUPS_PROPERTY);
-
-        // init ivars from properties
-        this.fetchOffset = (fetchOffset != null) ? 
Integer.parseInt(fetchOffset.toString())
-                : QueryMetadata.FETCH_OFFSET_DEFAULT;
-
-        this.fetchLimit = (fetchLimit != null) ? 
Integer.parseInt(fetchLimit.toString())
-                : QueryMetadata.FETCH_LIMIT_DEFAULT;
-
-        this.pageSize = (pageSize != null) ? 
Integer.parseInt(pageSize.toString()) : QueryMetadata.PAGE_SIZE_DEFAULT;
-
-        this.statementFetchSize = (statementFetchSize != null) ? 
Integer.parseInt(statementFetchSize.toString())
-                : QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT;
-
-        this.fetchingDataRows = (fetchingDataRows != null) ? 
"true".equalsIgnoreCase(fetchingDataRows.toString())
-                : QueryMetadata.FETCHING_DATA_ROWS_DEFAULT;
-
-        this.cacheStrategy = (cacheStrategy != null) ? 
QueryCacheStrategy.safeValueOf(cacheStrategy.toString())
-                : QueryCacheStrategy.getDefaultStrategy();
-
-        this.cacheGroups = null;
-        if (cacheGroups instanceof String[]) {
-            this.cacheGroups = (String[]) cacheGroups;
-        } else if (cacheGroups instanceof String) {
-            StringTokenizer toks = new StringTokenizer(cacheGroups.toString(), 
",");
-            this.cacheGroups = new String[toks.countTokens()];
-            for (int i = 0; i < this.cacheGroups.length; i++) {
-                this.cacheGroups[i] = toks.nextToken();
-            }
-        }
-    }
-
-    public void encodeAsXML(XMLEncoder encoder) {
-
-        if (fetchingDataRows != QueryMetadata.FETCHING_DATA_ROWS_DEFAULT) {
-            encoder.printProperty(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY, 
fetchingDataRows);
-        }
-
-        if (fetchOffset != QueryMetadata.FETCH_OFFSET_DEFAULT) {
-            encoder.printProperty(QueryMetadata.FETCH_OFFSET_PROPERTY, 
fetchOffset);
-        }
-
-        if (fetchLimit != QueryMetadata.FETCH_LIMIT_DEFAULT) {
-            encoder.printProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, 
fetchLimit);
-        }
-
-        if (pageSize != QueryMetadata.PAGE_SIZE_DEFAULT) {
-            encoder.printProperty(QueryMetadata.PAGE_SIZE_PROPERTY, pageSize);
-        }
-
-        if (cacheStrategy != null && QueryCacheStrategy.getDefaultStrategy() 
!= cacheStrategy) {
-            encoder.printProperty(QueryMetadata.CACHE_STRATEGY_PROPERTY, 
cacheStrategy.name());
-        }
-
-        if (statementFetchSize != QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT) {
-            encoder.printProperty(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY, 
statementFetchSize);
-        }
-
-        if (prefetchTree != null) {
-            prefetchTree.encodeAsXML(encoder);
-        }
-
-        if (cacheGroups != null && cacheGroups.length > 0) {
-            StringBuilder buffer = new StringBuilder(cacheGroups[0]);
-            for (int i = 1; i < cacheGroups.length; i++) {
-                buffer.append(',').append(cacheGroups[i]);
-            }
-            encoder.printProperty(QueryMetadata.CACHE_GROUPS_PROPERTY, 
buffer.toString());
-        }
-    }
-
-    /**
-     * @since 1.2
-     */
-    public String getCacheKey() {
-        return cacheKey;
-    }
-
-    /**
-     * @since 1.2
-     */
-    public DataMap getDataMap() {
-        return dataMap;
-    }
-
-    /**
-     * @since 1.2
-     */
-    public Procedure getProcedure() {
-        return null;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public Map<String, String> getPathSplitAliases() {
-        return Collections.emptyMap();
-    }
-
-    /**
-     * @since 1.2
-     */
-    public DbEntity getDbEntity() {
-        return dbEntity;
-    }
-
-    /**
-     * @since 1.2
-     */
-    public ObjEntity getObjEntity() {
-        return classDescriptor != null ? classDescriptor.getEntity() : null;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public ClassDescriptor getClassDescriptor() {
-        return classDescriptor;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public List<Object> getResultSetMapping() {
-        return resultSetMapping;
-    }
-
-    /**
-     * @since 1.2
-     */
-    public PrefetchTreeNode getPrefetchTree() {
-        return prefetchTree;
-    }
-
-    void setPrefetchTree(PrefetchTreeNode prefetchTree) {
-        this.prefetchTree = prefetchTree != null ? deepClone(prefetchTree, 
null) : null;
-    }
-
-    private PrefetchTreeNode deepClone(PrefetchTreeNode source, 
PrefetchTreeNode targetParent) {
-
-        PrefetchTreeNode target = new PrefetchTreeNode(targetParent, 
source.getName());
-        target.setEjbqlPathEntityId(source.getEjbqlPathEntityId());
-        target.setEntityName(source.getEntityName());
-        target.setPhantom(source.isPhantom());
-        target.setSemantics(source.getSemantics());
-
-        for (PrefetchTreeNode child : source.getChildren()) {
-            target.addChild(deepClone(child, target));
-        }
-
-        return target;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public QueryCacheStrategy getCacheStrategy() {
-        return cacheStrategy;
-    }
-
-    /**
-     * @since 3.0
-     */
-    void setCacheStrategy(QueryCacheStrategy cacheStrategy) {
-        this.cacheStrategy = cacheStrategy;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public String[] getCacheGroups() {
-        return cacheGroups;
-    }
-
-    /**
-     * @since 3.0
-     */
-    void setCacheGroups(String... groups) {
-        this.cacheGroups = groups;
-    }
-
-    public boolean isFetchingDataRows() {
-        return fetchingDataRows;
-    }
-
-    public int getFetchLimit() {
-        return fetchLimit;
-    }
-
-    public int getPageSize() {
-        return pageSize;
-    }
-
-    public Query getOrginatingQuery() {
-        return null;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public int getFetchOffset() {
-        return fetchOffset;
-    }
-
-    public boolean isRefreshingObjects() {
-        return true;
-    }
-
-    void setFetchingDataRows(boolean b) {
-        fetchingDataRows = b;
-    }
-
-    void setFetchLimit(int i) {
-        fetchLimit = i;
-    }
-
-    void setFetchOffset(int i) {
-        fetchOffset = i;
-    }
-
-    void setPageSize(int i) {
-        pageSize = i;
-    }
-
-    /**
-     * Sets statement's fetch size (0 for no default size)
-     * 
-     * @since 3.0
-     */
-    void setStatementFetchSize(int size) {
-        this.statementFetchSize = size;
-    }
-
-    /**
-     * @return statement's fetch size
-     * @since 3.0
-     */
-    public int getStatementFetchSize() {
-        return statementFetchSize;
-    }
-
-    /**
-     * Adds a joint prefetch.
-     * 
-     * @since 1.2
-     */
-    PrefetchTreeNode addPrefetch(String path, int semantics) {
-        if (prefetchTree == null) {
-            prefetchTree = new PrefetchTreeNode();
-        }
-
-        PrefetchTreeNode node = prefetchTree.addPath(path);
-        node.setSemantics(semantics);
-        node.setPhantom(false);
-        return node;
-    }
-
-    /**
-     * Adds all prefetches from a provided collection.
-     * 
-     * @since 1.2
-     */
-    void addPrefetches(Collection<String> prefetches, int semantics) {
-        if (prefetches != null) {
-            for (String prefetch : prefetches) {
-                addPrefetch(prefetch, semantics);
-            }
-        }
-    }
-
-    /**
-     * Clears all joint prefetches.
-     * 
-     * @since 1.2
-     */
-    void clearPrefetches() {
-        prefetchTree = null;
-    }
-
-    /**
-     * Removes joint prefetch.
-     * 
-     * @since 1.2
-     */
-    void removePrefetch(String prefetch) {
-        if (prefetchTree != null) {
-            prefetchTree.removePath(prefetch);
-        }
-    }
+       int fetchLimit = QueryMetadata.FETCH_LIMIT_DEFAULT;
+       int fetchOffset = QueryMetadata.FETCH_OFFSET_DEFAULT;
+
+       int statementFetchSize = QueryMetadata.FETCH_OFFSET_DEFAULT;
+
+       int pageSize = QueryMetadata.PAGE_SIZE_DEFAULT;
+       boolean fetchingDataRows = QueryMetadata.FETCHING_DATA_ROWS_DEFAULT;
+       QueryCacheStrategy cacheStrategy = 
QueryCacheStrategy.getDefaultStrategy();
+
+       PrefetchTreeNode prefetchTree;
+       String cacheKey;
+       String[] cacheGroups;
+
+       transient List<Object> resultSetMapping;
+       transient DbEntity dbEntity;
+       transient DataMap dataMap;
+       transient Object lastRoot;
+       transient ClassDescriptor classDescriptor;
+       transient EntityResolver lastEntityResolver;
+
+       /**
+        * Copies values of another QueryMetadata object to this object.
+        */
+       void copyFromInfo(QueryMetadata info) {
+               this.lastEntityResolver = null;
+               this.lastRoot = null;
+               this.classDescriptor = null;
+               this.dbEntity = null;
+               this.dataMap = null;
+
+               this.fetchingDataRows = info.isFetchingDataRows();
+               this.fetchLimit = info.getFetchLimit();
+               this.pageSize = info.getPageSize();
+               this.cacheStrategy = info.getCacheStrategy();
+               this.cacheKey = info.getCacheKey();
+               this.cacheGroups = info.getCacheGroups();
+               this.resultSetMapping = info.getResultSetMapping();
+
+               setPrefetchTree(info.getPrefetchTree());
+       }
+
+       boolean resolve(Object root, EntityResolver resolver, String cacheKey) {
+
+               if (lastRoot != root || lastEntityResolver != resolver) {
+
+                       this.cacheKey = cacheKey;
+
+                       this.classDescriptor = null;
+                       this.dbEntity = null;
+                       this.dataMap = null;
+
+                       ObjEntity entity = null;
+
+                       if (root != null) {
+                               if (root instanceof Class<?>) {
+                                       entity = 
resolver.getObjEntity((Class<?>) root);
+                                       if (entity == null) { // entity not 
found, try to resolve it
+                                                                               
        // with
+                                               // client resolver
+                                               EntityResolver clientResolver = 
resolver.getClientEntityResolver();
+                                               if (clientResolver != resolver) 
{
+                                                       ObjEntity clientEntity 
= clientResolver.getObjEntity((Class<?>) root);
+
+                                                       if (clientEntity != 
null) {
+                                                               entity = 
resolver.getObjEntity(clientEntity.getName());
+                                                       }
+                                               }
+                                       }
+
+                                       if (entity != null) {
+                                               this.dbEntity = 
entity.getDbEntity();
+                                               this.dataMap = 
entity.getDataMap();
+                                       }
+                               } else if (root instanceof ObjEntity) {
+                                       entity = (ObjEntity) root;
+                                       this.dbEntity = entity.getDbEntity();
+                                       this.dataMap = entity.getDataMap();
+                               } else if (root instanceof String) {
+                                       entity = resolver.getObjEntity((String) 
root);
+                                       if (entity != null) {
+                                               this.dbEntity = 
entity.getDbEntity();
+                                               this.dataMap = 
entity.getDataMap();
+                                       }
+                               } else if (root instanceof DbEntity) {
+                                       this.dbEntity = (DbEntity) root;
+                                       this.dataMap = dbEntity.getDataMap();
+                               } else if (root instanceof DataMap) {
+                                       this.dataMap = (DataMap) root;
+                               } else if (root instanceof Persistent) {
+                                       entity = 
resolver.getObjEntity((Persistent) root);
+                                       if (entity != null) {
+                                               this.dbEntity = 
entity.getDbEntity();
+                                               this.dataMap = 
entity.getDataMap();
+                                       }
+                               }
+                       }
+
+                       if (entity != null) {
+                               this.classDescriptor = 
resolver.getClassDescriptor(entity.getName());
+                       }
+
+                       this.lastRoot = root;
+                       this.lastEntityResolver = resolver;
+
+                       return true;
+               }
+
+               return false;
+       }
+
+       void initWithProperties(Map<String, ?> properties) {
+               // must init defaults even if properties are empty
+               if (properties == null) {
+                       properties = Collections.EMPTY_MAP;
+               }
+
+               Object fetchOffset = 
properties.get(QueryMetadata.FETCH_OFFSET_PROPERTY);
+               Object fetchLimit = 
properties.get(QueryMetadata.FETCH_LIMIT_PROPERTY);
+               Object pageSize = 
properties.get(QueryMetadata.PAGE_SIZE_PROPERTY);
+               Object statementFetchSize = 
properties.get(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY);
+               Object fetchingDataRows = 
properties.get(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY);
+
+               Object cacheStrategy = 
properties.get(QueryMetadata.CACHE_STRATEGY_PROPERTY);
+
+               Object cacheGroups = 
properties.get(QueryMetadata.CACHE_GROUPS_PROPERTY);
+
+               // init ivars from properties
+               this.fetchOffset = (fetchOffset != null) ? 
Integer.parseInt(fetchOffset.toString())
+                               : QueryMetadata.FETCH_OFFSET_DEFAULT;
+
+               this.fetchLimit = (fetchLimit != null) ? 
Integer.parseInt(fetchLimit.toString())
+                               : QueryMetadata.FETCH_LIMIT_DEFAULT;
+
+               this.pageSize = (pageSize != null) ? 
Integer.parseInt(pageSize.toString()) : QueryMetadata.PAGE_SIZE_DEFAULT;
+
+               this.statementFetchSize = (statementFetchSize != null) ? 
Integer.parseInt(statementFetchSize.toString())
+                               : QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT;
+
+               this.fetchingDataRows = (fetchingDataRows != null) ? 
"true".equalsIgnoreCase(fetchingDataRows.toString())
+                               : QueryMetadata.FETCHING_DATA_ROWS_DEFAULT;
+
+               this.cacheStrategy = (cacheStrategy != null) ? 
QueryCacheStrategy.safeValueOf(cacheStrategy.toString())
+                               : QueryCacheStrategy.getDefaultStrategy();
+
+               this.cacheGroups = null;
+               if (cacheGroups instanceof String[]) {
+                       this.cacheGroups = (String[]) cacheGroups;
+               } else if (cacheGroups instanceof String) {
+                       StringTokenizer toks = new 
StringTokenizer(cacheGroups.toString(), ",");
+                       this.cacheGroups = new String[toks.countTokens()];
+                       for (int i = 0; i < this.cacheGroups.length; i++) {
+                               this.cacheGroups[i] = toks.nextToken();
+                       }
+               }
+       }
+
+       public void encodeAsXML(XMLEncoder encoder) {
+
+               if (fetchingDataRows != 
QueryMetadata.FETCHING_DATA_ROWS_DEFAULT) {
+                       
encoder.printProperty(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY, 
fetchingDataRows);
+               }
+
+               if (fetchOffset != QueryMetadata.FETCH_OFFSET_DEFAULT) {
+                       
encoder.printProperty(QueryMetadata.FETCH_OFFSET_PROPERTY, fetchOffset);
+               }
+
+               if (fetchLimit != QueryMetadata.FETCH_LIMIT_DEFAULT) {
+                       
encoder.printProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, fetchLimit);
+               }
+
+               if (pageSize != QueryMetadata.PAGE_SIZE_DEFAULT) {
+                       encoder.printProperty(QueryMetadata.PAGE_SIZE_PROPERTY, 
pageSize);
+               }
+
+               if (cacheStrategy != null && 
QueryCacheStrategy.getDefaultStrategy() != cacheStrategy) {
+                       
encoder.printProperty(QueryMetadata.CACHE_STRATEGY_PROPERTY, 
cacheStrategy.name());
+               }
+
+               if (statementFetchSize != 
QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT) {
+                       
encoder.printProperty(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY, 
statementFetchSize);
+               }
+
+               if (prefetchTree != null) {
+                       prefetchTree.encodeAsXML(encoder);
+               }
+
+               if (cacheGroups != null && cacheGroups.length > 0) {
+                       StringBuilder buffer = new 
StringBuilder(cacheGroups[0]);
+                       for (int i = 1; i < cacheGroups.length; i++) {
+                               buffer.append(',').append(cacheGroups[i]);
+                       }
+                       
encoder.printProperty(QueryMetadata.CACHE_GROUPS_PROPERTY, buffer.toString());
+               }
+       }
+
+       /**
+        * @since 1.2
+        */
+       public String getCacheKey() {
+               return cacheKey;
+       }
+
+       /**
+        * @since 1.2
+        */
+       public DataMap getDataMap() {
+               return dataMap;
+       }
+
+       /**
+        * @since 1.2
+        */
+       public Procedure getProcedure() {
+               return null;
+       }
+
+       /**
+        * @since 3.0
+        */
+       public Map<String, String> getPathSplitAliases() {
+               return Collections.emptyMap();
+       }
+
+       /**
+        * @since 1.2
+        */
+       public DbEntity getDbEntity() {
+               return dbEntity;
+       }
+
+       /**
+        * @since 1.2
+        */
+       public ObjEntity getObjEntity() {
+               return classDescriptor != null ? classDescriptor.getEntity() : 
null;
+       }
+
+       /**
+        * @since 3.0
+        */
+       public ClassDescriptor getClassDescriptor() {
+               return classDescriptor;
+       }
+
+       /**
+        * @since 3.0
+        */
+       public List<Object> getResultSetMapping() {
+               return resultSetMapping;
+       }
+
+       /**
+        * @since 1.2
+        */
+       public PrefetchTreeNode getPrefetchTree() {
+               return prefetchTree;
+       }
+
+       void setPrefetchTree(PrefetchTreeNode prefetchTree) {
+               this.prefetchTree = prefetchTree != null ? 
deepClone(prefetchTree, null) : null;
+       }
+
+       private PrefetchTreeNode deepClone(PrefetchTreeNode source, 
PrefetchTreeNode targetParent) {
+
+               PrefetchTreeNode target = new PrefetchTreeNode(targetParent, 
source.getName());
+               target.setEjbqlPathEntityId(source.getEjbqlPathEntityId());
+               target.setEntityName(source.getEntityName());
+               target.setPhantom(source.isPhantom());
+               target.setSemantics(source.getSemantics());
+
+               for (PrefetchTreeNode child : source.getChildren()) {
+                       target.addChild(deepClone(child, target));
+               }
+
+               return target;
+       }
+
+       /**
+        * @since 3.0
+        */
+       public QueryCacheStrategy getCacheStrategy() {
+               return cacheStrategy;
+       }
+
+       /**
+        * @since 3.0
+        */
+       void setCacheStrategy(QueryCacheStrategy cacheStrategy) {
+               this.cacheStrategy = cacheStrategy;
+       }
+
+       /**
+        * @since 3.0
+        */
+       public String[] getCacheGroups() {
+               return cacheGroups;
+       }
+
+       /**
+        * @since 3.0
+        */
+       void setCacheGroups(String... groups) {
+               this.cacheGroups = groups;
+       }
+
+       public boolean isFetchingDataRows() {
+               return fetchingDataRows;
+       }
+
+       public int getFetchLimit() {
+               return fetchLimit;
+       }
+
+       public int getPageSize() {
+               return pageSize;
+       }
+
+       public Query getOrginatingQuery() {
+               return null;
+       }
+
+       /**
+        * @since 3.0
+        */
+       public int getFetchOffset() {
+               return fetchOffset;
+       }
+
+       public boolean isRefreshingObjects() {
+               return true;
+       }
+
+       void setFetchingDataRows(boolean b) {
+               fetchingDataRows = b;
+       }
+
+       void setFetchLimit(int i) {
+               fetchLimit = i;
+       }
+
+       void setFetchOffset(int i) {
+               fetchOffset = i;
+       }
+
+       void setPageSize(int i) {
+               pageSize = i;
+       }
+
+       /**
+        * Sets statement's fetch size (0 for no default size)
+        * 
+        * @since 3.0
+        */
+       void setStatementFetchSize(int size) {
+               this.statementFetchSize = size;
+       }
+
+       /**
+        * @return statement's fetch size
+        * @since 3.0
+        */
+       public int getStatementFetchSize() {
+               return statementFetchSize;
+       }
+
+       /**
+        * Adds a joint prefetch.
+        * 
+        * @since 1.2
+        */
+       PrefetchTreeNode addPrefetch(String path, int semantics) {
+               if (prefetchTree == null) {
+                       prefetchTree = new PrefetchTreeNode();
+               }
+
+               PrefetchTreeNode node = prefetchTree.addPath(path);
+               node.setSemantics(semantics);
+               node.setPhantom(false);
+               return node;
+       }
+
+       /**
+        * Adds a joint prefetch.
+        * 
+        * @since 4.0
+        */
+       void mergePrefetch(PrefetchTreeNode node) {
+               if (prefetchTree == null) {
+                       prefetchTree = new PrefetchTreeNode();
+               }
+
+               prefetchTree.merge(node);
+       }
+
+       /**
+        * Adds all prefetches from a provided collection.
+        * 
+        * @since 1.2
+        */
+       void addPrefetches(Collection<String> prefetches, int semantics) {
+               if (prefetches != null) {
+                       for (String prefetch : prefetches) {
+                               addPrefetch(prefetch, semantics);
+                       }
+               }
+       }
+
+       /**
+        * Clears all joint prefetches.
+        * 
+        * @since 1.2
+        */
+       void clearPrefetches() {
+               prefetchTree = null;
+       }
+
+       /**
+        * Removes joint prefetch.
+        * 
+        * @since 1.2
+        */
+       void removePrefetch(String prefetch) {
+               if (prefetchTree != null) {
+                       prefetchTree.removePath(prefetch);
+               }
+       }
 }

Reply via email to