Skip to content

Instantly share code, notes, and snippets.

@andrus
Created January 20, 2017 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrus/29616d6f20fc5094a4eb77114d751db0 to your computer and use it in GitHub Desktop.
Save andrus/29616d6f20fc5094a4eb77114d751db0 to your computer and use it in GitHub Desktop.
A builder of Cayenne relationships that allows to map them on the fly in a running app.
public class Relationships {
private Class<?> sourceType;
private Class<?> targetType;
private String name;
private String reverseName;
private int cardinalityBits;
private int toDepPKBits;
private Collection<String[]> forwardJoins;
public static Relationships oneToOne(String name) {
return new Relationships().named(name).cardinality(false, false);
}
public static Relationships oneToMany(String name) {
return new Relationships().named(name).cardinality(true, false);
}
public static Relationships manyToOne(String name) {
return new Relationships().named(name).cardinality(false, true);
}
private Relationships() {
this.forwardJoins = new HashSet<>();
}
public <S extends Persistent, T extends Persistent> Relationships between(Class<S> sourceType, Class<T> targetType) {
this.sourceType = Objects.requireNonNull(sourceType);
this.targetType = Objects.requireNonNull(targetType);
return this;
}
protected Relationships named(String name) {
this.name = name;
return this;
}
public Relationships createReverse(String name) {
this.reverseName = name;
return this;
}
protected Relationships cardinality(boolean forwardToMany, boolean reverseToMany) {
if (forwardToMany && reverseToMany) {
throw new IllegalArgumentException("Both sides of the relationship can not be to-many");
}
int bits = 0;
if (forwardToMany) {
bits = bits | 1;
}
if (reverseToMany) {
bits = bits | 2;
}
this.cardinalityBits = bits;
return this;
}
public Relationships toDepPK() {
return toDepPK(true, false);
}
public Relationships fromDepPK() {
return toDepPK(false, true);
}
protected Relationships toDepPK(boolean forwardToDepPK, boolean reverseToDepPK) {
if (forwardToDepPK && reverseToDepPK) {
throw new IllegalArgumentException("Both sides of the relationship can not be to-dep PK");
}
int bits = 0;
if (forwardToDepPK) {
bits = bits | 1;
}
if (reverseToDepPK) {
bits = bits | 2;
}
this.toDepPKBits = bits;
return this;
}
public Relationships joined(String srcColumn, String targetColumn) {
forwardJoins.add(new String[]{srcColumn, targetColumn});
return this;
}
public void exec(EntityResolver resolver) {
validate();
ObjEntity srcOE = resolver.getObjEntity(sourceType);
ObjEntity targetOE = resolver.getObjEntity(targetType);
Objects.requireNonNull(srcOE);
Objects.requireNonNull(targetOE);
DbEntity srcDE = srcOE.getDbEntity();
DbEntity targetDE = targetOE.getDbEntity();
validateJoins(srcDE, targetDE);
// forward relationship
DbRelationship forwardDBR = new DbRelationship(name);
forwardDBR.setToMany((cardinalityBits & 1) > 0);
forwardDBR.setToDependentPK((toDepPKBits & 1) > 0);
forwardDBR.setTargetEntityName(targetDE.getName());
forwardJoins.forEach(j -> {
forwardDBR.addJoin(new DbJoin(forwardDBR, j[0], j[1]));
});
srcDE.addRelationship(forwardDBR);
ObjRelationship forwardOR = new ObjRelationship(forwardDBR.getName());
forwardOR.setTargetEntityName(targetOE.getName());
srcOE.addRelationship(forwardOR);
forwardOR.setDbRelationshipPath(forwardDBR.getName());
// reverse relationship .. create DB flavor reverse even if no object reverse is specified
// TODO: use Cayenne unique name generator
DbRelationship reverseDBR = new DbRelationship(reverseName != null ? reverseName : "auto_" + srcDE.getName());
reverseDBR.setToMany((cardinalityBits & 2) > 0);
reverseDBR.setToDependentPK((toDepPKBits & 2) > 0);
reverseDBR.setTargetEntityName(srcDE.getName());
forwardJoins.forEach(j -> {
reverseDBR.addJoin(new DbJoin(reverseDBR, j[1], j[0]));
});
targetDE.addRelationship(reverseDBR);
if (reverseName != null) {
ObjRelationship reverseOR = new ObjRelationship(reverseDBR.getName());
reverseOR.setTargetEntityName(srcOE.getName());
targetOE.addRelationship(reverseOR);
reverseOR.setDbRelationshipPath(reverseDBR.getName());
}
}
protected void validate() {
Objects.requireNonNull(sourceType);
Objects.requireNonNull(targetType);
if (forwardJoins.isEmpty()) {
throw new IllegalStateException("No joins specified");
}
}
protected void validateJoins(DbEntity srcDE, DbEntity targetDE) {
forwardJoins.forEach(j -> {
if (srcDE.getAttribute(j[0]) == null) {
throw new IllegalStateException("Invalid source join column: " + j[0]);
}
if (targetDE.getAttribute(j[1]) == null) {
throw new IllegalStateException("Invalid target join column: " + j[1]);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment