You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2013/06/22 21:36:54 UTC

svn commit: r1495773 [1/3] - in /cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src: main/java/org/apache/cayenne/ main/java/org/apache/cayenne/access/ main/java/org/apache/cayenne/access/jdbc/ main/java/org/apache/cayenne/access/trans/ main/j...

Author: aadamchik
Date: Sat Jun 22 19:36:53 2013
New Revision: 1495773

URL: http://svn.apache.org/r1495773
Log:
CAY-1789  Lock-free EntityResolver

cleanup

Modified:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/Cayenne.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/ProcedureTranslator.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResolver.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/SQLResult.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/PersistentDescriptorFactory.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/ClientServerChannelTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextPrefetchTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DeleteObjectTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DeleteRulesTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/FlattenedArcKeyTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/QuotedIdentifiersTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/jdbc/BatchActionTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/jdbc/SoftDeleteBatchQueryBuilderTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/trans/DeleteBatchQueryBuilderTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/trans/InsertBatchQueryBuilderTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/trans/UpdateBatchQueryBuilderTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/ashwood/AshwoodEntitySorterTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/dba/JdbcPkGeneratorTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/exp/ValueInjectorTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ClientEntityResolverTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/EntityInheritanceTreeTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/EntityResolverGenericStackTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/EntityResolverInheritanceTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/EntityResolverTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ObjEntityTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ObjRelationshipTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/query/SelectQueryPrefetchRouterActionQualifiedEntityTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/query/SelectQueryPrefetchRouterActionTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/query/SelectQueryTest.java

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java Sat Jun 22 19:36:53 2013
@@ -573,7 +573,7 @@ public abstract class BaseContext implem
 
         ObjEntity entity;
         try {
-            entity = getEntityResolver().lookupObjEntity(object.getClass());
+            entity = getEntityResolver().getObjEntity(object.getClass());
         } catch (CayenneRuntimeException ex) {
             // ObjEntity cannot be fetched, ignored
             entity = null;

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/Cayenne.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/Cayenne.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/Cayenne.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/Cayenne.java Sat Jun 22 19:36:53 2013
@@ -399,7 +399,7 @@ public class Cayenne {
             Class<T> dataObjectClass,
             Map<String, ?> pk) {
 
-        ObjEntity entity = context.getEntityResolver().lookupObjEntity(dataObjectClass);
+        ObjEntity entity = context.getEntityResolver().getObjEntity(dataObjectClass);
         if (entity == null) {
             throw new CayenneRuntimeException("Non-existent ObjEntity for class: "
                     + dataObjectClass);
@@ -539,7 +539,7 @@ public class Cayenne {
             throw new IllegalArgumentException("Null DataObject class.");
         }
 
-        ObjEntity entity = context.getEntityResolver().lookupObjEntity(dataObjectClass);
+        ObjEntity entity = context.getEntityResolver().getObjEntity(dataObjectClass);
         if (entity == null) {
             throw new CayenneRuntimeException("Unmapped DataObject Class: "
                     + dataObjectClass.getName());

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java Sat Jun 22 19:36:53 2013
@@ -244,7 +244,7 @@ public class CayenneContext extends Base
             throw new NullPointerException("Persistent class can't be null.");
         }
 
-        ObjEntity entity = getEntityResolver().lookupObjEntity(persistentClass);
+        ObjEntity entity = getEntityResolver().getObjEntity(persistentClass);
         if (entity == null) {
             throw new CayenneRuntimeException("No entity mapped for class: " + persistentClass);
         }
@@ -265,7 +265,7 @@ public class CayenneContext extends Base
             throw new NullPointerException("An attempt to register null object.");
         }
 
-        ObjEntity entity = getEntityResolver().lookupObjEntity(object.getClass());
+        ObjEntity entity = getEntityResolver().getObjEntity(object.getClass());
         ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entity.getName());
         registerNewObject((Persistent) object, entity.getName(), descriptor);
     }
@@ -274,7 +274,6 @@ public class CayenneContext extends Base
      * Runs a query, returning result as list.
      */
     @Override
-    @SuppressWarnings("unchecked")
     public List performQuery(Query query) {
         List result = onQuery(this, query).firstList();
         return result != null ? result : new ArrayList<Object>(1);

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java Sat Jun 22 19:36:53 2013
@@ -415,7 +415,7 @@ public class DataContext extends BaseCon
      * @since 3.1
      */
     public <T extends Persistent> T objectFromDataRow(Class<T> objectClass, DataRow dataRow) {
-        ObjEntity entity = this.getEntityResolver().lookupObjEntity(objectClass);
+        ObjEntity entity = this.getEntityResolver().getObjEntity(objectClass);
 
         if (entity == null) {
             throw new CayenneRuntimeException("Unmapped Java class: " + objectClass);
@@ -451,7 +451,7 @@ public class DataContext extends BaseCon
             throw new NullPointerException("Null 'persistentClass'");
         }
 
-        ObjEntity entity = getEntityResolver().lookupObjEntity(persistentClass);
+        ObjEntity entity = getEntityResolver().getObjEntity(persistentClass);
         if (entity == null) {
             throw new IllegalArgumentException("Class is not mapped with Cayenne: " + persistentClass.getName());
         }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java Sat Jun 22 19:36:53 2013
@@ -189,7 +189,7 @@ public class ProcedureAction extends Bas
      * Returns stored procedure for an internal query.
      */
     protected Procedure getProcedure() {
-        return getEntityResolver().lookupProcedure(query);
+        return query.getMetaData(getEntityResolver()).getProcedure();
     }
 
     /**

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/ProcedureTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/ProcedureTranslator.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/ProcedureTranslator.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/ProcedureTranslator.java Sat Jun 22 19:36:53 2013
@@ -172,7 +172,7 @@ public class ProcedureTranslator {
     }
 
     public Procedure getProcedure() {
-        return entityResolver.lookupProcedure(query);
+        return query.getMetaData(entityResolver).getProcedure();
     }
 
     public ProcedureQuery getProcedureQuery() {

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResolver.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResolver.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResolver.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResolver.java Sat Jun 22 19:36:53 2013
@@ -337,9 +337,9 @@ public class EntityResolver implements M
      */
     @Deprecated
     public Collection<SQLResult> getResultSets() {
-       return getResults();
+        return getResults();
     }
-    
+
     /**
      * @since 3.2
      */
@@ -379,12 +379,31 @@ public class EntityResolver implements M
         return result;
     }
 
-    public Procedure getProcedure(String name) {
-        return lookupProcedure(name);
+    public Procedure getProcedure(String procedureName) {
+        Procedure result = mappingCache.getProcedure(procedureName);
+        if (result == null) {
+            // reconstruct cache just in case some of the datamaps
+            // have changed and now contain the required information
+            refreshMappingCache();
+            result = mappingCache.getProcedure(procedureName);
+        }
+
+        return result;
     }
 
+    /**
+     * Returns a named query or null if no query exists for a given name.
+     */
     public Query getQuery(String name) {
-        return lookupQuery(name);
+        Query result = mappingCache.getQuery(name);
+
+        if (result == null) {
+            // reconstruct cache just in case some of the datamaps
+            // have changed and now contain the required information
+            refreshMappingCache();
+            result = mappingCache.getQuery(name);
+        }
+        return result;
     }
 
     /**
@@ -573,42 +592,34 @@ public class EntityResolver implements M
                 return getObjEntity(id.getEntityName());
             }
         } else if (object instanceof Class) {
-            return lookupObjEntity((Class<?>) object);
+            return getObjEntity((Class<?>) object);
         }
 
-        return lookupObjEntity(object.getClass());
+        return getObjEntity(object.getClass());
     }
 
+    /**
+     * @deprecated since 3.2. Use q.getMetaData(resolver).getProcedure()
+     */
+    @Deprecated
     public Procedure lookupProcedure(Query q) {
         return q.getMetaData(this).getProcedure();
     }
 
+    /**
+     * @deprecated since 3.2 use {@link #getProcedure(String)}.
+     */
+    @Deprecated
     public Procedure lookupProcedure(String procedureName) {
-
-        Procedure result = mappingCache.getProcedure(procedureName);
-        if (result == null) {
-            // reconstruct cache just in case some of the datamaps
-            // have changed and now contain the required information
-            refreshMappingCache();
-            result = mappingCache.getProcedure(procedureName);
-        }
-
-        return result;
+        return getProcedure(procedureName);
     }
 
     /**
-     * Returns a named query or null if no query exists for a given name.
+     * @deprecated since 3.2 use {@link #getQuery(String)}.
      */
+    @Deprecated
     public Query lookupQuery(String name) {
-        Query result = mappingCache.getQuery(name);
-
-        if (result == null) {
-            // reconstruct cache just in case some of the datamaps
-            // have changed and now contain the required information
-            refreshMappingCache();
-            result = mappingCache.getQuery(name);
-        }
-        return result;
+        return getQuery(name);
     }
 
     public synchronized void removeDataMap(DataMap map) {

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java Sat Jun 22 19:36:53 2013
@@ -81,7 +81,7 @@ public class EntityResult {
             return resolver.getObjEntity(entityName);
         }
         else if (entityClass != null) {
-            return resolver.lookupObjEntity(entityClass);
+            return resolver.getObjEntity(entityClass);
         }
         else {
             throw new IllegalStateException("Both entity name and class are null");

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java Sat Jun 22 19:36:53 2013
@@ -41,781 +41,754 @@ import org.apache.cayenne.util.XMLEncode
  */
 public class ObjRelationship extends Relationship implements ConfigurationNode {
 
-	/**
-	 * Denotes a default type of to-many relationship collection which is a Java
-	 * List.
-	 * 
-	 * @since 3.0
-	 */
-	public static final String DEFAULT_COLLECTION_TYPE = "java.util.List";
-
-	boolean readOnly;
-
-	protected int deleteRule = DeleteRule.NO_ACTION;
-	protected boolean usedForLocking;
-
-	protected List<DbRelationship> dbRelationships = new ArrayList<DbRelationship>(
-			2);
-
-	/**
-	 * Db-relationships path that is set but not yet parsed (turned into
-	 * List<DbRelationship>) Used during map loading
-	 */
-	String deferredPath;
-
-	/**
-	 * Stores the type of collection mapped by a to-many relationship. Null for
-	 * to-one relationships.
-	 * 
-	 * @since 3.0
-	 */
-	protected String collectionType;
-
-	/**
-	 * Stores a property name of a target entity used to create a relationship
-	 * map. Only has effect if collectionType property is set to
-	 * "java.util.Map".
-	 * 
-	 * @since 3.0
-	 */
-	protected String mapKey;
-
-	public ObjRelationship() {
-		this(null);
-	}
-
-	public ObjRelationship(String name) {
-		super(name);
-	}
-
-	/**
-	 * @since 3.1
-	 */
-	public <T> T acceptVisitor(ConfigurationNodeVisitor<T> visitor) {
-		return visitor.visitObjRelationship(this);
-	}
-
-	/**
-	 * Prints itself as XML to the provided XMLEncoder.
-	 * 
-	 * @since 1.1
-	 */
-	public void encodeAsXML(XMLEncoder encoder) {
-		ObjEntity source = (ObjEntity) getSourceEntity();
-		if (source == null) {
-			return;
-		}
-
-		encoder.print("<obj-relationship name=\"" + getName());
-		encoder.print("\" source=\"" + source.getName());
-
-		// looking up a target entity ensures that bogus names are not saved...
-		// whether
-		// this is good or bad is debatable, as users may want to point to
-		// non-existent
-		// entities on purpose.
-		ObjEntity target = (ObjEntity) getTargetEntity();
-		if (target != null) {
-			encoder.print("\" target=\"" + target.getName());
-		}
-
-		if (getCollectionType() != null
-				&& !DEFAULT_COLLECTION_TYPE.equals(getCollectionType())) {
-			encoder.print("\" collection-type=\"" + getCollectionType());
-		}
-
-		if (getMapKey() != null) {
-			encoder.print("\" map-key=\"" + getMapKey());
-		}
-
-		if (isUsedForLocking()) {
-			encoder.print("\" lock=\"true");
-		}
-
-		String deleteRule = DeleteRule.deleteRuleName(getDeleteRule());
-		if (getDeleteRule() != DeleteRule.NO_ACTION && deleteRule != null) {
-			encoder.print("\" deleteRule=\"" + deleteRule);
-		}
-
-		// quietly get rid of invalid path... this is not the best way of doing
-		// things,
-		// but it is consistent across map package
-		String path = getValidRelationshipPath();
-		if (path != null) {
-			encoder.print("\" db-relationship-path=\"" + path);
-		}
-
-		encoder.println("\"/>");
-	}
-
-	/**
-	 * Returns a target ObjEntity of this relationship. Entity is looked up in
-	 * the parent DataMap using "targetEntityName".
-	 */
-	@Override
-	public Entity getTargetEntity() {
-		String targetName = getTargetEntityName();
-		if (targetName == null) {
-			return null;
-		}
-
-		return getNonNullNamespace().getObjEntity(targetName);
-	}
-
-	/**
-	 * Returns the name of a complimentary relationship going in the opposite
-	 * direction or null if it doesn't exist.
-	 * 
-	 * @since 1.2
-	 */
-	public String getReverseRelationshipName() {
-		ObjRelationship reverse = getReverseRelationship();
-		return (reverse != null) ? reverse.getName() : null;
-	}
-
-	/**
-	 * Returns a "complimentary" ObjRelationship going in the opposite
-	 * direction. Returns null if no such relationship is found.
-	 */
-	@Override
-	public ObjRelationship getReverseRelationship() {
-
-		// reverse the list
-		List<DbRelationship> relationships = getDbRelationships();
-		List<DbRelationship> reversed = new ArrayList<DbRelationship>(
-				relationships.size());
-
-		for (DbRelationship relationship : relationships) {
-			DbRelationship reverse = relationship.getReverseRelationship();
-			if (reverse == null) {
-				return null;
-			}
-
-			reversed.add(0, reverse);
-		}
-
-		ObjEntity target = (ObjEntity) this.getTargetEntity();
-		if (target == null) {
-			return null;
-		}
-
-		Entity source = getSourceEntity();
-
-		for (ObjRelationship relationship : target.getRelationships()) {
-
-			if (relationship.getTargetEntity() != source) {
-				continue;
-			}
-
-			List<?> otherRels = relationship.getDbRelationships();
-			if (reversed.size() != otherRels.size()) {
-				continue;
-			}
-
-			int len = reversed.size();
-			boolean relsMatch = true;
-			for (int i = 0; i < len; i++) {
-				if (otherRels.get(i) != reversed.get(i)) {
-					relsMatch = false;
-					break;
-				}
-			}
-
-			if (relsMatch) {
-				return relationship;
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Creates a complimentary reverse relationship from target entity to the
-	 * source entity. A new relationship is created regardless of whether one
-	 * already exists. Returned relationship is not attached to the source
-	 * entity and has no name. Throws a {@link CayenneRuntimeException} if
-	 * reverse DbRelationship is not mapped.
-	 * 
-	 * @since 3.0
-	 */
-	public ObjRelationship createReverseRelationship() {
-		ObjRelationship reverse = new ObjRelationship();
-		reverse.setSourceEntity(getTargetEntity());
-		reverse.setTargetEntityName(getSourceEntity().getName());
-		reverse.setDbRelationshipPath(getReverseDbRelationshipPath());
-		return reverse;
-	}
-
-	/**
-	 * Returns an immutable list of underlying DbRelationships.
-	 */
-	public List<DbRelationship> getDbRelationships() {
-		refreshFromDeferredPath();
-		return Collections.unmodifiableList(dbRelationships);
-	}
-
-	/**
-	 * Appends a DbRelationship to the existing list of DbRelationships.
-	 */
-	public void addDbRelationship(DbRelationship dbRel) {
-		refreshFromDeferredPath();
-		if (dbRel.getName() == null) {
-			throw new IllegalArgumentException("DbRelationship has no name");
-		}
-
-		// Adding a second is creating a flattened relationship.
-		// Ensure that the new relationship properly continues
-		// on the flattened path
-		int numDbRelationships = dbRelationships.size();
-		if (numDbRelationships > 0) {
-			DbRelationship lastRel = dbRelationships
-					.get(numDbRelationships - 1);
-			if (!lastRel.getTargetEntityName().equals(
-					dbRel.getSourceEntity().getName())) {
-				throw new CayenneRuntimeException(
-						"Error adding db relationship "
-								+ dbRel
-								+ " to ObjRelationship "
-								+ this
-								+ " because the source of the newly added relationship "
-								+ "is not the target of the previous relationship "
-								+ "in the chain");
-			}
-		}
-
-		dbRelationships.add(dbRel);
-
-		this.recalculateReadOnlyValue();
-		this.recalculateToManyValue();
-	}
-
-	/**
-	 * Removes the relationship <code>dbRel</code> from the list of
-	 * relationships.
-	 */
-	public void removeDbRelationship(DbRelationship dbRel) {
-		refreshFromDeferredPath();
-		if (dbRelationships.remove(dbRel)) {
-			this.recalculateReadOnlyValue();
-			this.recalculateToManyValue();
-		}
-	}
-
-	public void clearDbRelationships() {
-		deferredPath = null;
-		this.dbRelationships.clear();
-		this.readOnly = false;
-		this.toMany = false;
-	}
-
-	/**
-	 * Returns a boolean indicating whether the presence of a non-null source
-	 * key(s) will not guarantee a presence of a target record. PK..FK
-	 * relationships are all optional, but there are other more subtle cases,
-	 * such as PK..PK, etc.
-	 * 
-	 * @since 3.0
-	 */
-	public boolean isOptional() {
-		if (isToMany() || isFlattened()) {
-			return true;
-		}
-
-		// entities with qualifiers may result in filtering even existing target
-		// rows, so
-		// such relationships are optional
-		if (isQualifiedEntity((ObjEntity) getTargetEntity())) {
-			return true;
-		}
-
-		DbRelationship dbRelationship = getDbRelationships().get(0);
-
-		// to-one mandatory relationships are either from non-PK or to master pk
-		if (dbRelationship.isToPK()) {
-			if (!dbRelationship.isFromPK()) {
-				return false;
-			}
-
-			DbRelationship reverseRelationship = dbRelationship
-					.getReverseRelationship();
-			if (reverseRelationship.isToDependentPK()) {
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	/**
-	 * Returns true if the relationship is non-optional and target has no
-	 * subclasses.
-	 * 
-	 * @since 3.0
-	 */
-	public boolean isSourceDefiningTargetPrecenseAndType(
-			EntityResolver entityResolver) {
-
-		if (isOptional()) {
-			return false;
-		}
-
-		EntityInheritanceTree inheritanceTree = entityResolver
-				.lookupInheritanceTree(getTargetEntityName());
-
-		return inheritanceTree == null
-				|| inheritanceTree.getChildren().isEmpty();
-	}
-
-	/**
-	 * Returns true if the entity or its super entities have a limiting
-	 * qualifier.
-	 */
-	private boolean isQualifiedEntity(ObjEntity entity) {
-		if (entity.getDeclaredQualifier() != null) {
-			return true;
-		}
-
-		entity = entity.getSuperEntity();
-
-		if (entity == null) {
-			return false;
-		}
-
-		return isQualifiedEntity(entity);
-	}
-
-	/**
-	 * Returns a boolean indicating whether modifying a target of such
-	 * relationship in any way will not change the underlying table row of the
-	 * source.
-	 * 
-	 * @since 1.1
-	 */
-	public boolean isSourceIndependentFromTargetChange() {
-		// note - call "isToPK" at the end of the chain, since
-		// if it is to a dependent PK, we still should return true...
-		return isToMany() || isFlattened() || isToDependentEntity()
-				|| !isToPK();
-	}
-
-	/**
-	 * Returns true if underlying DbRelationships point to dependent entity.
-	 */
-	public boolean isToDependentEntity() {
-		return (getDbRelationships().get(0)).isToDependentPK();
-	}
-
-	/**
-	 * Returns true if the underlying DbRelationships point to a at least one of
-	 * the columns of the target entity.
-	 * 
-	 * @since 1.1
-	 */
-	public boolean isToPK() {
-		return (getDbRelationships().get(0)).isToPK();
-	}
-
-	/**
-	 * Returns true if the relationship is a "flattened" relationship. A
-	 * relationship is considered "flattened" if it maps to more than one
-	 * DbRelationship. Such chain of DbRelationships is also called
-	 * "relationship path". All flattened relationships are at least readable,
-	 * but only those formed across a many-many join table (with no custom
-	 * attributes other than foreign keys) can be automatically written.
-	 * 
-	 * @see #isReadOnly
-	 * @return flag indicating if the relationship is flattened or not.
-	 */
-	public boolean isFlattened() {
-		return getDbRelationships().size() > 1;
-	}
-
-	/**
-	 * Returns true if the relationship is flattened, but is not of the single
-	 * case that can have automatic write support. Otherwise, it returns false.
-	 * 
-	 * @return flag indicating if the relationship is read only or not
-	 */
-	public boolean isReadOnly() {
-		refreshFromDeferredPath();
-		recalculateReadOnlyValue();
-		return readOnly;
-	}
-
-	@Override
-	public boolean isToMany() {
-		refreshFromDeferredPath();
-		recalculateToManyValue();
-		return super.isToMany();
-	}
-
-	/**
-	 * Returns the deleteRule. The delete rule is a constant from the DeleteRule
-	 * class, and specifies what should happen to the destination object when
-	 * the source object is deleted.
-	 * 
-	 * @return int a constant from DeleteRule
-	 * @see #setDeleteRule
-	 */
-	public int getDeleteRule() {
-		return deleteRule;
-	}
-
-	/**
-	 * Sets the delete rule of the relationship.
-	 * 
-	 * @param value
-	 *            New delete rule. Must be one of the constants defined in
-	 *            DeleteRule class.
-	 * @see DeleteRule
-	 * @throws IllegalArgumentException
-	 *             if the value is not a valid delete rule.
-	 */
-	public void setDeleteRule(int value) {
-		if ((value != DeleteRule.CASCADE) && (value != DeleteRule.DENY)
-				&& (value != DeleteRule.NULLIFY)
-				&& (value != DeleteRule.NO_ACTION)) {
-
-			throw new IllegalArgumentException("Delete rule value " + value
-					+ " is not a constant from the DeleteRule class");
-		}
-
-		this.deleteRule = value;
-	}
-
-	/**
-	 * Returns whether this attribute should be used for locking.
-	 * 
-	 * @since 1.1
-	 */
-	public boolean isUsedForLocking() {
-		return usedForLocking;
-	}
-
-	/**
-	 * Sets whether this attribute should be used for locking.
-	 * 
-	 * @since 1.1
-	 */
-	public void setUsedForLocking(boolean usedForLocking) {
-		this.usedForLocking = usedForLocking;
-	}
-
-	/**
-	 * Returns a dot-separated path over mapped DbRelationships.
-	 * 
-	 * @since 1.1
-	 */
-	public String getDbRelationshipPath() {
-		refreshFromDeferredPath();
-
-		// build path on the fly
-		if (getDbRelationships().isEmpty()) {
-			return null;
-		}
-
-		StringBuilder path = new StringBuilder();
-		Iterator<DbRelationship> it = getDbRelationships().iterator();
-		while (it.hasNext()) {
-			DbRelationship next = it.next();
-			path.append(next.getName());
-			if (it.hasNext()) {
-				path.append(Entity.PATH_SEPARATOR);
-			}
-		}
-
-		return path.toString();
-	}
-
-	/**
-	 * Returns a reversed dbRelationship path.
-	 * 
-	 * @since 1.2
-	 */
-	public String getReverseDbRelationshipPath() throws ExpressionException {
-
-		List<DbRelationship> relationships = getDbRelationships();
-		if (relationships == null || relationships.isEmpty()) {
-			return null;
-		}
-
-		StringBuilder buffer = new StringBuilder();
-
-		// iterate in reverse order
-		ListIterator<DbRelationship> it = relationships
-				.listIterator(relationships.size());
-		while (it.hasPrevious()) {
-
-			DbRelationship relationship = it.previous();
-			DbRelationship reverse = relationship.getReverseRelationship();
-
-			// another sanity check
-			if (reverse == null) {
-				throw new CayenneRuntimeException(
-						"No reverse relationship exist for " + relationship);
-			}
-
-			if (buffer.length() > 0) {
-				buffer.append(Entity.PATH_SEPARATOR);
-			}
-
-			buffer.append(reverse.getName());
-		}
-
-		return buffer.toString();
-	}
-
-	/**
-	 * Sets mapped DbRelationships as a dot-separated path.
-	 */
-	public void setDbRelationshipPath(String relationshipPath) {
-		if (!Util.nullSafeEquals(getDbRelationshipPath(), relationshipPath)) {
-			refreshFromPath(relationshipPath, false);
-		}
-	}
-
-	/**
-	 * Sets relationship path, but does not trigger its conversion to
-	 * List<DbRelationship> For internal purposes, primarily datamap loading
-	 */
-	void setDeferredDbRelationshipPath(String relationshipPath) {
-		if (!Util.nullSafeEquals(getDbRelationshipPath(), relationshipPath)) {
-			deferredPath = relationshipPath;
-		}
-	}
-
-	/**
-	 * Loads path from "deferredPath" variable (if specified)
-	 */
-	synchronized void refreshFromDeferredPath() {
-		if (deferredPath != null) {
-			refreshFromPath(deferredPath, true);
-			deferredPath = null;
-		}
-	}
-
-	/**
-	 * Returns dot-separated path over DbRelationships, only including
-	 * components that have valid DbRelationships.
-	 */
-	String getValidRelationshipPath() {
-		String path = getDbRelationshipPath();
-		if (path == null) {
-			return null;
-		}
-
-		ObjEntity entity = (ObjEntity) getSourceEntity();
-		if (entity == null) {
-			throw new CayenneRuntimeException(
-					"Can't resolve DbRelationships, null source ObjEntity");
-		}
-
-		DbEntity dbEntity = entity.getDbEntity();
-		if (dbEntity == null) {
-			return null;
-		}
-
-		StringBuilder validPath = new StringBuilder();
-
-		try {
-			for (PathComponent<DbAttribute, DbRelationship> pathComponent : dbEntity
-					.resolvePath(new ASTDbPath(path), Collections.emptyMap())) {
-
-				if (validPath.length() > 0) {
-					validPath.append(Entity.PATH_SEPARATOR);
-				}
-				validPath.append(pathComponent.getName());
-			}
-		} catch (ExpressionException ex) {
-
-		}
-
-		return validPath.toString();
-	}
-
-	/**
-	 * Rebuild a list of relationships if String relationshipPath has changed.
-	 */
-	final void refreshFromPath(String dbRelationshipPath, boolean stripInvalid) {
-		synchronized (this) {
-
-			// remove existing relationships
-			dbRelationships.clear();
-
-			if (dbRelationshipPath != null) {
-
-				ObjEntity entity = (ObjEntity) getSourceEntity();
-				if (entity == null) {
-					throw new CayenneRuntimeException(
-							"Can't resolve DbRelationships, null source ObjEntity");
-				}
-
-				try {
-					// add new relationships from path
-					Iterator<CayenneMapEntry> it = entity
-							.resolvePathComponents(new ASTDbPath(
-									dbRelationshipPath));
-
-					while (it.hasNext()) {
-						DbRelationship relationship = (DbRelationship) it
-								.next();
-
-						dbRelationships.add(relationship);
-					}
-				} catch (ExpressionException ex) {
-					if (!stripInvalid) {
-						throw ex;
-					}
-				}
-			}
-
-			recalculateToManyValue();
-			recalculateReadOnlyValue();
-		}
-	}
-
-	/**
-	 * Recalculates whether a relationship is toMany or toOne, based on the
-	 * underlying db relationships.
-	 */
-	public void recalculateToManyValue() {
-		// If there is a single toMany along the path, then the flattend
-		// rel is toMany. If all are toOne, then the rel is toOne.
-		// Simple (non-flattened) relationships form the degenerate case
-		// taking the value of the single underlying dbrel.
-		for (DbRelationship thisRel : this.dbRelationships) {
-			if (thisRel.isToMany()) {
-				this.toMany = true;
-				return;
-			}
-		}
-
-		this.toMany = false;
-	}
-
-	/**
-	 * Recalculates a new readonly value based on the underlying
-	 * DbRelationships.
-	 */
-	public void recalculateReadOnlyValue() {
-		// not flattened, always read/write
-		if (dbRelationships.size() < 2) {
-			this.readOnly = false;
-			return;
-		}
-
-		// too long, can't handle this yet
-		if (dbRelationships.size() > 2) {
-			this.readOnly = true;
-			return;
-		}
-
-		DbRelationship firstRel = dbRelationships.get(0);
-		DbRelationship secondRel = dbRelationships.get(1);
-
-		// many-to-many with single-step join
-		// also 1..1..1 (CAY-1744) .. TODO all this should go away eventually
-		// per CAY-1743
-		if (!secondRel.isToMany()) {
-
-			// allow modifications if the joins are from FKs
-			if (!secondRel.isToPK()) {
-				this.readOnly = true;
-				return;
-			}
-
-			DbRelationship firstReverseRel = firstRel.getReverseRelationship();
-			if (firstReverseRel == null || !firstReverseRel.isToPK()) {
-				this.readOnly = true;
-				return;
-			}
-
-			this.readOnly = false;
-		}
-		else {
-			readOnly = true;
-		}
-	}
-
-	@Override
-	public String toString() {
-		return new ToStringBuilder(this).append("name", getName())
-				.append("dbRelationshipPath", getDbRelationshipPath())
-				.toString();
-	}
-
-	/**
-	 * Returns an ObjAttribute stripped of any server-side information, such as
-	 * DbAttribute mapping.
-	 * 
-	 * @since 1.2
-	 */
-	public ObjRelationship getClientRelationship() {
-		ObjRelationship reverse = getReverseRelationship();
-		String reverseName = reverse != null ? reverse.getName() : null;
-
-		ObjRelationship relationship = new ClientObjRelationship(getName(),
-				reverseName, isToMany(), isReadOnly());
-
-		relationship.setTargetEntityName(getTargetEntityName());
-		relationship.setDeleteRule(getDeleteRule());
-		relationship.setCollectionType(getCollectionType());
-
-		// TODO: copy locking flag...
-
-		return relationship;
-	}
-
-	/**
-	 * Returns the interface of collection mapped by a to-many relationship.
-	 * Returns null for to-one relationships. Default for to-many is
-	 * "java.util.List". Other possible values are "java.util.Set",
-	 * "java.util.Collection", "java.util.Map".
-	 * 
-	 * @since 3.0
-	 */
-	public String getCollectionType() {
-		if (collectionType != null) {
-			return collectionType;
-		}
-
-		return isToMany() ? DEFAULT_COLLECTION_TYPE : null;
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public void setCollectionType(String collectionType) {
-		this.collectionType = collectionType;
-	}
-
-	/**
-	 * Returns a property name of a target entity used to create a relationship
-	 * map. Only has effect if collectionType property is set to
-	 * "java.util.Map".
-	 * 
-	 * @return The attribute name used for the map key or <code>null</code> if
-	 *         the default (PK) is used as the map key.
-	 * @since 3.0
-	 */
-	public String getMapKey() {
-		return mapKey;
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public void setMapKey(String mapKey) {
-		this.mapKey = mapKey;
-	}
-
-	@Override
-	public boolean isMandatory() {
-		refreshFromDeferredPath();
-		if (dbRelationships.size() == 0) {
-			return false;
-		}
+    /**
+     * Denotes a default type of to-many relationship collection which is a Java
+     * List.
+     * 
+     * @since 3.0
+     */
+    public static final String DEFAULT_COLLECTION_TYPE = "java.util.List";
+
+    boolean readOnly;
+
+    protected int deleteRule = DeleteRule.NO_ACTION;
+    protected boolean usedForLocking;
+
+    protected List<DbRelationship> dbRelationships = new ArrayList<DbRelationship>(2);
+
+    /**
+     * Db-relationships path that is set but not yet parsed (turned into
+     * List<DbRelationship>) Used during map loading
+     */
+    String deferredPath;
+
+    /**
+     * Stores the type of collection mapped by a to-many relationship. Null for
+     * to-one relationships.
+     * 
+     * @since 3.0
+     */
+    protected String collectionType;
+
+    /**
+     * Stores a property name of a target entity used to create a relationship
+     * map. Only has effect if collectionType property is set to
+     * "java.util.Map".
+     * 
+     * @since 3.0
+     */
+    protected String mapKey;
+
+    public ObjRelationship() {
+        this(null);
+    }
+
+    public ObjRelationship(String name) {
+        super(name);
+    }
+
+    /**
+     * @since 3.1
+     */
+    public <T> T acceptVisitor(ConfigurationNodeVisitor<T> visitor) {
+        return visitor.visitObjRelationship(this);
+    }
+
+    /**
+     * Prints itself as XML to the provided XMLEncoder.
+     * 
+     * @since 1.1
+     */
+    public void encodeAsXML(XMLEncoder encoder) {
+        ObjEntity source = (ObjEntity) getSourceEntity();
+        if (source == null) {
+            return;
+        }
+
+        encoder.print("<obj-relationship name=\"" + getName());
+        encoder.print("\" source=\"" + source.getName());
+
+        // looking up a target entity ensures that bogus names are not saved...
+        // whether
+        // this is good or bad is debatable, as users may want to point to
+        // non-existent
+        // entities on purpose.
+        ObjEntity target = (ObjEntity) getTargetEntity();
+        if (target != null) {
+            encoder.print("\" target=\"" + target.getName());
+        }
+
+        if (getCollectionType() != null && !DEFAULT_COLLECTION_TYPE.equals(getCollectionType())) {
+            encoder.print("\" collection-type=\"" + getCollectionType());
+        }
+
+        if (getMapKey() != null) {
+            encoder.print("\" map-key=\"" + getMapKey());
+        }
+
+        if (isUsedForLocking()) {
+            encoder.print("\" lock=\"true");
+        }
+
+        String deleteRule = DeleteRule.deleteRuleName(getDeleteRule());
+        if (getDeleteRule() != DeleteRule.NO_ACTION && deleteRule != null) {
+            encoder.print("\" deleteRule=\"" + deleteRule);
+        }
+
+        // quietly get rid of invalid path... this is not the best way of doing
+        // things,
+        // but it is consistent across map package
+        String path = getValidRelationshipPath();
+        if (path != null) {
+            encoder.print("\" db-relationship-path=\"" + path);
+        }
+
+        encoder.println("\"/>");
+    }
+
+    /**
+     * Returns a target ObjEntity of this relationship. Entity is looked up in
+     * the parent DataMap using "targetEntityName".
+     */
+    @Override
+    public Entity getTargetEntity() {
+        String targetName = getTargetEntityName();
+        if (targetName == null) {
+            return null;
+        }
+
+        return getNonNullNamespace().getObjEntity(targetName);
+    }
+
+    /**
+     * Returns the name of a complimentary relationship going in the opposite
+     * direction or null if it doesn't exist.
+     * 
+     * @since 1.2
+     */
+    public String getReverseRelationshipName() {
+        ObjRelationship reverse = getReverseRelationship();
+        return (reverse != null) ? reverse.getName() : null;
+    }
+
+    /**
+     * Returns a "complimentary" ObjRelationship going in the opposite
+     * direction. Returns null if no such relationship is found.
+     */
+    @Override
+    public ObjRelationship getReverseRelationship() {
+
+        // reverse the list
+        List<DbRelationship> relationships = getDbRelationships();
+        List<DbRelationship> reversed = new ArrayList<DbRelationship>(relationships.size());
+
+        for (DbRelationship relationship : relationships) {
+            DbRelationship reverse = relationship.getReverseRelationship();
+            if (reverse == null) {
+                return null;
+            }
+
+            reversed.add(0, reverse);
+        }
+
+        ObjEntity target = (ObjEntity) this.getTargetEntity();
+        if (target == null) {
+            return null;
+        }
+
+        Entity source = getSourceEntity();
+
+        for (ObjRelationship relationship : target.getRelationships()) {
+
+            if (relationship.getTargetEntity() != source) {
+                continue;
+            }
+
+            List<?> otherRels = relationship.getDbRelationships();
+            if (reversed.size() != otherRels.size()) {
+                continue;
+            }
+
+            int len = reversed.size();
+            boolean relsMatch = true;
+            for (int i = 0; i < len; i++) {
+                if (otherRels.get(i) != reversed.get(i)) {
+                    relsMatch = false;
+                    break;
+                }
+            }
+
+            if (relsMatch) {
+                return relationship;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates a complimentary reverse relationship from target entity to the
+     * source entity. A new relationship is created regardless of whether one
+     * already exists. Returned relationship is not attached to the source
+     * entity and has no name. Throws a {@link CayenneRuntimeException} if
+     * reverse DbRelationship is not mapped.
+     * 
+     * @since 3.0
+     */
+    public ObjRelationship createReverseRelationship() {
+        ObjRelationship reverse = new ObjRelationship();
+        reverse.setSourceEntity(getTargetEntity());
+        reverse.setTargetEntityName(getSourceEntity().getName());
+        reverse.setDbRelationshipPath(getReverseDbRelationshipPath());
+        return reverse;
+    }
+
+    /**
+     * Returns an immutable list of underlying DbRelationships.
+     */
+    public List<DbRelationship> getDbRelationships() {
+        refreshFromDeferredPath();
+        return Collections.unmodifiableList(dbRelationships);
+    }
+
+    /**
+     * Appends a DbRelationship to the existing list of DbRelationships.
+     */
+    public void addDbRelationship(DbRelationship dbRel) {
+        refreshFromDeferredPath();
+        if (dbRel.getName() == null) {
+            throw new IllegalArgumentException("DbRelationship has no name");
+        }
+
+        // Adding a second is creating a flattened relationship.
+        // Ensure that the new relationship properly continues
+        // on the flattened path
+        int numDbRelationships = dbRelationships.size();
+        if (numDbRelationships > 0) {
+            DbRelationship lastRel = dbRelationships.get(numDbRelationships - 1);
+            if (!lastRel.getTargetEntityName().equals(dbRel.getSourceEntity().getName())) {
+                throw new CayenneRuntimeException("Error adding db relationship " + dbRel + " to ObjRelationship "
+                        + this + " because the source of the newly added relationship "
+                        + "is not the target of the previous relationship " + "in the chain");
+            }
+        }
+
+        dbRelationships.add(dbRel);
+
+        this.recalculateReadOnlyValue();
+        this.recalculateToManyValue();
+    }
+
+    /**
+     * Removes the relationship <code>dbRel</code> from the list of
+     * relationships.
+     */
+    public void removeDbRelationship(DbRelationship dbRel) {
+        refreshFromDeferredPath();
+        if (dbRelationships.remove(dbRel)) {
+            this.recalculateReadOnlyValue();
+            this.recalculateToManyValue();
+        }
+    }
+
+    public void clearDbRelationships() {
+        deferredPath = null;
+        this.dbRelationships.clear();
+        this.readOnly = false;
+        this.toMany = false;
+    }
+
+    /**
+     * Returns a boolean indicating whether the presence of a non-null source
+     * key(s) will not guarantee a presence of a target record. PK..FK
+     * relationships are all optional, but there are other more subtle cases,
+     * such as PK..PK, etc.
+     * 
+     * @since 3.0
+     */
+    public boolean isOptional() {
+        if (isToMany() || isFlattened()) {
+            return true;
+        }
+
+        // entities with qualifiers may result in filtering even existing target
+        // rows, so
+        // such relationships are optional
+        if (isQualifiedEntity((ObjEntity) getTargetEntity())) {
+            return true;
+        }
+
+        DbRelationship dbRelationship = getDbRelationships().get(0);
+
+        // to-one mandatory relationships are either from non-PK or to master pk
+        if (dbRelationship.isToPK()) {
+            if (!dbRelationship.isFromPK()) {
+                return false;
+            }
+
+            DbRelationship reverseRelationship = dbRelationship.getReverseRelationship();
+            if (reverseRelationship.isToDependentPK()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if the relationship is non-optional and target has no
+     * subclasses.
+     * 
+     * @since 3.0
+     */
+    public boolean isSourceDefiningTargetPrecenseAndType(EntityResolver entityResolver) {
+
+        if (isOptional()) {
+            return false;
+        }
+
+        EntityInheritanceTree inheritanceTree = entityResolver.getInheritanceTree(getTargetEntityName());
+        return inheritanceTree == null || inheritanceTree.getChildren().isEmpty();
+    }
+
+    /**
+     * Returns true if the entity or its super entities have a limiting
+     * qualifier.
+     */
+    private boolean isQualifiedEntity(ObjEntity entity) {
+        if (entity.getDeclaredQualifier() != null) {
+            return true;
+        }
+
+        entity = entity.getSuperEntity();
+
+        if (entity == null) {
+            return false;
+        }
+
+        return isQualifiedEntity(entity);
+    }
+
+    /**
+     * Returns a boolean indicating whether modifying a target of such
+     * relationship in any way will not change the underlying table row of the
+     * source.
+     * 
+     * @since 1.1
+     */
+    public boolean isSourceIndependentFromTargetChange() {
+        // note - call "isToPK" at the end of the chain, since
+        // if it is to a dependent PK, we still should return true...
+        return isToMany() || isFlattened() || isToDependentEntity() || !isToPK();
+    }
+
+    /**
+     * Returns true if underlying DbRelationships point to dependent entity.
+     */
+    public boolean isToDependentEntity() {
+        return (getDbRelationships().get(0)).isToDependentPK();
+    }
+
+    /**
+     * Returns true if the underlying DbRelationships point to a at least one of
+     * the columns of the target entity.
+     * 
+     * @since 1.1
+     */
+    public boolean isToPK() {
+        return (getDbRelationships().get(0)).isToPK();
+    }
+
+    /**
+     * Returns true if the relationship is a "flattened" relationship. A
+     * relationship is considered "flattened" if it maps to more than one
+     * DbRelationship. Such chain of DbRelationships is also called
+     * "relationship path". All flattened relationships are at least readable,
+     * but only those formed across a many-many join table (with no custom
+     * attributes other than foreign keys) can be automatically written.
+     * 
+     * @see #isReadOnly
+     * @return flag indicating if the relationship is flattened or not.
+     */
+    public boolean isFlattened() {
+        return getDbRelationships().size() > 1;
+    }
+
+    /**
+     * Returns true if the relationship is flattened, but is not of the single
+     * case that can have automatic write support. Otherwise, it returns false.
+     * 
+     * @return flag indicating if the relationship is read only or not
+     */
+    public boolean isReadOnly() {
+        refreshFromDeferredPath();
+        recalculateReadOnlyValue();
+        return readOnly;
+    }
+
+    @Override
+    public boolean isToMany() {
+        refreshFromDeferredPath();
+        recalculateToManyValue();
+        return super.isToMany();
+    }
+
+    /**
+     * Returns the deleteRule. The delete rule is a constant from the DeleteRule
+     * class, and specifies what should happen to the destination object when
+     * the source object is deleted.
+     * 
+     * @return int a constant from DeleteRule
+     * @see #setDeleteRule
+     */
+    public int getDeleteRule() {
+        return deleteRule;
+    }
+
+    /**
+     * Sets the delete rule of the relationship.
+     * 
+     * @param value
+     *            New delete rule. Must be one of the constants defined in
+     *            DeleteRule class.
+     * @see DeleteRule
+     * @throws IllegalArgumentException
+     *             if the value is not a valid delete rule.
+     */
+    public void setDeleteRule(int value) {
+        if ((value != DeleteRule.CASCADE) && (value != DeleteRule.DENY) && (value != DeleteRule.NULLIFY)
+                && (value != DeleteRule.NO_ACTION)) {
+
+            throw new IllegalArgumentException("Delete rule value " + value
+                    + " is not a constant from the DeleteRule class");
+        }
+
+        this.deleteRule = value;
+    }
+
+    /**
+     * Returns whether this attribute should be used for locking.
+     * 
+     * @since 1.1
+     */
+    public boolean isUsedForLocking() {
+        return usedForLocking;
+    }
+
+    /**
+     * Sets whether this attribute should be used for locking.
+     * 
+     * @since 1.1
+     */
+    public void setUsedForLocking(boolean usedForLocking) {
+        this.usedForLocking = usedForLocking;
+    }
+
+    /**
+     * Returns a dot-separated path over mapped DbRelationships.
+     * 
+     * @since 1.1
+     */
+    public String getDbRelationshipPath() {
+        refreshFromDeferredPath();
+
+        // build path on the fly
+        if (getDbRelationships().isEmpty()) {
+            return null;
+        }
+
+        StringBuilder path = new StringBuilder();
+        Iterator<DbRelationship> it = getDbRelationships().iterator();
+        while (it.hasNext()) {
+            DbRelationship next = it.next();
+            path.append(next.getName());
+            if (it.hasNext()) {
+                path.append(Entity.PATH_SEPARATOR);
+            }
+        }
+
+        return path.toString();
+    }
+
+    /**
+     * Returns a reversed dbRelationship path.
+     * 
+     * @since 1.2
+     */
+    public String getReverseDbRelationshipPath() throws ExpressionException {
+
+        List<DbRelationship> relationships = getDbRelationships();
+        if (relationships == null || relationships.isEmpty()) {
+            return null;
+        }
+
+        StringBuilder buffer = new StringBuilder();
+
+        // iterate in reverse order
+        ListIterator<DbRelationship> it = relationships.listIterator(relationships.size());
+        while (it.hasPrevious()) {
+
+            DbRelationship relationship = it.previous();
+            DbRelationship reverse = relationship.getReverseRelationship();
+
+            // another sanity check
+            if (reverse == null) {
+                throw new CayenneRuntimeException("No reverse relationship exist for " + relationship);
+            }
+
+            if (buffer.length() > 0) {
+                buffer.append(Entity.PATH_SEPARATOR);
+            }
+
+            buffer.append(reverse.getName());
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Sets mapped DbRelationships as a dot-separated path.
+     */
+    public void setDbRelationshipPath(String relationshipPath) {
+        if (!Util.nullSafeEquals(getDbRelationshipPath(), relationshipPath)) {
+            refreshFromPath(relationshipPath, false);
+        }
+    }
+
+    /**
+     * Sets relationship path, but does not trigger its conversion to
+     * List<DbRelationship> For internal purposes, primarily datamap loading
+     */
+    void setDeferredDbRelationshipPath(String relationshipPath) {
+        if (!Util.nullSafeEquals(getDbRelationshipPath(), relationshipPath)) {
+            deferredPath = relationshipPath;
+        }
+    }
+
+    /**
+     * Loads path from "deferredPath" variable (if specified)
+     */
+    synchronized void refreshFromDeferredPath() {
+        if (deferredPath != null) {
+            refreshFromPath(deferredPath, true);
+            deferredPath = null;
+        }
+    }
+
+    /**
+     * Returns dot-separated path over DbRelationships, only including
+     * components that have valid DbRelationships.
+     */
+    String getValidRelationshipPath() {
+        String path = getDbRelationshipPath();
+        if (path == null) {
+            return null;
+        }
+
+        ObjEntity entity = (ObjEntity) getSourceEntity();
+        if (entity == null) {
+            throw new CayenneRuntimeException("Can't resolve DbRelationships, null source ObjEntity");
+        }
+
+        DbEntity dbEntity = entity.getDbEntity();
+        if (dbEntity == null) {
+            return null;
+        }
+
+        StringBuilder validPath = new StringBuilder();
+
+        try {
+            for (PathComponent<DbAttribute, DbRelationship> pathComponent : dbEntity.resolvePath(new ASTDbPath(path),
+                    Collections.emptyMap())) {
+
+                if (validPath.length() > 0) {
+                    validPath.append(Entity.PATH_SEPARATOR);
+                }
+                validPath.append(pathComponent.getName());
+            }
+        } catch (ExpressionException ex) {
+
+        }
+
+        return validPath.toString();
+    }
+
+    /**
+     * Rebuild a list of relationships if String relationshipPath has changed.
+     */
+    final void refreshFromPath(String dbRelationshipPath, boolean stripInvalid) {
+        synchronized (this) {
+
+            // remove existing relationships
+            dbRelationships.clear();
+
+            if (dbRelationshipPath != null) {
+
+                ObjEntity entity = (ObjEntity) getSourceEntity();
+                if (entity == null) {
+                    throw new CayenneRuntimeException("Can't resolve DbRelationships, null source ObjEntity");
+                }
+
+                try {
+                    // add new relationships from path
+                    Iterator<CayenneMapEntry> it = entity.resolvePathComponents(new ASTDbPath(dbRelationshipPath));
+
+                    while (it.hasNext()) {
+                        DbRelationship relationship = (DbRelationship) it.next();
+
+                        dbRelationships.add(relationship);
+                    }
+                } catch (ExpressionException ex) {
+                    if (!stripInvalid) {
+                        throw ex;
+                    }
+                }
+            }
+
+            recalculateToManyValue();
+            recalculateReadOnlyValue();
+        }
+    }
+
+    /**
+     * Recalculates whether a relationship is toMany or toOne, based on the
+     * underlying db relationships.
+     */
+    public void recalculateToManyValue() {
+        // If there is a single toMany along the path, then the flattend
+        // rel is toMany. If all are toOne, then the rel is toOne.
+        // Simple (non-flattened) relationships form the degenerate case
+        // taking the value of the single underlying dbrel.
+        for (DbRelationship thisRel : this.dbRelationships) {
+            if (thisRel.isToMany()) {
+                this.toMany = true;
+                return;
+            }
+        }
+
+        this.toMany = false;
+    }
+
+    /**
+     * Recalculates a new readonly value based on the underlying
+     * DbRelationships.
+     */
+    public void recalculateReadOnlyValue() {
+        // not flattened, always read/write
+        if (dbRelationships.size() < 2) {
+            this.readOnly = false;
+            return;
+        }
+
+        // too long, can't handle this yet
+        if (dbRelationships.size() > 2) {
+            this.readOnly = true;
+            return;
+        }
+
+        DbRelationship firstRel = dbRelationships.get(0);
+        DbRelationship secondRel = dbRelationships.get(1);
+
+        // many-to-many with single-step join
+        // also 1..1..1 (CAY-1744) .. TODO all this should go away eventually
+        // per CAY-1743
+        if (!secondRel.isToMany()) {
+
+            // allow modifications if the joins are from FKs
+            if (!secondRel.isToPK()) {
+                this.readOnly = true;
+                return;
+            }
+
+            DbRelationship firstReverseRel = firstRel.getReverseRelationship();
+            if (firstReverseRel == null || !firstReverseRel.isToPK()) {
+                this.readOnly = true;
+                return;
+            }
+
+            this.readOnly = false;
+        } else {
+            readOnly = true;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this).append("name", getName())
+                .append("dbRelationshipPath", getDbRelationshipPath()).toString();
+    }
+
+    /**
+     * Returns an ObjAttribute stripped of any server-side information, such as
+     * DbAttribute mapping.
+     * 
+     * @since 1.2
+     */
+    public ObjRelationship getClientRelationship() {
+        ObjRelationship reverse = getReverseRelationship();
+        String reverseName = reverse != null ? reverse.getName() : null;
+
+        ObjRelationship relationship = new ClientObjRelationship(getName(), reverseName, isToMany(), isReadOnly());
+
+        relationship.setTargetEntityName(getTargetEntityName());
+        relationship.setDeleteRule(getDeleteRule());
+        relationship.setCollectionType(getCollectionType());
+
+        // TODO: copy locking flag...
+
+        return relationship;
+    }
+
+    /**
+     * Returns the interface of collection mapped by a to-many relationship.
+     * Returns null for to-one relationships. Default for to-many is
+     * "java.util.List". Other possible values are "java.util.Set",
+     * "java.util.Collection", "java.util.Map".
+     * 
+     * @since 3.0
+     */
+    public String getCollectionType() {
+        if (collectionType != null) {
+            return collectionType;
+        }
+
+        return isToMany() ? DEFAULT_COLLECTION_TYPE : null;
+    }
+
+    /**
+     * @since 3.0
+     */
+    public void setCollectionType(String collectionType) {
+        this.collectionType = collectionType;
+    }
+
+    /**
+     * Returns a property name of a target entity used to create a relationship
+     * map. Only has effect if collectionType property is set to
+     * "java.util.Map".
+     * 
+     * @return The attribute name used for the map key or <code>null</code> if
+     *         the default (PK) is used as the map key.
+     * @since 3.0
+     */
+    public String getMapKey() {
+        return mapKey;
+    }
+
+    /**
+     * @since 3.0
+     */
+    public void setMapKey(String mapKey) {
+        this.mapKey = mapKey;
+    }
+
+    @Override
+    public boolean isMandatory() {
+        refreshFromDeferredPath();
+        if (dbRelationships.size() == 0) {
+            return false;
+        }
 
-		return dbRelationships.get(0).isMandatory();
-	}
+        return dbRelationships.get(0).isMandatory();
+    }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/SQLResult.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/SQLResult.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/SQLResult.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/SQLResult.java Sat Jun 22 19:36:53 2013
@@ -26,9 +26,9 @@ import java.util.Map;
 import org.apache.cayenne.reflect.ClassDescriptor;
 
 /**
- * A metadata object that defines how a row in a result set can be converted to result
- * objects. SQLResult can be mapped to a single scalar, a single entity or a mix of
- * scalars and entities that is represented as an Object[].
+ * A metadata object that defines how a row in a result set can be converted to
+ * result objects. SQLResult can be mapped to a single scalar, a single entity
+ * or a mix of scalars and entities that is represented as an Object[].
  * 
  * @since 3.0
  */
@@ -55,32 +55,22 @@ public class SQLResult {
         int offset = 0;
         for (Object component : getComponents()) {
             if (component instanceof String) {
-                resolvedComponents.add(new DefaultScalarResultSegment(
-                        (String) component,
-                        offset));
+                resolvedComponents.add(new DefaultScalarResultSegment((String) component, offset));
                 offset = offset + 1;
-            }
-            else if (component instanceof EntityResult) {
+            } else if (component instanceof EntityResult) {
                 EntityResult entityResult = (EntityResult) component;
                 Map<String, String> fields = entityResult.getDbFields(resolver);
 
                 String entityName = entityResult.getEntityName();
                 if (entityName == null) {
-                    entityName = resolver
-                            .lookupObjEntity(entityResult.getEntityClass())
-                            .getName();
+                    entityName = resolver.getObjEntity(entityResult.getEntityClass()).getName();
                 }
 
                 ClassDescriptor classDescriptor = resolver.getClassDescriptor(entityName);
-                resolvedComponents.add(new DefaultEntityResultSegment(
-                        classDescriptor,
-                        fields,
-                        offset));
+                resolvedComponents.add(new DefaultEntityResultSegment(classDescriptor, fields, offset));
                 offset = offset + fields.size();
-            }
-            else {
-                throw new IllegalArgumentException(
-                        "Unsupported result descriptor component: " + component);
+            } else {
+                throw new IllegalArgumentException("Unsupported result descriptor component: " + component);
             }
         }
 
@@ -103,12 +93,13 @@ public class SQLResult {
     }
 
     /**
-     * Returns a list of "uncompiled" result descriptors. Column descriptors are returned
-     * as Strings, entity descriptors - as {@link EntityResult}. To get fully resolved
-     * descriptors, use {@link #getResolvedComponents(EntityResolver)}.
+     * Returns a list of "uncompiled" result descriptors. Column descriptors are
+     * returned as Strings, entity descriptors - as {@link EntityResult}. To get
+     * fully resolved descriptors, use
+     * {@link #getResolvedComponents(EntityResolver)}.
      */
     public List<Object> getComponents() {
-        return resultDescriptors != null ? resultDescriptors : Collections.EMPTY_LIST;
+        return resultDescriptors != null ? resultDescriptors : Collections.emptyList();
     }
 
     public void addEntityResult(EntityResult entityResult) {

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java?rev=1495773&r1=1495772&r2=1495773&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java Sat Jun 22 19:36:53 2013
@@ -98,14 +98,13 @@ class BaseQueryMetadata implements Query
 
             if (root != null) {
                 if (root instanceof Class<?>) {
-                    entity = resolver.lookupObjEntity((Class<?>) root);
-                    if (entity == null) { // entity not found, try to resolve it with
+                    entity = resolver.getObjEntity((Class<?>) root);
+                    if (entity == null) { // entity not found, try to resolve it
+                                          // with
                         // client resolver
-                        EntityResolver clientResolver = resolver
-                                .getClientEntityResolver();
+                        EntityResolver clientResolver = resolver.getClientEntityResolver();
                         if (clientResolver != resolver) {
-                            ObjEntity clientEntity = clientResolver
-                                    .lookupObjEntity((Class<?>) root);
+                            ObjEntity clientEntity = clientResolver.getObjEntity((Class<?>) root);
 
                             if (clientEntity != null) {
                                 entity = resolver.getObjEntity(clientEntity.getName());
@@ -117,27 +116,22 @@ class BaseQueryMetadata implements Query
                         this.dbEntity = entity.getDbEntity();
                         this.dataMap = entity.getDataMap();
                     }
-                }
-                else if (root instanceof ObjEntity) {
+                } else if (root instanceof ObjEntity) {
                     entity = (ObjEntity) root;
                     this.dbEntity = entity.getDbEntity();
                     this.dataMap = entity.getDataMap();
-                }
-                else if (root instanceof String) {
+                } 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) {
+                } else if (root instanceof DbEntity) {
                     this.dbEntity = (DbEntity) root;
                     this.dataMap = dbEntity.getDataMap();
-                }
-                else if (root instanceof DataMap) {
+                } else if (root instanceof DataMap) {
                     this.dataMap = (DataMap) root;
-                }
-                else if (root instanceof Persistent) {
+                } else if (root instanceof Persistent) {
                     entity = resolver.lookupObjEntity(root);
                     if (entity != null) {
                         this.dbEntity = entity.getDbEntity();
@@ -168,44 +162,35 @@ class BaseQueryMetadata implements Query
         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 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.fetchOffset = (fetchOffset != null) ? Integer.parseInt(fetchOffset.toString())
+                : QueryMetadata.FETCH_OFFSET_DEFAULT;
 
-        this.fetchLimit = (fetchLimit != null)
-                ? Integer.parseInt(fetchLimit.toString())
+        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.pageSize = (pageSize != null) ? Integer.parseInt(pageSize.toString()) : QueryMetadata.PAGE_SIZE_DEFAULT;
 
-        this.statementFetchSize = (statementFetchSize != null)
-                ? Integer.parseInt(statementFetchSize.toString())
+        this.statementFetchSize = (statementFetchSize != null) ? Integer.parseInt(statementFetchSize.toString())
                 : QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT;
 
-        this.fetchingDataRows = (fetchingDataRows != null)
-                ? "true".equalsIgnoreCase(fetchingDataRows.toString())
+        this.fetchingDataRows = (fetchingDataRows != null) ? "true".equalsIgnoreCase(fetchingDataRows.toString())
                 : QueryMetadata.FETCHING_DATA_ROWS_DEFAULT;
 
-        this.cacheStrategy = (cacheStrategy != null) ? QueryCacheStrategy
-                .safeValueOf(cacheStrategy.toString()) : QueryCacheStrategy
-                .getDefaultStrategy();
+        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) {
+        } 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++) {
@@ -217,9 +202,7 @@ class BaseQueryMetadata implements Query
     public void encodeAsXML(XMLEncoder encoder) {
 
         if (fetchingDataRows != QueryMetadata.FETCHING_DATA_ROWS_DEFAULT) {
-            encoder.printProperty(
-                    QueryMetadata.FETCHING_DATA_ROWS_PROPERTY,
-                    fetchingDataRows);
+            encoder.printProperty(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY, fetchingDataRows);
         }
 
         if (fetchOffset != QueryMetadata.FETCH_OFFSET_DEFAULT) {
@@ -234,16 +217,12 @@ class BaseQueryMetadata implements Query
             encoder.printProperty(QueryMetadata.PAGE_SIZE_PROPERTY, pageSize);
         }
 
-        if (cacheStrategy != null
-                && QueryCacheStrategy.getDefaultStrategy() != cacheStrategy) {
-            encoder.printProperty(QueryMetadata.CACHE_STRATEGY_PROPERTY, cacheStrategy
-                    .name());
+        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);
+            encoder.printProperty(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY, statementFetchSize);
         }
 
         if (prefetchTree != null) {