org.apache.cayenne.map.DbRelationship Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cayenne-client-nodeps
Show all versions of cayenne-client-nodeps
Cayenne Object Persistence Framework
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.map;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.event.EventManager;
import org.apache.cayenne.event.EventSubject;
import org.apache.cayenne.map.event.RelationshipEvent;
import org.apache.cayenne.util.Util;
import org.apache.cayenne.util.XMLEncoder;
/**
* A DbRelationship is a descriptor of a database inter-table relationship based on one or
* more primary key/foreign key pairs.
*
* @author Misha Shengaout
* @author Andrei Adamchik
*/
public class DbRelationship extends Relationship {
// DbRelationship events
public static final EventSubject PROPERTY_DID_CHANGE = EventSubject.getSubject(
DbRelationship.class,
"PropertyDidChange");
// The columns through which the join is implemented.
protected List joins = new ArrayList();
// Is relationship from source to target points to dependent primary
// key (primary key column of destination table that is also a FK to the source
// column)
protected boolean toDependentPK;
public DbRelationship() {
super();
}
public DbRelationship(String name) {
super(name);
}
/**
* Prints itself as XML to the provided XMLEncoder.
*
* @since 1.1
*/
public void encodeAsXML(XMLEncoder encoder) {
encoder.print("");
encoder.indent(1);
encoder.print(getJoins());
encoder.indent(-1);
encoder.println(" ");
}
/**
* Returns a target of this relationship. If relationship is not attached to a
* DbEntity, and DbEntity doesn't have a namcespace, and exception is thrown.
*/
public Entity getTargetEntity() {
String targetName = getTargetEntityName();
if (targetName == null) {
return null;
}
return getNonNullNamespace().getDbEntity(targetName);
}
/**
* Returns a Collection of target attributes.
*
* @since 1.1
*/
public Collection getTargetAttributes() {
if (joins.size() == 0) {
return Collections.EMPTY_LIST;
}
return CollectionUtils.collect(joins, JoinTransformers.targetExtractor);
}
/**
* Returns a Collection of source attributes.
*
* @since 1.1
*/
public Collection getSourceAttributes() {
if (joins.size() == 0) {
return Collections.EMPTY_LIST;
}
return CollectionUtils.collect(joins, JoinTransformers.sourceExtractor);
}
/**
* Creates a new relationship with the same set of joins, but going in the opposite
* direction.
*
* @since 1.0.5
*/
public DbRelationship createReverseRelationship() {
DbRelationship reverse = new DbRelationship();
reverse.setSourceEntity(getTargetEntity());
reverse.setTargetEntityName(getSourceEntity().getName());
// TODO: must set toDepPK correctly
// must set toMany correctly
reverse.setToMany(!toMany);
Iterator it = joins.iterator();
while (it.hasNext()) {
DbJoin join = (DbJoin) it.next();
DbJoin reverseJoin = join.createReverseJoin();
reverseJoin.setRelationship(reverse);
reverse.addJoin(reverseJoin);
}
return reverse;
}
/**
* Returns DbRelationship that is the opposite of this DbRelationship. This means a
* relationship from this target entity to this source entity with the same join
* semantics. Returns null if no such relationship exists.
*/
public DbRelationship getReverseRelationship() {
Entity target = this.getTargetEntity();
if (target == null) {
return null;
}
Entity src = this.getSourceEntity();
// special case - relationship to self with no joins...
if (target == src && joins.size() == 0) {
return null;
}
TestJoin testJoin = new TestJoin(this);
Iterator it = target.getRelationships().iterator();
while (it.hasNext()) {
DbRelationship rel = (DbRelationship) it.next();
if (rel.getTargetEntity() != src)
continue;
List otherJoins = rel.getJoins();
if (otherJoins.size() != joins.size()) {
continue;
}
Iterator jit = otherJoins.iterator();
boolean joinsMatch = true;
while (jit.hasNext()) {
DbJoin join = (DbJoin) jit.next();
// flip join and try to find similar
testJoin.setSourceName(join.getTargetName());
testJoin.setTargetName(join.getSourceName());
if (!joins.contains(testJoin)) {
joinsMatch = false;
break;
}
}
if (joinsMatch) {
return rel;
}
}
return null;
}
/**
* Returns true if the relationship points to at least one of the PK columns of the
* target entity.
*
* @since 1.1
*/
public boolean isToPK() {
Iterator it = getJoins().iterator();
while (it.hasNext()) {
DbJoin join = (DbJoin) it.next();
if (join.getTarget() == null) {
return false;
}
if (join.getTarget().isPrimaryKey()) {
return true;
}
}
return false;
}
/**
* Returns true
if a method isToDependentPK
of reverse
* relationship of this relationship returns true
.
*/
public boolean isToMasterPK() {
if (isToMany() || isToDependentPK()) {
return false;
}
DbRelationship revRel = getReverseRelationship();
return (revRel != null) ? revRel.isToDependentPK() : false;
}
/**
* Returns true
if relationship from source to target points to
* dependent primary key. Dependent PK is a primary key column of the destination
* table that is also a FK to the source column.
*/
public boolean isToDependentPK() {
return toDependentPK;
}
public void setToDependentPK(boolean toDependentPK) {
if (this.toDependentPK != toDependentPK) {
this.toDependentPK = toDependentPK;
firePropertyDidChange();
}
}
/**
* @since 1.1
*/
public boolean isValidForDepPk() {
Iterator it = getJoins().iterator();
// handle case with no joins
if (!it.hasNext()) {
return false;
}
while (it.hasNext()) {
DbJoin join = (DbJoin) it.next();
DbAttribute target = join.getTarget();
DbAttribute source = join.getSource();
if ((target != null && !target.isPrimaryKey())
|| (source != null && !source.isPrimaryKey())) {
return false;
}
}
return true;
}
/**
* Returns a list of joins. List is returned by reference, so any modifications of the
* list will affect this relationship.
*/
public List getJoins() {
return joins;
}
/**
* Adds a join.
*
* @since 1.1
*/
public void addJoin(DbJoin join) {
if (join != null) {
joins.add(join);
}
}
public void removeJoin(DbJoin join) {
joins.remove(join);
}
public void removeAllJoins() {
joins.clear();
}
public void setJoins(Collection newJoins) {
this.removeAllJoins();
if (newJoins != null) {
joins.addAll(newJoins);
}
}
/**
* Creates a snapshot of primary key attributes of a target object of this
* relationship based on a snapshot of a source. Only "to-one" relationships are
* supported. Returns null if relationship does not point to an object. Throws
* CayenneRuntimeException if relationship is "to many" or if snapshot is missing id
* components.
*/
public Map targetPkSnapshotWithSrcSnapshot(Map srcSnapshot) {
if (isToMany()) {
throw new CayenneRuntimeException(
"Only 'to one' relationships support this method.");
}
Map idMap;
int numJoins = joins.size();
int foundNulls = 0;
// optimize for the most common single column join
if (numJoins == 1) {
DbJoin join = (DbJoin) joins.get(0);
Object val = srcSnapshot.get(join.getSourceName());
if (val == null) {
foundNulls++;
idMap = Collections.EMPTY_MAP;
}
else {
idMap = Collections.singletonMap(join.getTargetName(), val);
}
}
// handle generic case: numJoins > 1
else {
idMap = new HashMap(numJoins * 2);
for (int i = 0; i < numJoins; i++) {
DbJoin join = (DbJoin) joins.get(i);
DbAttribute source = join.getSource();
Object val = srcSnapshot.get(join.getSourceName());
if (val == null) {
// some keys may be nulls and some not in case of multi-key
// relationships where PK and FK partially overlap (see CAY-284)
if (!source.isMandatory()) {
return null;
}
foundNulls++;
}
else {
idMap.put(join.getTargetName(), val);
}
}
}
if (foundNulls == 0) {
return idMap;
}
else if (foundNulls == numJoins) {
return null;
}
else {
throw new CayenneRuntimeException("Some parts of FK are missing in snapshot,"
+ " relationship: "
+ this);
}
}
/**
* Common code to srcSnapshotWithTargetSnapshot. Both are functionally the same,
* except for the name, and whether they operate on a toMany or a toOne.
*/
private Map srcSnapshotWithTargetSnapshot(Map targetSnapshot) {
int len = joins.size();
// optimize for the most common single column join
if (len == 1) {
DbJoin join = (DbJoin) joins.get(0);
Object val = targetSnapshot.get(join.getTargetName());
return Collections.singletonMap(join.getSourceName(), val);
}
// general case
Map idMap = new HashMap(len * 2);
for (int i = 0; i < len; i++) {
DbJoin join = (DbJoin) joins.get(i);
Object val = targetSnapshot.get(join.getTargetName());
idMap.put(join.getSourceName(), val);
}
return idMap;
}
/**
* Creates a snapshot of foreign key attributes of a source object of this
* relationship based on a snapshot of a target. Only "to-one" relationships are
* supported. Throws CayenneRuntimeException if relationship is "to many".
*/
public Map srcFkSnapshotWithTargetSnapshot(Map targetSnapshot) {
if (isToMany()) {
throw new CayenneRuntimeException(
"Only 'to one' relationships support this method.");
}
return srcSnapshotWithTargetSnapshot(targetSnapshot);
}
/**
* Creates a snapshot of primary key attributes of a source object of this
* relationship based on a snapshot of a target. Only "to-many" relationships are
* supported. Throws CayenneRuntimeException if relationship is "to one".
*/
public Map srcPkSnapshotWithTargetSnapshot(Map targetSnapshot) {
if (!isToMany())
throw new CayenneRuntimeException(
"Only 'to many' relationships support this method.");
return srcSnapshotWithTargetSnapshot(targetSnapshot);
}
/**
* Sets relationship multiplicity.
*/
public void setToMany(boolean toMany) {
if (this.toMany != toMany) {
this.toMany = toMany;
this.firePropertyDidChange();
}
}
protected void firePropertyDidChange() {
RelationshipEvent event = new RelationshipEvent(this, this, this
.getSourceEntity());
EventManager.getDefaultManager().postEvent(event, PROPERTY_DID_CHANGE);
}
final static class JoinTransformers {
static final Transformer targetExtractor = new Transformer() {
public Object transform(Object input) {
return (input instanceof DbJoin) ? ((DbJoin) input).getTarget() : input;
}
};
static final Transformer sourceExtractor = new Transformer() {
public Object transform(Object input) {
return (input instanceof DbJoin) ? ((DbJoin) input).getSource() : input;
}
};
}
// a join used for comparison
final static class TestJoin extends DbJoin {
TestJoin(DbRelationship relationship) {
super(relationship);
}
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o == this) {
return true;
}
if (!(o instanceof DbJoin)) {
return false;
}
DbJoin j = (DbJoin) o;
return j.relationship == this.relationship
&& Util.nullSafeEquals(j.sourceName, this.sourceName)
&& Util.nullSafeEquals(j.targetName, this.targetName);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy