org.apache.cayenne.map.Entity 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.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionException;
import org.apache.cayenne.util.CayenneMapEntry;
import org.apache.cayenne.util.ToStringBuilder;
import org.apache.cayenne.util.XMLSerializable;
/**
* An Entity is an abstract descriptor for an entity mapping concept. Entity can represent
* either a descriptor of database table or a persistent object.
*
*/
public abstract class Entity implements CayenneMapEntry, XMLSerializable, Serializable {
public static final String PATH_SEPARATOR = ".";
/**
* A prefix or a suffix that can be used in a path component to indicate that an OUTER
* JOIN should be used when resolving the expression.
*
* @since 3.0
*/
public static final String OUTER_JOIN_INDICATOR = "+";
protected String name;
protected DataMap dataMap;
protected SortedMap attributes;
protected SortedMap relationships;
/**
* Creates an unnamed Entity.
*/
public Entity() {
this(null);
}
/**
* Creates a named Entity.
*/
public Entity(String name) {
attributes = new TreeMap();
relationships = new TreeMap();
setName(name);
}
@Override
public String toString() {
return new ToStringBuilder(this).append("name", getName()).toString();
}
/**
* Returns entity name. Name is a unique identifier of the entity within its DataMap.
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getParent() {
return getDataMap();
}
public void setParent(Object parent) {
if (parent != null && !(parent instanceof DataMap))
throw new IllegalArgumentException("Expected null or DataMap, got: " + parent);
setDataMap((DataMap) parent);
}
/**
* @return parent DataMap of this entity.
*/
public DataMap getDataMap() {
return dataMap;
}
/**
* Sets parent DataMap of this entity.
*/
public void setDataMap(DataMap dataMap) {
this.dataMap = dataMap;
}
/**
* Returns attribute with name attributeName
or null if no attribute
* with this name exists.
*/
public Attribute getAttribute(String attributeName) {
return attributes.get(attributeName);
}
/**
* Adds new attribute to the entity, setting its parent entity to be this object. If
* attribute has no name, IllegalArgumentException is thrown.
*/
public void addAttribute(Attribute attribute) {
if (attribute.getName() == null)
throw new IllegalArgumentException("Attempt to insert unnamed attribute.");
// block overrides
// TODO: change method signature to return replaced attribute and make sure the
// Modeler handles it...
Object existingAttribute = attributes.get(attribute.getName());
if (existingAttribute != null) {
if (existingAttribute == attribute)
return;
else
throw new IllegalArgumentException("An attempt to override attribute '"
+ attribute.getName()
+ "'");
}
// Check that there aren't any relationships with the same name as the given
// attribute.
Object existingRelationship = relationships.get(attribute.getName());
if (existingRelationship != null)
throw new IllegalArgumentException(
"Attribute name conflict with existing relationship '"
+ attribute.getName()
+ "'");
attributes.put(attribute.getName(), attribute);
attribute.setEntity(this);
}
/** Removes an attribute named attrName
. */
public void removeAttribute(String attrName) {
attributes.remove(attrName);
}
public void clearAttributes() {
attributes.clear();
}
/**
* Returns relationship with name relName
. Will return null if no
* relationship with this name exists in the entity.
*/
public Relationship getRelationship(String relName) {
return relationships.get(relName);
}
/** Adds new relationship to the entity. */
public void addRelationship(Relationship relationship) {
if (relationship.getName() == null)
throw new IllegalArgumentException("Attempt to insert unnamed relationship.");
// block overrides
// TODO: change method signature to return replaced attribute and make sure the
// Modeler handles it...
Object existingRelationship = relationships.get(relationship.getName());
if (existingRelationship != null) {
if (existingRelationship == relationship)
return;
else
throw new IllegalArgumentException(
"An attempt to override relationship '"
+ relationship.getName()
+ "'");
}
// Check that there aren't any attributes with the same name as the given
// relationship.
Object existingAttribute = attributes.get(relationship.getName());
if (existingAttribute != null)
throw new IllegalArgumentException(
"Relationship name conflict with existing attribute '"
+ relationship.getName()
+ "'");
relationships.put(relationship.getName(), relationship);
relationship.setSourceEntity(this);
}
/** Removes a relationship named attrName
. */
public void removeRelationship(String relName) {
relationships.remove(relName);
}
public void clearRelationships() {
relationships.clear();
}
/**
* Returns an unmodifiable map of relationships sorted by name.
*/
public SortedMap getRelationshipMap() {
// create a new instance ... earlier attempts to cache it in the entity caused
// serialization issues (esp. with Hessian).
return Collections.unmodifiableSortedMap(relationships);
}
/**
* Returns a relationship that has a specified entity as a target. If there is more
* than one relationship for the same target, it is unpredictable which one will be
* returned.
*
* @since 1.1
*/
public Relationship getAnyRelationship(Entity targetEntity) {
if (getRelationships().isEmpty())
return null;
for (Relationship r : getRelationships()) {
if (r.getTargetEntity() == targetEntity)
return r;
}
return null;
}
/**
* Returns an unmodifiable collection of Relationships that exist in this entity.
*/
public Collection extends Relationship> getRelationships() {
// create a new instance ... earlier attempts to cache it in the entity caused
// serialization issues (esp. with Hessian).
return Collections.unmodifiableCollection(relationships.values());
}
/**
* Returns an unmodifiable sorted map of entity attributes.
*/
public SortedMap getAttributeMap() {
// create a new instance ... earlier attempts to cache it in the entity caused
// serialization issues (esp. with Hessian).
return Collections.unmodifiableSortedMap(attributes);
}
/**
* Returns an unmodifiable collection of entity attributes.
*/
public Collection extends Attribute> getAttributes() {
// create a new instance ... earlier attempts to cache it in the entity caused
// serialization issues (esp. with Hessian).
return Collections.unmodifiableCollection(attributes.values());
}
/**
* Translates Expression rooted in this entity to an analogous expression rooted in
* related entity.
*
* @since 1.1
*/
public abstract Expression translateToRelatedEntity(
Expression expression,
String relationshipPath);
/**
* Convenience method returning the last component in the path iterator. If the last
* component is an alias, it is fully resolved down to the last ObjRelationship.
*
* @since 3.0
*/
@SuppressWarnings("unchecked")
public PathComponent lastPathComponent(
Expression path,
Map aliasMap) {
for (PathComponent component : resolvePath(path, aliasMap)) {
if (component.isLast()) {
// resolve aliases if needed
return lastPathComponent(component);
}
}
return null;
}
@SuppressWarnings("unchecked")
private PathComponent lastPathComponent(
PathComponent component) {
if (!component.isAlias()) {
return component;
}
for (PathComponent subcomponent : component.getAliasedPath()) {
if (subcomponent.isLast()) {
return lastPathComponent(subcomponent);
}
}
throw new IllegalStateException("Invalid last path component: "
+ component.getName());
}
/**
* Returns an Iterable over the path components with elements represented as
* {@link PathComponent} instances, encapsulating a relationship, an attribute or a
* subpath alias. An optional "aliasMap" parameter is used to resolve subpaths from
* aliases.
*
* This method is lazy: if path is invalid and can not be resolved from this entity,
* this method will still return an Iterator, but an attempt to read the first invalid
* path component will result in ExpressionException.
*
*
* @since 3.0
*/
@SuppressWarnings("unchecked")
public abstract Iterable> resolvePath(
Expression pathExp,
Map aliasMap);
/**
* Processes expression pathExp
and returns an Iterator of path
* components that contains a sequence of Attributes and Relationships. Note that if
* path is invalid and can not be resolved from this entity, this method will still
* return an Iterator, but an attempt to read the first invalid path component will
* result in ExpressionException.
*/
public abstract Iterator resolvePathComponents(Expression pathExp)
throws ExpressionException;
/**
* Returns an Iterator over the path components that contains a sequence of Attributes
* and Relationships. Note that if path is invalid and can not be resolved from this
* entity, this method will still return an Iterator, but an attempt to read the first
* invalid path component will result in ExpressionException.
*/
public Iterator resolvePathComponents(String path)
throws ExpressionException {
return new PathIterator(path);
}
// An iterator resolving mapping components represented by the path string.
// This entity is assumed to be the root of the path.
final class PathIterator implements Iterator {
private StringTokenizer toks;
private Entity currentEnt;
private String path;
PathIterator(String path) {
super();
currentEnt = Entity.this;
toks = new StringTokenizer(path, PATH_SEPARATOR);
this.path = path;
}
public boolean hasNext() {
return toks.hasMoreTokens();
}
public CayenneMapEntry next() {
String pathComp = toks.nextToken();
// see if this is an attribute
Attribute attr = currentEnt.getAttribute(pathComp);
if (attr != null) {
// do a sanity check...
if (toks.hasMoreTokens())
throw new ExpressionException(
"Attribute must be the last component of the path: '"
+ pathComp
+ "'.",
path,
null);
return attr;
}
Relationship rel = currentEnt.getRelationship(pathComp);
if (rel != null) {
currentEnt = rel.getTargetEntity();
if (currentEnt != null || !toks.hasMoreTokens()) { //otherwise an exception will be thrown
return rel;
}
}
String entityName = (currentEnt != null) ? currentEnt.getName() : "(?)";
// build error message
StringBuilder buf = new StringBuilder();
buf
.append("Can't resolve path component: [")
.append(entityName)
.append('.')
.append(pathComp)
.append("].");
throw new ExpressionException(buf.toString(), path, null);
}
public void remove() {
throw new UnsupportedOperationException(
"'remove' operation is not supported.");
}
}
final MappingNamespace getNonNullNamespace() {
MappingNamespace parent = getDataMap();
if (parent == null)
throw new CayenneRuntimeException("Entity '"
+ getName()
+ "' has no parent MappingNamespace (such as DataMap)");
return parent;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy