cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aadamc...@apache.org
Subject svn commit: r1396712 [1/2] - in /cayenne/main/branches/STABLE-3.1: docs/doc/src/main/resources/ framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ framew...
Date Wed, 10 Oct 2012 18:01:24 GMT
Author: aadamchik
Date: Wed Oct 10 18:01:23 2012
New Revision: 1396712

URL: http://svn.apache.org/viewvc?rev=1396712&view=rev
Log:
CAY-1744 Unexpected read-only relationships in vertical inheritance mapping

relaxing read-only checks... still too crazy and should be removed all together
per CAY 1743
(also saving project in the modeler fixed a few unrelated vinheritance mappings)

Added:
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Root.java
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Sub1.java
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2X.java
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Root.java
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Sub1.java
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2X.java
Modified:
    cayenne/main/branches/STABLE-3.1/docs/doc/src/main/resources/RELEASE-NOTES.txt
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ObjRelationshipTest.java
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java
    cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/resources/inheritance-vertical.map.xml

Modified: cayenne/main/branches/STABLE-3.1/docs/doc/src/main/resources/RELEASE-NOTES.txt
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.1/docs/doc/src/main/resources/RELEASE-NOTES.txt?rev=1396712&r1=1396711&r2=1396712&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.1/docs/doc/src/main/resources/RELEASE-NOTES.txt (original)
+++ cayenne/main/branches/STABLE-3.1/docs/doc/src/main/resources/RELEASE-NOTES.txt Wed Oct 10 18:01:23 2012
@@ -27,6 +27,7 @@ CAY-1735 Serializable IncrementalFaultLi
 CAY-1738 Tutorial cayenne-rop-server should be packaged as a war
 CAY-1739 Cayenne ROP server resets session on every request if BASIC auth is used
 CAY-1741 Serializable DataObjectBaseProperty
+CAY-1744 Unexpected read-only relationships in vertical inheritance mapping
 
 ----------------------------------
 Release: 3.1B1

Modified: cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java?rev=1396712&r1=1396711&r2=1396712&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java (original)
+++ cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/ObjRelationship.java Wed Oct 10 18:01:23 2012
@@ -41,764 +41,781 @@ 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);
-
-        // only support many-to-many with single-step join
-        if (!firstRel.isToMany() || secondRel.isToMany()) {
-            this.readOnly = true;
-            return;
-        }
-
-        DataMap map = firstRel.getTargetEntity().getDataMap();
-        if (map == null) {
-            throw new CayenneRuntimeException(this.getClass().getName()
-                    + " could not obtain a DataMap for the destination of "
-                    + firstRel.getName());
-        }
-
-        // 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;
-    }
-
-    @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
+				.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;
+		}
 
-        return dbRelationships.get(0).isMandatory();
-    }
+		return dbRelationships.get(0).isMandatory();
+	}
 }

Modified: cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ObjRelationshipTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ObjRelationshipTest.java?rev=1396712&r1=1396711&r2=1396712&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ObjRelationshipTest.java (original)
+++ cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/map/ObjRelationshipTest.java Wed Oct 10 18:01:23 2012
@@ -21,12 +21,17 @@ package org.apache.cayenne.map;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.net.URL;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.configuration.DataMapLoader;
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.exp.ExpressionException;
+import org.apache.cayenne.resource.URLResource;
+import org.apache.cayenne.testdo.inheritance.vertical.Iv2Sub1;
 import org.apache.cayenne.unit.di.server.ServerCase;
 import org.apache.cayenne.unit.di.server.UseServerRuntime;
 import org.apache.cayenne.util.Util;
@@ -35,466 +40,476 @@ import org.apache.cayenne.util.XMLEncode
 @UseServerRuntime(ServerCase.TESTMAP_PROJECT)
 public class ObjRelationshipTest extends ServerCase {
 
-    @Inject
-    private ServerRuntime runtime;
+	@Inject
+	private ServerRuntime runtime;
 
-    private DbEntity artistDBEntity;
-    private DbEntity artistExhibitDBEntity;
-    private DbEntity exhibitDBEntity;
-    private DbEntity paintingDbEntity;
-    private DbEntity galleryDBEntity;
-
-    @Override
-    protected void setUpAfterInjection() throws Exception {
-        EntityResolver resolver = runtime.getDataDomain().getEntityResolver();
-
-        artistDBEntity = resolver.getDbEntity("ARTIST");
-        artistExhibitDBEntity = resolver.getDbEntity("ARTIST_EXHIBIT");
-        exhibitDBEntity = resolver.getDbEntity("EXHIBIT");
-        paintingDbEntity = resolver.getDbEntity("PAINTING");
-        galleryDBEntity = resolver.getDbEntity("GALLERY");
-    }
-
-    public void testEncodeAsXML() {
-        StringWriter buffer = new StringWriter();
-        PrintWriter out = new PrintWriter(buffer);
-        XMLEncoder encoder = new XMLEncoder(out);
-
-        DataMap map = new DataMap("M");
-        ObjEntity source = new ObjEntity("S");
-        ObjEntity target = new ObjEntity("T");
-        map.addObjEntity(source);
-        map.addObjEntity(target);
-
-        ObjRelationship r = new ObjRelationship("X");
-        r.setSourceEntity(source);
-        r.setTargetEntityName("T");
-        r.setCollectionType("java.util.Map");
-        r.setMapKey("bla");
-
-        r.encodeAsXML(encoder);
-        out.close();
-
-        String lineBreak = System.getProperty("line.separator");
-
-        assertEquals("<obj-relationship name=\"X\" source=\"S\" target=\"T\" "
-                + "collection-type=\"java.util.Map\" map-key=\"bla\"/>"
-                + lineBreak, buffer.getBuffer().toString());
-    }
-
-    public void testCollectionType() {
-        ObjRelationship r = new ObjRelationship("X");
-        assertNull(r.getCollectionType());
-        r.setCollectionType("java.util.Map");
-        assertEquals("java.util.Map", r.getCollectionType());
-    }
-
-    public void testSerializability() throws Exception {
-        ObjEntity artistObjEnt = runtime
-                .getDataDomain()
-                .getEntityResolver()
-                .getObjEntity("Artist");
-
-        // start with "to many"
-        ObjRelationship r1 = (ObjRelationship) artistObjEnt
-                .getRelationship("paintingArray");
-
-        ObjRelationship r2 = Util.cloneViaSerialization(r1);
-        assertEquals(r1.getName(), r2.getName());
-        assertEquals(r1.getDbRelationshipPath(), r2.getDbRelationshipPath());
-    }
-
-    public void testGetClientRelationship() {
-        final ObjEntity target = new ObjEntity("te1");
-        ObjRelationship r1 = new ObjRelationship("r1") {
-
-            @Override
-            public Entity getTargetEntity() {
-                return target;
-            }
-        };
-
-        r1.setDeleteRule(DeleteRule.DENY);
-        r1.setTargetEntityName("te1");
-
-        ObjRelationship r2 = r1.getClientRelationship();
-        assertNotNull(r2);
-        assertEquals(r1.getName(), r2.getName());
-        assertEquals(r1.getTargetEntityName(), r2.getTargetEntityName());
-        assertEquals(r1.getDeleteRule(), r2.getDeleteRule());
-    }
-
-    public void testGetReverseDbRelationshipPath() {
-        ObjEntity artistObjEnt = runtime
-                .getDataDomain()
-                .getEntityResolver()
-                .getObjEntity("Artist");
-        ObjEntity paintingObjEnt = runtime
-                .getDataDomain()
-                .getEntityResolver()
-                .getObjEntity("Painting");
-
-        // start with "to many"
-        ObjRelationship r1 = (ObjRelationship) artistObjEnt
-                .getRelationship("paintingArray");
-
-        assertEquals("toArtist", r1.getReverseDbRelationshipPath());
-
-        ObjRelationship r2 = (ObjRelationship) paintingObjEnt.getRelationship("toArtist");
-
-        assertEquals("paintingArray", r2.getReverseDbRelationshipPath());
-    }
-
-    public void testSetDbRelationshipPath() {
-        ObjEntity artistObjEnt = runtime
-                .getDataDomain()
-                .getEntityResolver()
-                .getObjEntity("Artist");
-
-        ObjRelationship r = new ObjRelationship("r");
-        r.setSourceEntity(artistObjEnt);
-        r.setDbRelationshipPath("paintingArray");
-        assertEquals(r.getDbRelationshipPath(), "paintingArray");
-    }
-
-    public void testRefreshFromPath() {
-        ObjRelationship relationship = new ObjRelationship();
-
-        // attempt to resolve must fail - relationship is outside of context,
-        // plus the path is random
-        try {
-            relationship.setDbRelationshipPath("dummy.path");
-            fail("set random path should have failed.");
-        }
-        catch (CayenneRuntimeException ex) {
-            // expected
-        }
-
-        DataMap map = new DataMap();
-        ObjEntity entity = new ObjEntity("Test");
-        map.addObjEntity(entity);
-
-        relationship.setSourceEntity(entity);
-        // attempt to resolve must fail - relationship is outside of context,
-        // plus the path is random
-        try {
-            relationship.refreshFromPath("dummy.path", false);
-            fail("refresh over a dummy path should have failed.");
-        }
-        catch (ExpressionException ex) {
-            // expected
-        }
-
-        // finally assemble ObjEntity to make the path valid
-        DbEntity dbEntity1 = new DbEntity("TEST1");
-        DbEntity dbEntity2 = new DbEntity("TEST2");
-        DbEntity dbEntity3 = new DbEntity("TEST3");
-        map.addDbEntity(dbEntity1);
-        map.addDbEntity(dbEntity2);
-        map.addDbEntity(dbEntity3);
-        entity.setDbEntityName("TEST1");
-        DbRelationship dummyR = new DbRelationship("dummy");
-        dummyR.setTargetEntityName("TEST2");
-        dummyR.setSourceEntity(dbEntity1);
-        DbRelationship pathR = new DbRelationship("path");
-        pathR.setTargetEntityName("TEST3");
-        pathR.setSourceEntity(dbEntity2);
-        dbEntity1.addRelationship(dummyR);
-        dbEntity2.addRelationship(pathR);
-
-        relationship.refreshFromPath("dummy.path", false);
-
-        List<DbRelationship> resolvedPath = relationship.getDbRelationships();
-        assertEquals(2, resolvedPath.size());
-        assertSame(dummyR, resolvedPath.get(0));
-        assertSame(pathR, resolvedPath.get(1));
-    }
-
-    public void testCalculateToMany() {
-        // assemble fixture....
-        DataMap map = new DataMap();
-        ObjEntity entity = new ObjEntity("Test");
-        map.addObjEntity(entity);
-
-        DbEntity dbEntity1 = new DbEntity("TEST1");
-        DbEntity dbEntity2 = new DbEntity("TEST2");
-        DbEntity dbEntity3 = new DbEntity("TEST3");
-        map.addDbEntity(dbEntity1);
-        map.addDbEntity(dbEntity2);
-        map.addDbEntity(dbEntity3);
-        entity.setDbEntityName("TEST1");
-        DbRelationship dummyR = new DbRelationship("dummy");
-        dummyR.setTargetEntityName("TEST2");
-        dummyR.setSourceEntity(dbEntity1);
-        DbRelationship pathR = new DbRelationship("path");
-        pathR.setTargetEntityName("TEST3");
-        pathR.setSourceEntity(dbEntity2);
-        dbEntity1.addRelationship(dummyR);
-        dbEntity2.addRelationship(pathR);
-
-        ObjRelationship relationship = new ObjRelationship();
-        relationship.setSourceEntity(entity);
-
-        // test how toMany changes dependending on the underlying DbRelationships
-        // add DbRelationships directly to avoid events to test "calculateToMany"
-        relationship.dbRelationships.add(dummyR);
-        assertFalse(relationship.isToMany());
-
-        dummyR.setToMany(true);
-        relationship.recalculateToManyValue();
-        assertTrue(relationship.isToMany());
-
-        dummyR.setToMany(false);
-        relationship.recalculateToManyValue();
-        assertFalse(relationship.isToMany());
-
-        // test chain
-        relationship.dbRelationships.add(pathR);
-        assertFalse(relationship.isToMany());
-
-        pathR.setToMany(true);
-        relationship.recalculateToManyValue();
-        assertTrue(relationship.isToMany());
-    }
-
-    public void testCalculateToManyFromPath() {
-        // assemble fixture....
-        DataMap map = new DataMap();
-        ObjEntity entity = new ObjEntity("Test");
-        map.addObjEntity(entity);
-
-        DbEntity dbEntity1 = new DbEntity("TEST1");
-        DbEntity dbEntity2 = new DbEntity("TEST2");
-        DbEntity dbEntity3 = new DbEntity("TEST3");
-        map.addDbEntity(dbEntity1);
-        map.addDbEntity(dbEntity2);
-        map.addDbEntity(dbEntity3);
-        entity.setDbEntityName("TEST1");
-        DbRelationship dummyR = new DbRelationship("dummy");
-        dummyR.setTargetEntityName("TEST2");
-        dummyR.setSourceEntity(dbEntity1);
-        DbRelationship pathR = new DbRelationship("path");
-        pathR.setTargetEntityName("TEST3");
-        pathR.setSourceEntity(dbEntity2);
-        dbEntity1.addRelationship(dummyR);
-        dbEntity2.addRelationship(pathR);
-
-        ObjRelationship relationship = new ObjRelationship();
-        relationship.setSourceEntity(entity);
-
-        // test how toMany changes when the path is set as a string
-
-        relationship.setDbRelationshipPath("dummy");
-        assertFalse(relationship.isToMany());
-
-        dummyR.setToMany(true);
-        relationship.setDbRelationshipPath(null);
-        relationship.setDbRelationshipPath("dummy");
-        assertTrue(relationship.isToMany());
-
-        dummyR.setToMany(false);
-        relationship.setDbRelationshipPath(null);
-        relationship.setDbRelationshipPath("dummy");
-        assertFalse(relationship.isToMany());
-
-        // test chain
-        relationship.setDbRelationshipPath(null);
-        relationship.setDbRelationshipPath("dummy.path");
-        assertFalse(relationship.isToMany());
-
-        pathR.setToMany(true);
-        relationship.setDbRelationshipPath(null);
-        relationship.setDbRelationshipPath("dummy.path");
-        assertTrue(relationship.isToMany());
-    }
-
-    public void testTargetEntity() throws Exception {
-        ObjRelationship relationship = new ObjRelationship("some_rel");
-        relationship.setTargetEntityName("targ");
-
-        try {
-            relationship.getTargetEntity();
-            fail("Without a container, getTargetEntity() must fail.");
-        }
-        catch (CayenneRuntimeException ex) {
-            // expected
-        }
-
-        // assemble container
-        DataMap map = new DataMap();
-        ObjEntity src = new ObjEntity("src");
-        map.addObjEntity(src);
-
-        src.addRelationship(relationship);
-        assertNull(relationship.getTargetEntity());
-
-        ObjEntity target = new ObjEntity("targ");
-        map.addObjEntity(target);
-
-        assertSame(target, relationship.getTargetEntity());
-    }
-
-    public void testGetReverseRel1() {
-
-        ObjEntity artistObjEnt = runtime
-                .getDataDomain()
-                .getEntityResolver()
-                .getObjEntity("Artist");
-        ObjEntity paintingObjEnt = runtime
-                .getDataDomain()
-                .getEntityResolver()
-                .getObjEntity("Painting");
-
-        // start with "to many"
-        ObjRelationship r1 = (ObjRelationship) artistObjEnt
-                .getRelationship("paintingArray");
-        ObjRelationship r2 = r1.getReverseRelationship();
-
-        assertNotNull(r2);
-        assertSame(paintingObjEnt.getRelationship("toArtist"), r2);
-    }
-
-    public void testGetReverseRel2() {
-        ObjEntity artistEnt = runtime.getDataDomain().getEntityResolver().getObjEntity(
-                "Artist");
-        ObjEntity paintingEnt = runtime.getDataDomain().getEntityResolver().getObjEntity(
-                "Painting");
-
-        // start with "to one"
-        ObjRelationship r1 = (ObjRelationship) paintingEnt.getRelationship("toArtist");
-        ObjRelationship r2 = r1.getReverseRelationship();
-
-        assertNotNull(r2);
-        assertSame(artistEnt.getRelationship("paintingArray"), r2);
-    }
-
-    public void testSingleDbRelationship() {
-        ObjRelationship relationship = new ObjRelationship();
-        DbRelationship r1 = new DbRelationship("X");
-        relationship.addDbRelationship(r1);
-        assertEquals(1, relationship.getDbRelationships().size());
-        assertEquals(r1, relationship.getDbRelationships().get(0));
-        assertFalse(relationship.isFlattened());
-        assertFalse(relationship.isReadOnly());
-        assertEquals(r1.isToMany(), relationship.isToMany());
-        relationship.removeDbRelationship(r1);
-        assertEquals(0, relationship.getDbRelationships().size());
-    }
-
-    public void testFlattenedRelationship() {
-        DbRelationship r1 = new DbRelationship("X");
-        DbRelationship r2 = new DbRelationship("Y");
-
-        r1.setSourceEntity(artistDBEntity);
-        r1.setTargetEntity(artistExhibitDBEntity);
-        r1.setToMany(true);
-
-        r2.setSourceEntity(artistExhibitDBEntity);
-        r2.setTargetEntity(exhibitDBEntity);
-        r2.setToMany(false);
-
-        ObjRelationship relationship = new ObjRelationship();
-        relationship.addDbRelationship(r1);
-        relationship.addDbRelationship(r2);
-        assertTrue(relationship.isToMany());
-        assertEquals(2, relationship.getDbRelationships().size());
-        assertEquals(r1, relationship.getDbRelationships().get(0));
-        assertEquals(r2, relationship.getDbRelationships().get(1));
-
-        assertTrue(relationship.isFlattened());
-
-        relationship.removeDbRelationship(r1);
-        assertFalse(relationship.isToMany()); // only remaining rel is r2... a toOne
-        assertEquals(1, relationship.getDbRelationships().size());
-        assertEquals(r2, relationship.getDbRelationships().get(0));
-        assertFalse(relationship.isFlattened());
-        assertFalse(relationship.isReadOnly());
-
-    }
-
-    public void testReadOnlyMoreThan3DbRelsRelationship() {
-        // Readonly is a flattened relationship that isn't over a single many->many link
-        // table
-        DbRelationship r1 = new DbRelationship("X");
-        DbRelationship r2 = new DbRelationship("Y");
-        DbRelationship r3 = new DbRelationship("Z");
-
-        r1.setSourceEntity(artistDBEntity);
-        r1.setTargetEntity(artistExhibitDBEntity);
-        r1.setToMany(true);
-        r2.setSourceEntity(artistExhibitDBEntity);
-        r2.setTargetEntity(exhibitDBEntity);
-        r2.setToMany(false);
-        r3.setSourceEntity(exhibitDBEntity);
-        r3.setTargetEntity(galleryDBEntity);
-        r3.setToMany(false);
-
-        ObjRelationship relationship = new ObjRelationship();
-        relationship.addDbRelationship(r1);
-        relationship.addDbRelationship(r2);
-        relationship.addDbRelationship(r3);
-
-        assertTrue(relationship.isFlattened());
-        assertTrue(relationship.isReadOnly());
-        assertTrue(relationship.isToMany());
-
-    }
-
-    // Test for a read-only flattened relationship that is readonly because it's dbrel
-    // sequence is "incorrect" (or rather, unsupported)
-    public void testIncorrectSequenceReadOnlyRelationship() {
-        DbRelationship r1 = new DbRelationship("X");
-        DbRelationship r2 = new DbRelationship("Y");
-
-        r1.setSourceEntity(artistDBEntity);
-        r1.setTargetEntity(paintingDbEntity);
-        r1.setToMany(true);
-        r2.setSourceEntity(paintingDbEntity);
-        r2.setTargetEntity(galleryDBEntity);
-        r2.setToMany(false);
-
-        ObjRelationship relationship = new ObjRelationship();
-        relationship.addDbRelationship(r1);
-        relationship.addDbRelationship(r2);
-
-        assertTrue(relationship.isFlattened());
-        assertTrue(relationship.isReadOnly());
-        assertTrue(relationship.isToMany());
-    }
-
-    // Test a relationship loaded from the test datamap that we know should be flattened
-    public void testKnownFlattenedRelationship() {
-        ObjEntity artistEnt = runtime.getDataDomain().getEntityResolver().getObjEntity(
-                "Artist");
-        ObjRelationship theRel = (ObjRelationship) artistEnt
-                .getRelationship("groupArray");
-        assertNotNull(theRel);
-        assertTrue(theRel.isFlattened());
-        assertFalse(theRel.isReadOnly());
-    }
-
-    public void testBadDeleteRuleValue() {
-        ObjRelationship relationship = new ObjRelationship();
-
-        try {
-            relationship.setDeleteRule(999);
-            fail("Should have failed with IllegalArgumentException");
-        }
-        catch (IllegalArgumentException e) {
-            // Good... it should throw an exception
-        }
-    }
-
-    public void testOkDeleteRuleValue() {
-        ObjRelationship relationship = new ObjRelationship();
-        try {
-            relationship.setDeleteRule(DeleteRule.CASCADE);
-            relationship.setDeleteRule(DeleteRule.DENY);
-            relationship.setDeleteRule(DeleteRule.NULLIFY);
-        }
-        catch (IllegalArgumentException e) {
-            e.printStackTrace();
-            fail("Should not have thrown an exception :" + e.getMessage());
-        }
-    }
+	private DbEntity artistDBEntity;
+	private DbEntity artistExhibitDBEntity;
+	private DbEntity exhibitDBEntity;
+	private DbEntity paintingDbEntity;
+	private DbEntity galleryDBEntity;
+
+	@Override
+	protected void setUpAfterInjection() throws Exception {
+		EntityResolver resolver = runtime.getDataDomain().getEntityResolver();
+
+		artistDBEntity = resolver.getDbEntity("ARTIST");
+		artistExhibitDBEntity = resolver.getDbEntity("ARTIST_EXHIBIT");
+		exhibitDBEntity = resolver.getDbEntity("EXHIBIT");
+		paintingDbEntity = resolver.getDbEntity("PAINTING");
+		galleryDBEntity = resolver.getDbEntity("GALLERY");
+	}
+
+	public void testEncodeAsXML() {
+		StringWriter buffer = new StringWriter();
+		PrintWriter out = new PrintWriter(buffer);
+		XMLEncoder encoder = new XMLEncoder(out);
+
+		DataMap map = new DataMap("M");
+		ObjEntity source = new ObjEntity("S");
+		ObjEntity target = new ObjEntity("T");
+		map.addObjEntity(source);
+		map.addObjEntity(target);
+
+		ObjRelationship r = new ObjRelationship("X");
+		r.setSourceEntity(source);
+		r.setTargetEntityName("T");
+		r.setCollectionType("java.util.Map");
+		r.setMapKey("bla");
+
+		r.encodeAsXML(encoder);
+		out.close();
+
+		String lineBreak = System.getProperty("line.separator");
+
+		assertEquals("<obj-relationship name=\"X\" source=\"S\" target=\"T\" "
+				+ "collection-type=\"java.util.Map\" map-key=\"bla\"/>"
+				+ lineBreak, buffer.getBuffer().toString());
+	}
+
+	public void testCollectionType() {
+		ObjRelationship r = new ObjRelationship("X");
+		assertNull(r.getCollectionType());
+		r.setCollectionType("java.util.Map");
+		assertEquals("java.util.Map", r.getCollectionType());
+	}
+
+	public void testSerializability() throws Exception {
+		ObjEntity artistObjEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Artist");
+
+		// start with "to many"
+		ObjRelationship r1 = (ObjRelationship) artistObjEnt
+				.getRelationship("paintingArray");
+
+		ObjRelationship r2 = Util.cloneViaSerialization(r1);
+		assertEquals(r1.getName(), r2.getName());
+		assertEquals(r1.getDbRelationshipPath(), r2.getDbRelationshipPath());
+	}
+
+	public void testGetClientRelationship() {
+		final ObjEntity target = new ObjEntity("te1");
+		ObjRelationship r1 = new ObjRelationship("r1") {
+
+			@Override
+			public Entity getTargetEntity() {
+				return target;
+			}
+		};
+
+		r1.setDeleteRule(DeleteRule.DENY);
+		r1.setTargetEntityName("te1");
+
+		ObjRelationship r2 = r1.getClientRelationship();
+		assertNotNull(r2);
+		assertEquals(r1.getName(), r2.getName());
+		assertEquals(r1.getTargetEntityName(), r2.getTargetEntityName());
+		assertEquals(r1.getDeleteRule(), r2.getDeleteRule());
+	}
+
+	public void testGetReverseDbRelationshipPath() {
+		ObjEntity artistObjEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Artist");
+		ObjEntity paintingObjEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Painting");
+
+		// start with "to many"
+		ObjRelationship r1 = (ObjRelationship) artistObjEnt
+				.getRelationship("paintingArray");
+
+		assertEquals("toArtist", r1.getReverseDbRelationshipPath());
+
+		ObjRelationship r2 = (ObjRelationship) paintingObjEnt
+				.getRelationship("toArtist");
+
+		assertEquals("paintingArray", r2.getReverseDbRelationshipPath());
+	}
+
+	public void testSetDbRelationshipPath() {
+		ObjEntity artistObjEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Artist");
+
+		ObjRelationship r = new ObjRelationship("r");
+		r.setSourceEntity(artistObjEnt);
+		r.setDbRelationshipPath("paintingArray");
+		assertEquals(r.getDbRelationshipPath(), "paintingArray");
+	}
+
+	public void testRefreshFromPath() {
+		ObjRelationship relationship = new ObjRelationship();
+
+		// attempt to resolve must fail - relationship is outside of context,
+		// plus the path is random
+		try {
+			relationship.setDbRelationshipPath("dummy.path");
+			fail("set random path should have failed.");
+		} catch (CayenneRuntimeException ex) {
+			// expected
+		}
+
+		DataMap map = new DataMap();
+		ObjEntity entity = new ObjEntity("Test");
+		map.addObjEntity(entity);
+
+		relationship.setSourceEntity(entity);
+		// attempt to resolve must fail - relationship is outside of context,
+		// plus the path is random
+		try {
+			relationship.refreshFromPath("dummy.path", false);
+			fail("refresh over a dummy path should have failed.");
+		} catch (ExpressionException ex) {
+			// expected
+		}
+
+		// finally assemble ObjEntity to make the path valid
+		DbEntity dbEntity1 = new DbEntity("TEST1");
+		DbEntity dbEntity2 = new DbEntity("TEST2");
+		DbEntity dbEntity3 = new DbEntity("TEST3");
+		map.addDbEntity(dbEntity1);
+		map.addDbEntity(dbEntity2);
+		map.addDbEntity(dbEntity3);
+		entity.setDbEntityName("TEST1");
+		DbRelationship dummyR = new DbRelationship("dummy");
+		dummyR.setTargetEntityName("TEST2");
+		dummyR.setSourceEntity(dbEntity1);
+		DbRelationship pathR = new DbRelationship("path");
+		pathR.setTargetEntityName("TEST3");
+		pathR.setSourceEntity(dbEntity2);
+		dbEntity1.addRelationship(dummyR);
+		dbEntity2.addRelationship(pathR);
+
+		relationship.refreshFromPath("dummy.path", false);
+
+		List<DbRelationship> resolvedPath = relationship.getDbRelationships();
+		assertEquals(2, resolvedPath.size());
+		assertSame(dummyR, resolvedPath.get(0));
+		assertSame(pathR, resolvedPath.get(1));
+	}
+
+	public void testCalculateToMany() {
+		// assemble fixture....
+		DataMap map = new DataMap();
+		ObjEntity entity = new ObjEntity("Test");
+		map.addObjEntity(entity);
+
+		DbEntity dbEntity1 = new DbEntity("TEST1");
+		DbEntity dbEntity2 = new DbEntity("TEST2");
+		DbEntity dbEntity3 = new DbEntity("TEST3");
+		map.addDbEntity(dbEntity1);
+		map.addDbEntity(dbEntity2);
+		map.addDbEntity(dbEntity3);
+		entity.setDbEntityName("TEST1");
+		DbRelationship dummyR = new DbRelationship("dummy");
+		dummyR.setTargetEntityName("TEST2");
+		dummyR.setSourceEntity(dbEntity1);
+		DbRelationship pathR = new DbRelationship("path");
+		pathR.setTargetEntityName("TEST3");
+		pathR.setSourceEntity(dbEntity2);
+		dbEntity1.addRelationship(dummyR);
+		dbEntity2.addRelationship(pathR);
+
+		ObjRelationship relationship = new ObjRelationship();
+		relationship.setSourceEntity(entity);
+
+		// test how toMany changes dependending on the underlying
+		// DbRelationships
+		// add DbRelationships directly to avoid events to test
+		// "calculateToMany"
+		relationship.dbRelationships.add(dummyR);
+		assertFalse(relationship.isToMany());
+
+		dummyR.setToMany(true);
+		relationship.recalculateToManyValue();
+		assertTrue(relationship.isToMany());
+
+		dummyR.setToMany(false);
+		relationship.recalculateToManyValue();
+		assertFalse(relationship.isToMany());
+
+		// test chain
+		relationship.dbRelationships.add(pathR);
+		assertFalse(relationship.isToMany());
+
+		pathR.setToMany(true);
+		relationship.recalculateToManyValue();
+		assertTrue(relationship.isToMany());
+	}
+
+	public void testCalculateToManyFromPath() {
+		// assemble fixture....
+		DataMap map = new DataMap();
+		ObjEntity entity = new ObjEntity("Test");
+		map.addObjEntity(entity);
+
+		DbEntity dbEntity1 = new DbEntity("TEST1");
+		DbEntity dbEntity2 = new DbEntity("TEST2");
+		DbEntity dbEntity3 = new DbEntity("TEST3");
+		map.addDbEntity(dbEntity1);
+		map.addDbEntity(dbEntity2);
+		map.addDbEntity(dbEntity3);
+		entity.setDbEntityName("TEST1");
+		DbRelationship dummyR = new DbRelationship("dummy");
+		dummyR.setTargetEntityName("TEST2");
+		dummyR.setSourceEntity(dbEntity1);
+		DbRelationship pathR = new DbRelationship("path");
+		pathR.setTargetEntityName("TEST3");
+		pathR.setSourceEntity(dbEntity2);
+		dbEntity1.addRelationship(dummyR);
+		dbEntity2.addRelationship(pathR);
+
+		ObjRelationship relationship = new ObjRelationship();
+		relationship.setSourceEntity(entity);
+
+		// test how toMany changes when the path is set as a string
+
+		relationship.setDbRelationshipPath("dummy");
+		assertFalse(relationship.isToMany());
+
+		dummyR.setToMany(true);
+		relationship.setDbRelationshipPath(null);
+		relationship.setDbRelationshipPath("dummy");
+		assertTrue(relationship.isToMany());
+
+		dummyR.setToMany(false);
+		relationship.setDbRelationshipPath(null);
+		relationship.setDbRelationshipPath("dummy");
+		assertFalse(relationship.isToMany());
+
+		// test chain
+		relationship.setDbRelationshipPath(null);
+		relationship.setDbRelationshipPath("dummy.path");
+		assertFalse(relationship.isToMany());
+
+		pathR.setToMany(true);
+		relationship.setDbRelationshipPath(null);
+		relationship.setDbRelationshipPath("dummy.path");
+		assertTrue(relationship.isToMany());
+	}
+
+	public void testTargetEntity() throws Exception {
+		ObjRelationship relationship = new ObjRelationship("some_rel");
+		relationship.setTargetEntityName("targ");
+
+		try {
+			relationship.getTargetEntity();
+			fail("Without a container, getTargetEntity() must fail.");
+		} catch (CayenneRuntimeException ex) {
+			// expected
+		}
+
+		// assemble container
+		DataMap map = new DataMap();
+		ObjEntity src = new ObjEntity("src");
+		map.addObjEntity(src);
+
+		src.addRelationship(relationship);
+		assertNull(relationship.getTargetEntity());
+
+		ObjEntity target = new ObjEntity("targ");
+		map.addObjEntity(target);
+
+		assertSame(target, relationship.getTargetEntity());
+	}
+
+	public void testGetReverseRel1() {
+
+		ObjEntity artistObjEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Artist");
+		ObjEntity paintingObjEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Painting");
+
+		// start with "to many"
+		ObjRelationship r1 = (ObjRelationship) artistObjEnt
+				.getRelationship("paintingArray");
+		ObjRelationship r2 = r1.getReverseRelationship();
+
+		assertNotNull(r2);
+		assertSame(paintingObjEnt.getRelationship("toArtist"), r2);
+	}
+
+	public void testGetReverseRel2() {
+		ObjEntity artistEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Artist");
+		ObjEntity paintingEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Painting");
+
+		// start with "to one"
+		ObjRelationship r1 = (ObjRelationship) paintingEnt
+				.getRelationship("toArtist");
+		ObjRelationship r2 = r1.getReverseRelationship();
+
+		assertNotNull(r2);
+		assertSame(artistEnt.getRelationship("paintingArray"), r2);
+	}
+
+	public void testSingleDbRelationship() {
+		ObjRelationship relationship = new ObjRelationship();
+		DbRelationship r1 = new DbRelationship("X");
+		relationship.addDbRelationship(r1);
+		assertEquals(1, relationship.getDbRelationships().size());
+		assertEquals(r1, relationship.getDbRelationships().get(0));
+		assertFalse(relationship.isFlattened());
+		assertFalse(relationship.isReadOnly());
+		assertEquals(r1.isToMany(), relationship.isToMany());
+		relationship.removeDbRelationship(r1);
+		assertEquals(0, relationship.getDbRelationships().size());
+	}
+
+	public void testFlattenedRelationship() {
+		DbRelationship r1 = new DbRelationship("X");
+		DbRelationship r2 = new DbRelationship("Y");
+
+		r1.setSourceEntity(artistDBEntity);
+		r1.setTargetEntity(artistExhibitDBEntity);
+		r1.setToMany(true);
+
+		r2.setSourceEntity(artistExhibitDBEntity);
+		r2.setTargetEntity(exhibitDBEntity);
+		r2.setToMany(false);
+
+		ObjRelationship relationship = new ObjRelationship();
+		relationship.addDbRelationship(r1);
+		relationship.addDbRelationship(r2);
+		assertTrue(relationship.isToMany());
+		assertEquals(2, relationship.getDbRelationships().size());
+		assertEquals(r1, relationship.getDbRelationships().get(0));
+		assertEquals(r2, relationship.getDbRelationships().get(1));
+
+		assertTrue(relationship.isFlattened());
+
+		relationship.removeDbRelationship(r1);
+		assertFalse(relationship.isToMany()); // only remaining rel is r2... a
+												// toOne
+		assertEquals(1, relationship.getDbRelationships().size());
+		assertEquals(r2, relationship.getDbRelationships().get(0));
+		assertFalse(relationship.isFlattened());
+		assertFalse(relationship.isReadOnly());
+
+	}
+
+	public void testReadOnly_Flattened1_1__N_1() {
+
+		// check common vertical inheritance relationships
+
+		DataMapLoader loader = runtime.getInjector().getInstance(
+				DataMapLoader.class);
+		URL url = getClass().getClassLoader().getResource(
+				"inheritance-vertical.map.xml");
+		DataMap dataMap = loader.load(new URLResource(url));
+		EntityResolver resolver = new EntityResolver(
+				Collections.singleton(dataMap));
+
+		ObjEntity iv2Sub1 = resolver.lookupObjEntity(Iv2Sub1.class);
+		ObjRelationship x = (ObjRelationship) iv2Sub1
+				.getRelationship(Iv2Sub1.X_PROPERTY);
+		assertTrue(x.isFlattened());
+		assertFalse(x.isReadOnly());
+	}
+
+	public void testReadOnlyMoreThan3DbRelsRelationship() {
+		// Readonly is a flattened relationship that isn't over a single
+		// many->many link
+		// table
+		DbRelationship r1 = new DbRelationship("X");
+		DbRelationship r2 = new DbRelationship("Y");
+		DbRelationship r3 = new DbRelationship("Z");
+
+		r1.setSourceEntity(artistDBEntity);
+		r1.setTargetEntity(artistExhibitDBEntity);
+		r1.setToMany(true);
+		r2.setSourceEntity(artistExhibitDBEntity);
+		r2.setTargetEntity(exhibitDBEntity);
+		r2.setToMany(false);
+		r3.setSourceEntity(exhibitDBEntity);
+		r3.setTargetEntity(galleryDBEntity);
+		r3.setToMany(false);
+
+		ObjRelationship relationship = new ObjRelationship();
+		relationship.addDbRelationship(r1);
+		relationship.addDbRelationship(r2);
+		relationship.addDbRelationship(r3);
+
+		assertTrue(relationship.isFlattened());
+		assertTrue(relationship.isReadOnly());
+		assertTrue(relationship.isToMany());
+
+	}
+
+	// Test for a read-only flattened relationship that is readonly because it's
+	// dbrel
+	// sequence is "incorrect" (or rather, unsupported)
+	public void testIncorrectSequenceReadOnlyRelationship() {
+		DbRelationship r1 = new DbRelationship("X");
+		DbRelationship r2 = new DbRelationship("Y");
+
+		r1.setSourceEntity(artistDBEntity);
+		r1.setTargetEntity(paintingDbEntity);
+		r1.setToMany(true);
+		r2.setSourceEntity(paintingDbEntity);
+		r2.setTargetEntity(galleryDBEntity);
+		r2.setToMany(false);
+
+		ObjRelationship relationship = new ObjRelationship();
+		relationship.addDbRelationship(r1);
+		relationship.addDbRelationship(r2);
+
+		assertTrue(relationship.isFlattened());
+		assertTrue(relationship.isReadOnly());
+		assertTrue(relationship.isToMany());
+	}
+
+	// Test a relationship loaded from the test datamap that we know should be
+	// flattened
+	public void testKnownFlattenedRelationship() {
+		ObjEntity artistEnt = runtime.getDataDomain().getEntityResolver()
+				.getObjEntity("Artist");
+		ObjRelationship theRel = (ObjRelationship) artistEnt
+				.getRelationship("groupArray");
+		assertNotNull(theRel);
+		assertTrue(theRel.isFlattened());
+		assertFalse(theRel.isReadOnly());
+	}
+
+	public void testBadDeleteRuleValue() {
+		ObjRelationship relationship = new ObjRelationship();
+
+		try {
+			relationship.setDeleteRule(999);
+			fail("Should have failed with IllegalArgumentException");
+		} catch (IllegalArgumentException e) {
+			// Good... it should throw an exception
+		}
+	}
+
+	public void testOkDeleteRuleValue() {
+		ObjRelationship relationship = new ObjRelationship();
+		try {
+			relationship.setDeleteRule(DeleteRule.CASCADE);
+			relationship.setDeleteRule(DeleteRule.DENY);
+			relationship.setDeleteRule(DeleteRule.NULLIFY);
+		} catch (IllegalArgumentException e) {
+			e.printStackTrace();
+			fail("Should not have thrown an exception :" + e.getMessage());
+		}
+	}
 }

Added: cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Root.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Root.java?rev=1396712&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Root.java (added)
+++ cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Root.java Wed Oct 10 18:01:23 2012
@@ -0,0 +1,7 @@
+package org.apache.cayenne.testdo.inheritance.vertical;
+
+import org.apache.cayenne.testdo.inheritance.vertical.auto._Iv2Root;
+
+public class Iv2Root extends _Iv2Root {
+
+}

Added: cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Sub1.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Sub1.java?rev=1396712&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Sub1.java (added)
+++ cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2Sub1.java Wed Oct 10 18:01:23 2012
@@ -0,0 +1,7 @@
+package org.apache.cayenne.testdo.inheritance.vertical;
+
+import org.apache.cayenne.testdo.inheritance.vertical.auto._Iv2Sub1;
+
+public class Iv2Sub1 extends _Iv2Sub1 {
+
+}

Added: cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2X.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2X.java?rev=1396712&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2X.java (added)
+++ cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/Iv2X.java Wed Oct 10 18:01:23 2012
@@ -0,0 +1,7 @@
+package org.apache.cayenne.testdo.inheritance.vertical;
+
+import org.apache.cayenne.testdo.inheritance.vertical.auto._Iv2X;
+
+public class Iv2X extends _Iv2X {
+
+}

Added: cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Root.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Root.java?rev=1396712&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Root.java (added)
+++ cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Root.java Wed Oct 10 18:01:23 2012
@@ -0,0 +1,24 @@
+package org.apache.cayenne.testdo.inheritance.vertical.auto;
+
+import org.apache.cayenne.CayenneDataObject;
+
+/**
+ * Class _Iv2Root was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Iv2Root extends CayenneDataObject {
+
+    public static final String DISCRIMINATOR_PROPERTY = "discriminator";
+
+    public static final String ID_PK_COLUMN = "ID";
+
+    public void setDiscriminator(String discriminator) {
+        writeProperty(DISCRIMINATOR_PROPERTY, discriminator);
+    }
+    public String getDiscriminator() {
+        return (String)readProperty(DISCRIMINATOR_PROPERTY);
+    }
+
+}

Added: cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Sub1.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Sub1.java?rev=1396712&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Sub1.java (added)
+++ cayenne/main/branches/STABLE-3.1/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/inheritance/vertical/auto/_Iv2Sub1.java Wed Oct 10 18:01:23 2012
@@ -0,0 +1,24 @@
+package org.apache.cayenne.testdo.inheritance.vertical.auto;
+
+import org.apache.cayenne.testdo.inheritance.vertical.Iv2Root;
+import org.apache.cayenne.testdo.inheritance.vertical.Iv2X;
+
+/**
+ * Class _Iv2Sub1 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Iv2Sub1 extends Iv2Root {
+
+    public static final String X_PROPERTY = "x";
+
+    public static final String ID_PK_COLUMN = "ID";
+
+
+    public Iv2X getX() {
+        return (Iv2X)readProperty(X_PROPERTY);
+    }
+
+
+}



Mime
View raw message