org.apache.cayenne.map.DbEntity Maven / Gradle / Ivy
/*****************************************************************
* 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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.configuration.ConfigurationNode;
import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionException;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.map.event.AttributeEvent;
import org.apache.cayenne.map.event.DbAttributeListener;
import org.apache.cayenne.map.event.DbEntityListener;
import org.apache.cayenne.map.event.DbRelationshipListener;
import org.apache.cayenne.map.event.EntityEvent;
import org.apache.cayenne.map.event.MapEvent;
import org.apache.cayenne.map.event.RelationshipEvent;
import org.apache.cayenne.util.CayenneMapEntry;
import org.apache.cayenne.util.Util;
import org.apache.cayenne.util.XMLEncoder;
import org.apache.commons.collections.Transformer;
/**
* A DbEntity is a mapping descriptor that defines a structure of a database table.
*/
public class DbEntity extends Entity implements ConfigurationNode, DbEntityListener, DbAttributeListener,
DbRelationshipListener {
protected String catalog;
protected String schema;
protected Collection primaryKey;
/**
* @since 1.2
*/
protected Collection generatedAttributes;
protected DbKeyGenerator primaryKeyGenerator;
/**
* Qualifier, that will be applied to all select queries and joins with this DbEntity
*/
protected Expression qualifier;
/**
* Creates an unnamed DbEntity.
*/
public DbEntity() {
super();
this.primaryKey = new ArrayList(2);
this.generatedAttributes = new ArrayList(2);
}
/**
* Creates a named DbEntity.
*/
public DbEntity(String name) {
this();
this.setName(name);
}
/**
* @since 3.1
*/
public T acceptVisitor(ConfigurationNodeVisitor visitor) {
return visitor.visitDbEntity(this);
}
/**
* Prints itself as XML to the provided XMLEncoder.
*
* @since 1.1
*/
public void encodeAsXML(XMLEncoder encoder) {
encoder.print(" 0) {
encoder.print(" schema=\"");
encoder.print(Util.encodeXmlAttribute(getSchema().trim()));
encoder.print('\"');
}
if (getCatalog() != null && getCatalog().trim().length() > 0) {
encoder.print(" catalog=\"");
encoder.print(Util.encodeXmlAttribute(getCatalog().trim()));
encoder.print('\"');
}
encoder.println('>');
encoder.indent(1);
encoder.print(getAttributeMap());
if (getPrimaryKeyGenerator() != null) {
getPrimaryKeyGenerator().encodeAsXML(encoder);
}
if (getQualifier() != null) {
encoder.print("");
getQualifier().encodeAsXML(encoder);
encoder.println(" ");
}
encoder.indent(-1);
encoder.println(" ");
}
/**
* Returns table name including schema, if present.
*/
public String getFullyQualifiedName() {
return (schema != null) ? schema + '.' + getName() : getName();
}
/**
* Returns database schema of this table.
*
* @return table's schema, null if not set.
*/
public String getSchema() {
return schema;
}
/**
* Sets the database schema name of the table described by this DbEntity.
*/
public void setSchema(String schema) {
this.schema = schema;
}
/**
* Returns the catalog name of the table described by this DbEntity.
*/
public String getCatalog() {
return catalog;
}
/**
* Sets the catalog name of the table described by this DbEntity.
*/
public void setCatalog(String catalog) {
this.catalog = catalog;
}
/**
* Returns an unmodifiable collection of DbAttributes representing the primary key of
* the table described by this DbEntity.
*
* @since 3.0
*/
public Collection getPrimaryKeys() {
return Collections.unmodifiableCollection(primaryKey);
}
/**
* Returns a Collection of all attributes that either belong to this DbEntity or
* inherited.
*/
@Override
public Collection getAttributes() {
return (Collection) super.getAttributes();
}
/**
* Returns an unmodifiable collection of DbAttributes that are generated by the
* database.
*
* @since 1.2
*/
public Collection getGeneratedAttributes() {
return Collections.unmodifiableCollection(generatedAttributes);
}
/**
* Adds a new attribute to this entity.
*
* @throws IllegalArgumentException if Attribute has no name or there is an existing
* attribute with the same name
* @throws IllegalArgumentException if a relationship has the same name as this
* attribute
* @since 3.0
*/
public void addAttribute(DbAttribute attr) {
super.addAttribute(attr);
this.dbAttributeAdded(new AttributeEvent(this, attr, this, MapEvent.ADD));
}
/**
* Removes attribute from the entity, removes any relationship joins containing this
* attribute. Does nothing if the attribute name is not found.
*
* @see org.apache.cayenne.map.Entity#removeAttribute(String)
*/
@Override
public void removeAttribute(String attrName) {
Attribute attr = getAttribute(attrName);
if (attr == null) {
return;
}
DataMap map = getDataMap();
if (map != null) {
for (DbEntity ent : map.getDbEntities()) {
for (DbRelationship relationship : ent.getRelationships()) {
Iterator joins = relationship.getJoins().iterator();
while (joins.hasNext()) {
DbJoin join = joins.next();
if (join.getSource() == attr || join.getTarget() == attr) {
joins.remove();
}
}
}
}
}
super.removeAttribute(attrName);
this.dbAttributeRemoved(new AttributeEvent(this, attr, this, MapEvent.REMOVE));
}
@Override
public void clearAttributes() {
super.clearAttributes();
// post dummy event for no specific attribute
this.dbAttributeRemoved(new AttributeEvent(this, null, this, MapEvent.REMOVE));
}
/**
* Returns a Collection of relationships from this entity or inherited.
*/
@Override
public Collection getRelationships() {
return (Collection) super.getRelationships();
}
@Override
public SortedMap getRelationshipMap() {
return (SortedMap) super.getRelationshipMap();
}
/**
* @since 3.0
*/
@Override
@SuppressWarnings("unchecked")
public PathComponent lastPathComponent(
Expression path,
Map aliasMap) {
return super.lastPathComponent(path, aliasMap);
}
/**
* Returns an Iterable instance over expression path components based on this entity.
*
* @since 3.0
*/
@Override
@SuppressWarnings("unchecked")
public Iterable> resolvePath(
final Expression pathExp,
final Map aliasMap) {
if (pathExp.getType() == Expression.DB_PATH) {
return new Iterable>() {
public Iterator iterator() {
return new PathComponentIterator(DbEntity.this, (String) pathExp
.getOperand(0), aliasMap);
}
};
}
throw new ExpressionException("Invalid expression type: '"
+ pathExp.expName()
+ "', DB_PATH is expected.");
}
@Override
public Iterator resolvePathComponents(Expression pathExp)
throws ExpressionException {
if (pathExp.getType() != Expression.DB_PATH) {
throw new ExpressionException("Invalid expression type: '"
+ pathExp.expName()
+ "', DB_PATH is expected.");
}
return new PathIterator((String) pathExp.getOperand(0));
}
/**
* Set the primary key generator for this entity. If null is passed, nothing is
* changed.
*/
public void setPrimaryKeyGenerator(DbKeyGenerator primaryKeyGenerator) {
this.primaryKeyGenerator = primaryKeyGenerator;
if (primaryKeyGenerator != null) {
primaryKeyGenerator.setDbEntity(this);
}
}
/**
* Return the primary key generator for this entity.
*/
public DbKeyGenerator getPrimaryKeyGenerator() {
return primaryKeyGenerator;
}
/**
* DbEntity property changed event. May be name, attribute or relationship added or
* removed, etc. Attribute and relationship property changes are handled in respective
* listeners.
*
* @since 1.2
*/
public void dbEntityChanged(EntityEvent e) {
if ((e == null) || (e.getEntity() != this)) {
// not our concern
return;
}
// handle entity name changes
if (e.getId() == EntityEvent.CHANGE && e.isNameChange()) {
String newName = e.getNewName();
DataMap map = getDataMap();
if (map != null) {
// handle all of the relationship target names that need to be changed
for (DbEntity dbe : map.getDbEntities()) {
for (DbRelationship relationship : dbe.getRelationships()) {
if (relationship.getTargetEntity() == this) {
relationship.setTargetEntityName(newName);
}
}
}
// get all of the related object entities
for (ObjEntity oe : map.getMappedEntities(this)) {
if (oe.getDbEntity() == this) {
oe.setDbEntityName(newName);
}
}
}
}
}
/**
* New entity has been created/added.
*/
public void dbEntityAdded(EntityEvent e) {
// does nothing currently
}
/**
* Entity has been removed.
*/
public void dbEntityRemoved(EntityEvent e) {
// does nothing currently
}
public void dbAttributeAdded(AttributeEvent e) {
this.handleAttributeUpdate(e);
}
public void dbAttributeChanged(AttributeEvent e) {
this.handleAttributeUpdate(e);
}
public void dbAttributeRemoved(AttributeEvent e) {
this.handleAttributeUpdate(e);
}
private void handleAttributeUpdate(AttributeEvent e) {
if ((e == null) || (e.getEntity() != this)) {
// not our concern
return;
}
// catch clearing (event with null ('any') DbAttribute)
Attribute attribute = e.getAttribute();
if ((attribute == null) && (this.attributes.isEmpty())) {
this.primaryKey.clear();
this.generatedAttributes.clear();
return;
}
// make sure we handle a DbAttribute
if (!(attribute instanceof DbAttribute)) {
return;
}
DbAttribute dbAttribute = (DbAttribute) attribute;
// handle attribute name changes
if (e.getId() == AttributeEvent.CHANGE && e.isNameChange()) {
String oldName = e.getOldName();
String newName = e.getNewName();
DataMap map = getDataMap();
if (map != null) {
for (DbEntity ent : map.getDbEntities()) {
// handle all of the dependent object entity attribute changes
for (ObjEntity oe : map.getMappedEntities(ent)) {
for (ObjAttribute attr : oe.getAttributes()) {
if (attr.getDbAttribute() == dbAttribute) {
attr.setDbAttributePath(newName);
}
}
}
// handle all of the relationships / joins that use the changed
// attribute
for (Relationship rel : ent.getRelationships()) {
for (DbJoin join : ((DbRelationship) rel).getJoins()) {
if (join.getSource() == dbAttribute) {
join.setSourceName(newName);
}
if (join.getTarget() == dbAttribute) {
join.setTargetName(newName);
}
}
}
}
}
// clear the attribute out of the collection
attributes.remove(oldName);
// add the attribute back in with the new name
super.addAttribute(dbAttribute);
}
// handle PK refresh
if (primaryKey.contains(dbAttribute) || dbAttribute.isPrimaryKey()) {
switch (e.getId()) {
case MapEvent.ADD:
this.primaryKey.add(dbAttribute);
break;
case MapEvent.REMOVE:
this.primaryKey.remove(dbAttribute);
break;
default:
// generic update
this.primaryKey.clear();
for (Object next : getAttributes()) {
DbAttribute dba = (DbAttribute) next;
if (dba.isPrimaryKey()) {
this.primaryKey.add(dba);
}
}
}
}
// handle generated key refresh
if (generatedAttributes.contains(dbAttribute) || dbAttribute.isGenerated()) {
switch (e.getId()) {
case MapEvent.ADD:
this.generatedAttributes.add(dbAttribute);
break;
case MapEvent.REMOVE:
this.generatedAttributes.remove(dbAttribute);
break;
default:
// generic update
this.generatedAttributes.clear();
for (Object next : getAttributes()) {
DbAttribute dba = (DbAttribute) next;
if (dba.isGenerated()) {
this.generatedAttributes.add(dba);
}
}
}
}
}
/**
* Relationship property changed.
*/
public void dbRelationshipChanged(RelationshipEvent e) {
if ((e == null) || (e.getEntity() != this)) {
// not our concern
return;
}
Relationship rel = e.getRelationship();
// make sure we handle a DbRelationship
if (!(rel instanceof DbRelationship)) {
return;
}
DbRelationship dbRel = (DbRelationship) rel;
// handle relationship name changes
if (e.getId() == RelationshipEvent.CHANGE && e.isNameChange()) {
String oldName = e.getOldName();
DataMap map = getDataMap();
if (map != null) {
// updating dbAttributePaths for attributes of all ObjEntities
for (ObjEntity objEntity : getDataMap().getObjEntities()) {
for (ObjAttribute attribute : objEntity.getAttributes()) {
attribute.updateDbAttributePath();
}
}
}
// clear the relationship out of the collection
relationships.remove(oldName);
// add the relationship back in with the new name
super.addRelationship(dbRel);
}
}
/** Relationship has been created/added. */
public void dbRelationshipAdded(RelationshipEvent e) {
// does nothing currently
}
/** Relationship has been removed. */
public void dbRelationshipRemoved(RelationshipEvent e) {
// does nothing currently
}
/**
* @return qualifier that will be ANDed to all select queries with this entity
*/
public Expression getQualifier() {
return qualifier;
}
/**
* Sets qualifier for this entity
*/
public void setQualifier(Expression qualifier) {
this.qualifier = qualifier;
}
/**
* Returns true if there is full replacement id is attached to an ObjectId. "Full"
* means that all PK columns are present and only PK columns are present.
*
* @since 1.2
*/
public boolean isFullReplacementIdAttached(ObjectId id) {
if (!id.isReplacementIdAttached()) {
return false;
}
Map replacement = id.getReplacementIdMap();
Collection pk = getPrimaryKeys();
if (pk.size() != replacement.size()) {
return false;
}
for (DbAttribute attribute : pk) {
if (!replacement.containsKey(attribute.getName())) {
return false;
}
}
return true;
}
/**
* Transforms Expression rooted in this entity to an analogous expression rooted in
* related entity.
*
* @since 1.1
*/
@Override
public Expression translateToRelatedEntity(
Expression expression,
String relationshipPath) {
if (expression == null) {
return null;
}
if (relationshipPath == null) {
return expression;
}
return expression.transform(new RelationshipPathConverter(relationshipPath));
}
final class RelationshipPathConverter implements Transformer {
String relationshipPath;
boolean toMany;
RelationshipPathConverter(String relationshipPath) {
this.relationshipPath = relationshipPath;
Iterator relationshipIt = resolvePathComponents(relationshipPath);
while (relationshipIt.hasNext()) {
// relationship path components must be DbRelationships
DbRelationship nextDBR = (DbRelationship) relationshipIt.next();
if (nextDBR.isToMany()) {
toMany = true;
break;
}
}
}
public Object transform(Object input) {
if (!(input instanceof Expression)) {
return input;
}
Expression expression = (Expression) input;
if (expression.getType() != Expression.DB_PATH) {
return input;
}
String path = (String) expression.getOperand(0);
String converted = translatePath(path);
Expression transformed = ExpressionFactory
.expressionOfType(Expression.DB_PATH);
transformed.setOperand(0, converted);
return transformed;
}
private PathComponentIterator createPathIterator(String path) {
return new PathComponentIterator(DbEntity.this, path,
new HashMap()); //TODO: do we need aliases here?
}
String translatePath(String path) {
// algorithm to determine the translated path:
// 0. If relationship has at least one to-many component, travel all the way
// back, and then all the way forward
// 1. If relationship path equals to input, travel one step back, and then one
// step forward.
// 2. If input completely includes relationship path, use input's remaining
// tail.
// 3. If relationship path and input have none or some leading components in
// common,
// (a) strip common leading part;
// (b) reverse the remaining relationship part;
// (c) append remaining input to the reversed remaining relationship.
// case (0)
if (toMany) {
PathComponentIterator pathIt = createPathIterator(path);
Iterator relationshipIt = resolvePathComponents(relationshipPath);
// for inserts from the both ends use LinkedList
LinkedList finalPath = new LinkedList();
// append remainder of the relationship, reversing it
while (relationshipIt.hasNext()) {
DbRelationship nextDBR = (DbRelationship) relationshipIt.next();
prependReversedPath(finalPath, nextDBR);
}
while (pathIt.hasNext()) {
// components may be attributes or relationships
PathComponent component = pathIt.next();
appendPath(finalPath, component);
}
return convertToPath(finalPath);
}
// case (1)
if (path.equals(relationshipPath)) {
LinkedList finalPath = new LinkedList();
PathComponentIterator pathIt = createPathIterator(path);
// just do one step back and one step forward to create correct joins...
// find last rel...
DbRelationship lastDBR = null;
while (pathIt.hasNext()) {
// relationship path components must be DbRelationships
lastDBR = (DbRelationship) pathIt.next().getRelationship();
}
if (lastDBR != null) {
prependReversedPath(finalPath, lastDBR);
appendPath(finalPath, lastDBR);
}
return convertToPath(finalPath);
}
// case (2)
String relationshipPathWithDot = relationshipPath + Entity.PATH_SEPARATOR;
if (path.startsWith(relationshipPathWithDot)) {
return path.substring(relationshipPathWithDot.length());
}
// case (3)
PathComponentIterator pathIt = createPathIterator(path);
Iterator relationshipIt = resolvePathComponents(relationshipPath);
// for inserts from the both ends use LinkedList
LinkedList finalPath = new LinkedList();
while (relationshipIt.hasNext() && pathIt.hasNext()) {
// relationship path components must be DbRelationships
DbRelationship nextDBR = (DbRelationship) relationshipIt.next();
// expression components may be attributes or relationships
PathComponent component = pathIt.next();
if (nextDBR != component.getRelationship()) {
// found split point
// consume the last iteration components,
// then break out to finish the iterators independently
prependReversedPath(finalPath, nextDBR);
appendPath(finalPath, component);
break;
}
break;
}
// append remainder of the relationship, reversing it
while (relationshipIt.hasNext()) {
DbRelationship nextDBR = (DbRelationship) relationshipIt.next();
prependReversedPath(finalPath, nextDBR);
}
while (pathIt.hasNext()) {
// components may be attributes or relationships
PathComponent component = pathIt.next();
appendPath(finalPath, component);
}
return convertToPath(finalPath);
}
private String convertToPath(List path) {
StringBuilder converted = new StringBuilder();
int len = path.size();
for (int i = 0; i < len; i++) {
if (i > 0) {
converted.append(Entity.PATH_SEPARATOR);
}
converted.append(path.get(i));
}
return converted.toString();
}
private void prependReversedPath(
LinkedList finalPath,
DbRelationship relationship) {
DbRelationship revNextDBR = relationship.getReverseRelationship();
if (revNextDBR == null) {
throw new CayenneRuntimeException(
"Unable to find reverse DbRelationship for "
+ relationship.getSourceEntity().getName()
+ Entity.PATH_SEPARATOR
+ relationship.getName()
+ ".");
}
finalPath.addFirst(revNextDBR.getName());
}
private void appendPath(
LinkedList finalPath,
CayenneMapEntry pathComponent) {
finalPath.addLast(pathComponent.getName());
}
private void appendPath(
LinkedList finalPath,
PathComponent pathComponent) {
CayenneMapEntry mapEntry = pathComponent.getAttribute() != null ?
pathComponent.getAttribute() :
pathComponent.getRelationship();
String name = mapEntry.getName();
if (pathComponent.getJoinType() == JoinType.LEFT_OUTER) {
name += OUTER_JOIN_INDICATOR;
}
finalPath.addLast(name);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy