/***************************************************************** * 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 org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.configuration.ConfigurationNode; import org.apache.cayenne.configuration.ConfigurationNodeVisitor; import org.apache.cayenne.dba.TypesMapping; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.exp.Expression; import org.apache.cayenne.exp.ExpressionException; import org.apache.cayenne.exp.ExpressionFactory; import org.apache.cayenne.map.event.EntityEvent; import org.apache.cayenne.map.event.ObjEntityListener; import org.apache.cayenne.util.CayenneMapEntry; import org.apache.cayenne.util.Util; import org.apache.cayenne.util.XMLEncoder; import org.apache.commons.collections.Transformer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; /** * ObjEntity is a mapping descriptor for a DataObject Java class. It contains * the information about the Java class itself, as well as its mapping to the * DbEntity layer. */ public class ObjEntity extends Entity implements ObjEntityListener, ConfigurationNode { public static final int LOCK_TYPE_NONE = 0; public static final int LOCK_TYPE_OPTIMISTIC = 1; // do not import CayenneDataObject as it introduces unneeded client // dependency private static final String CAYENNE_DATA_OBJECT_CLASS = "org.apache.cayenne.CayenneDataObject"; /** * A collection of default "generic" entity classes excluded from class * generation. * * @since 1.2 */ protected static final Collection<String> DEFAULT_GENERIC_CLASSES = Collections.singletonList(CAYENNE_DATA_OBJECT_CLASS); protected String superClassName; protected String className; protected String dbEntityName; protected String superEntityName; protected Expression qualifier; protected boolean readOnly; protected int lockType; protected boolean _abstract; protected boolean serverOnly; protected String clientClassName; protected String clientSuperClassName; @Deprecated protected List<EntityListener> entityListeners; protected CallbackMap callbacks; @Deprecated protected boolean excludingDefaultListeners; @Deprecated protected boolean excludingSuperclassListeners; protected Map<String, String> attributeOverrides; public ObjEntity() { this(null); } public ObjEntity(String name) { setName(name); this.lockType = LOCK_TYPE_NONE; this.callbacks = new CallbackMap(); this.entityListeners = new ArrayList<>(2); this.attributeOverrides = new TreeMap<>(); } /** * @since 3.1 */ @Override public <T> T acceptVisitor(ConfigurationNodeVisitor<T> visitor) { return visitor.visitObjEntity(this); } /** * Prints itself as XML to the provided XMLEncoder. * * @since 1.1 */ @Override public void encodeAsXML(XMLEncoder encoder) { encoder.print("<obj-entity name=\""); encoder.print(getName()); // additionally validate that superentity exists if (getSuperEntityName() != null && getSuperEntity() != null) { encoder.print("\" superEntityName=\""); encoder.print(getSuperEntityName()); } if (isAbstract()) { encoder.print("\" abstract=\"true"); } if (isServerOnly()) { encoder.print("\" serverOnly=\"true"); } if (getClassName() != null) { encoder.print("\" className=\""); encoder.print(getClassName()); } if (getClientClassName() != null) { encoder.print("\" clientClassName=\""); encoder.print(getClientClassName()); } if (isReadOnly()) { encoder.print("\" readOnly=\"true"); } if (getDeclaredLockType() == LOCK_TYPE_OPTIMISTIC) { encoder.print("\" lock-type=\"optimistic"); } if (getDbEntityName() != null && getDbEntity() != null) { // not writing DbEntity name if sub entity has same DbEntity // as super entity, see CAY-1477 if (!(getSuperEntity() != null && getSuperEntity().getDbEntity() == getDbEntity())) { encoder.print("\" dbEntityName=\""); encoder.print(Util.encodeXmlAttribute(getDbEntityName())); } } if (getSuperEntityName() == null && getSuperClassName() != null) { encoder.print("\" superClassName=\""); encoder.print(getSuperClassName()); } if (getSuperEntityName() == null && getClientSuperClassName() != null) { encoder.print("\" clientSuperClassName=\""); encoder.print(getClientSuperClassName()); } // deprecated if (isExcludingSuperclassListeners()) { encoder.print("\" exclude-superclass-listeners=\"true"); } // deprecated if (isExcludingDefaultListeners()) { encoder.print("\" exclude-default-listeners=\"true"); } encoder.println("\">"); encoder.indent(1); if (qualifier != null) { encoder.print("<qualifier>"); qualifier.encodeAsXML(encoder); encoder.println("</qualifier>"); } // store attributes encoder.print(getDeclaredAttributes()); for (Map.Entry<String, String> override : attributeOverrides.entrySet()) { encoder.print("<attribute-override name=\"" + override.getKey() + '\"'); encoder.print(" db-attribute-path=\""); encoder.print(Util.encodeXmlAttribute(override.getValue())); encoder.print('\"'); encoder.println("/>"); } // deprecated // write entity listeners for (EntityListener entityListener : entityListeners) { entityListener.encodeAsXML(encoder); } // write entity-level callbacks getCallbackMap().encodeCallbacksAsXML(encoder); encoder.indent(-1); encoder.println("</obj-entity>"); } /** * Returns an ObjEntity stripped of any server-side information, such as * DbEntity mapping. "clientClassName" property of this entity is used to * initialize "className" property of returned entity. * * @since 1.2 */ public ObjEntity getClientEntity() { ClientObjEntity entity = new ClientObjEntity(getName()); entity.setClassName(getClientClassName()); entity.setSuperClassName(getClientSuperClassName()); entity.setSuperEntityName(getSuperEntityName()); entity.setDeclaredQualifier(getDeclaredQualifier()); // TODO: should we also copy lock type? Collection<ObjAttribute> primaryKeys = getMutablePrimaryKeys(); Collection<ObjAttribute> clientPK = new ArrayList<>(primaryKeys.size()); for (ObjAttribute attribute : getDeclaredAttributes()) { ObjAttribute clientAttribute = attribute.getClientAttribute(); entity.addAttribute(clientAttribute); if (primaryKeys.remove(attribute)) { clientPK.add(clientAttribute); } } // after all meaningful pks got removed, here we only have synthetic pks // left... for (ObjAttribute attribute : primaryKeys) { ObjAttribute clientAttribute = attribute.getClientAttribute(); clientPK.add(clientAttribute); } entity.setPrimaryKeys(clientPK); // copy relationships; skip runtime generated relationships for (ObjRelationship relationship : getDeclaredRelationships()) { if (relationship.isRuntime()) { continue; } ObjEntity targetEntity = relationship.getTargetEntity(); // note that 'isClientAllowed' also checks parent DataMap client // policy // that can be handy in case of cross-map relationships if (targetEntity == null || !targetEntity.isClientAllowed()) { continue; } entity.addRelationship(relationship.getClientRelationship()); } // TODO: andrus 2/5/2007 - copy embeddables // TODO: andrus 2/5/2007 - copy callback methods return entity; } /** * Returns a non-null class name. For generic entities with no class * specified explicitly, default DataMap superclass is used, and if it is * not set - CayenneDataObject is used. * * @since 4.0 */ public String getJavaClassName() { String name = getClassName(); if (name == null && getDataMap() != null) { name = getDataMap().getDefaultSuperclass(); } if (name == null) { name = CAYENNE_DATA_OBJECT_CLASS; } return name; } /** * Returns Java class of persistent objects described by this entity. For * generic entities with no class specified explicitly, default DataMap * superclass is used, and if it is not set - CayenneDataObject is used. * Casts any thrown exceptions into CayenneRuntimeException. * * @since 1.2 * @deprecated since 4.0 this method based on statically defined class * loading algorithm is not going to work in environments like * OSGi. {@link AdhocObjectFactory} should be used as it can * provide the environment-specific class loading policy. */ @Deprecated public Class<?> getJavaClass() { String name = getJavaClassName(); try { return Util.getJavaClass(name); } catch (ClassNotFoundException e) { throw new CayenneRuntimeException("Failed to doLoad class " + name + ": " + e.getMessage(), e); } } /** * Returns an unmodifiable list of registered {@link EntityListener} * objects. Note that since the order of listeners is significant a list, * not just a generic Collection is returned. * * @since 3.0 * @deprecated since 4.0 unused, as listeners are no longer mapped in a * DataMap. */ @Deprecated public List<EntityListener> getEntityListeners() { return Collections.unmodifiableList(entityListeners); } /** * Adds a new EntityListener. * * @since 3.0 * @throws IllegalArgumentException * if a listener for the same class name is already registered. * @deprecated since 4.0 unused, as listeners are no longer mapped in a * DataMap. */ @Deprecated public void addEntityListener(EntityListener listener) { for (EntityListener next : entityListeners) { if (listener.getClassName().equals(next.getClassName())) { throw new IllegalArgumentException("Duplicate listener for " + next.getClassName()); } } entityListeners.add(listener); } /** * Removes a listener matching class name. * * @since 3.0 * @deprecated since 4.0 unused, as listeners are no longer mapped in a * DataMap. */ @Deprecated public void removeEntityListener(String className) { Iterator<EntityListener> it = entityListeners.iterator(); while (it.hasNext()) { EntityListener next = it.next(); if (className.equals(next.getClassName())) { it.remove(); break; } } } /** * @since 3.0 * @deprecated since 4.0 unused, as listeners are no longer mapped in a * DataMap. */ @Deprecated public EntityListener getEntityListener(String className) { for (EntityListener next : entityListeners) { if (className.equals(next.getClassName())) { return next; } } return null; } /** * Returns an object that stores callback methods of this entity. * * @since 3.0 */ public CallbackMap getCallbackMap() { return callbacks; } /** * Returns the type of lock used by this ObjEntity. If this entity is not * locked, this method would look in a super entity recursively, until it * finds a lock somewhere in the inheritance hierarchy. * * @since 1.1 */ public int getLockType() { // if this entity has an explicit lock, // no need to lookup inheritance hierarchy if (lockType != LOCK_TYPE_NONE) { return lockType; } ObjEntity superEntity = getSuperEntity(); return (superEntity != null) ? superEntity.getLockType() : lockType; } /** * Returns the type of lock used by this ObjEntity, regardless of what * locking type is used by super entities. * * @since 1.1 */ public int getDeclaredLockType() { return lockType; } /** * Sets the type of lock used by this ObjEntity. * * @since 1.1 */ public void setDeclaredLockType(int i) { lockType = i; } /** * Returns whether this entity is "generic", meaning it is not mapped to a * unique Java class. Criterion for generic entities is that it either has * no Java class mapped or its class is the same as DataMap's default * superclass, or it is CayenneDataObject. * * @since 1.2 */ public boolean isGeneric() { String className = getClassName(); return className == null || DEFAULT_GENERIC_CLASSES.contains(className) || (getDataMap() != null && className.equals(getDataMap().getDefaultSuperclass())); } /** * Returns true if this entity is allowed to be used on the client. Checks * that parent DataMap allows client entities and also that this entity is * not explicitly disabled for the client use. * * @since 1.2 */ public boolean isClientAllowed() { return getDataMap() != null && !isServerOnly() && getDataMap().isClientSupported(); } public boolean isAbstract() { return _abstract; } /** * Sets whether this entity is abstract only. */ public void setAbstract(boolean isAbstract) { this._abstract = isAbstract; } /** * Returns true if this entity is not available on the client. * * @since 1.2 */ public boolean isServerOnly() { return serverOnly; } /** * Sets whether this entity is available on the client. * * @since 1.2 */ public void setServerOnly(boolean serverOnly) { this.serverOnly = serverOnly; } /** * Returns a qualifier that imposes a restriction on what objects belong to * this entity. Returned qualifier is the one declared in this entity, and * does not include qualifiers declared in super entities. * * @since 1.1 */ public Expression getDeclaredQualifier() { return qualifier; } /** * Returns an entity name for a parent entity in the inheritance hierarchy. * * @since 1.1 */ public String getSuperEntityName() { return superEntityName; } /** * Sets a qualifier that imposes a limit on what objects belong to this * entity. * * @since 1.1 */ public void setDeclaredQualifier(Expression qualifier) { this.qualifier = qualifier; } /** * Sets an entity name for a parent entity in the inheritance hierarchy. * * @since 1.1 */ public void setSuperEntityName(String superEntityName) { this.superEntityName = superEntityName; } /** * Returns the name of DataObject class described by this entity. */ public String getClassName() { return className; } /** * Sets the name of the DataObject class described by this entity. */ public void setClassName(String className) { this.className = className; } /** * Returns the name of ClientDataObject class described by this entity. * * @since 1.2 */ public String getClientClassName() { return clientClassName; } /** * Sets the name of the ClientDataObject class described by this entity. * * @since 1.2 */ public void setClientClassName(String clientClassName) { this.clientClassName = clientClassName; } /** * Returns a fully-qualified name of the super class of the DataObject * class. This value is used as a hint for class generation. If the entity * inherits from another entity, a superclass is the class of that entity. */ public String getSuperClassName() { ObjEntity superEntity = getSuperEntity(); return (superEntity != null) ? superEntity.getClassName() : superClassName; } /** * Sets a fully-qualified name of the super class of the DataObject class. * This value is used as a hint for class generation. * <p> * <i>An attempt to set superclass on an inherited entity has no effect, * since a class of the super entity is always used as a superclass.</i> * </p> */ public void setSuperClassName(String superClassName) { this.superClassName = superClassName; } /** * Returns a fully-qualified name of the client-side super class of the * DataObject class. This value is used as a hint for class generation. If * the entity inherits from another entity, a superclass is the class of * that entity. * * @since 1.2 */ public String getClientSuperClassName() { ObjEntity superEntity = getSuperEntity(); return (superEntity != null) ? superEntity.getClientClassName() : clientSuperClassName; } /** * Sets a fully-qualified name of the client-side super class of the * ClientDataObject class. This value is used as a hint for class * generation. * <p> * <i>An attempt to set superclass on an inherited entity has no effect, * since a class of the super entity is always used as a superclass. </i> * </p> * * @since 1.2 */ public void setClientSuperClassName(String clientSuperClassName) { this.clientSuperClassName = clientSuperClassName; } /** * Returns a "super" entity in the entity inheritance hierarchy. * * @since 1.1 */ public ObjEntity getSuperEntity() { return (superEntityName != null) ? getNonNullNamespace().getObjEntity(superEntityName) : null; } /** * Returns a DbEntity associated with this ObjEntity. */ public DbEntity getDbEntity() { // since 1.2 - allow overriding DbEntity in the inheritance hierarchy... if (dbEntityName != null) { return getNonNullNamespace().getDbEntity(dbEntityName); } ObjEntity superEntity = getSuperEntity(); if (superEntity != null) { return superEntity.getDbEntity(); } return null; } /** * Sets the DbEntity used by this ObjEntity. * <p> * <i>Setting DbEntity on an inherited entity has no effect, since a class * of the super entity is always used as a superclass. </i> * </p> */ public void setDbEntity(DbEntity dbEntity) { this.dbEntityName = (dbEntity != null) ? dbEntity.getName() : null; } /** * Returns an unmodifiable collection of ObjAttributes representing the * primary key of the table described by this DbEntity. Note that since PK * is very often not an object property, the returned collection may contain * "synthetic" ObjAttributes that are created on the fly and are not a part * of ObjEntity and will not be a part of entity.getAttributes(). * * @since 3.0 */ public Collection<ObjAttribute> getPrimaryKeys() { return Collections.unmodifiableCollection(getMutablePrimaryKeys()); } private Collection<ObjAttribute> getMutablePrimaryKeys() { if (getDbEntity() == null) { throw new CayenneRuntimeException("No DbEntity for ObjEntity: %s", getName()); } Collection<DbAttribute> pkAttributes = getDbEntity().getPrimaryKeys(); Collection<ObjAttribute> attributes = new ArrayList<>(pkAttributes.size()); for (DbAttribute pk : pkAttributes) { ObjAttribute attribute = getAttributeForDbAttribute(pk); // create synthetic attribute if (attribute == null) { attribute = new SyntheticPKObjAttribute(Util.underscoredToJava(pk.getName(), false)); attribute.setDbAttributePath(pk.getName()); attribute.setType(TypesMapping.getJavaBySqlType(pk.getType())); } attributes.add(attribute); } return attributes; } /** * Returns a named attribute that is either declared in this ObjEntity or is * inherited. In any case returned attribute 'getEntity' method will return * this entity. Returns null if no matching attribute is found. */ @Override public ObjAttribute getAttribute(String name) { ObjAttribute attribute = (ObjAttribute) super.getAttribute(name); if (attribute != null) { return attribute; } // check embedded attribute int dot = name.indexOf('.'); if (dot > 0 && dot < name.length() - 1) { ObjAttribute embedded = getAttribute(name.substring(0, dot)); if (embedded instanceof EmbeddedAttribute) { return ((EmbeddedAttribute) embedded).getAttribute(name.substring(dot + 1)); } } // check super attribute ObjEntity superEntity = getSuperEntity(); if (superEntity != null) { ObjAttribute superAttribute = superEntity.getAttribute(name); if (superAttribute == null) { return null; } // decorate returned attribute to make it appear as if it belongs to // this // entity ObjAttribute decoratedAttribute = new ObjAttribute(superAttribute); decoratedAttribute.setEntity(this); String pathOverride = attributeOverrides.get(name); if (pathOverride != null) { decoratedAttribute.setDbAttributePath(pathOverride); } return decoratedAttribute; } return null; } /** * Returns a SortedMap of all attributes that either belong to this * ObjEntity or inherited. */ @Override public SortedMap<String, ObjAttribute> getAttributeMap() { if (superEntityName == null) { return getAttributeMapInternal(); } SortedMap<String, ObjAttribute> attributeMap = new TreeMap<>(); appendAttributes(attributeMap); return attributeMap; } /** * Recursively appends all attributes in the entity inheritance hierarchy. */ final void appendAttributes(Map<String, ObjAttribute> map) { map.putAll(getAttributeMapInternal()); ObjEntity superEntity = getSuperEntity(); if (superEntity != null) { SortedMap<String, ObjAttribute> attributeMap = new TreeMap<>(); superEntity.appendAttributes(attributeMap); for (String attributeName : attributeMap.keySet()) { String overridedDbPath = attributeOverrides.get(attributeName); ObjAttribute attribute = new ObjAttribute(attributeMap.get(attributeName)); attribute.setEntity(this); if (overridedDbPath != null) { attribute.setDbAttributePath(overridedDbPath); } map.put(attributeName, attribute); } } } @SuppressWarnings("unchecked") final SortedMap<String, ObjAttribute> getAttributeMapInternal() { return (SortedMap<String, ObjAttribute>) super.getAttributeMap(); } /** * @since 3.0 */ public void addAttributeOverride(String attributeName, String dbPath) { attributeOverrides.put(attributeName, dbPath); } /** * @since 4.0 */ public void removeAttributeOverride(String attributeName) { attributeOverrides.remove(attributeName); } /** * @since 3.0 */ public Map<String, String> getDeclaredAttributeOverrides() { return Collections.unmodifiableMap(attributeOverrides); } /** * Returns a Collection of all attributes that either belong to this * ObjEntity or inherited. */ @Override public Collection<ObjAttribute> getAttributes() { return getAttributeMap().values(); } /** * Returns a Collection of all attributes that belong to this ObjEntity, * excluding inherited attributes. * * @since 1.1 */ @SuppressWarnings("unchecked") public Collection<ObjAttribute> getDeclaredAttributes() { return (Collection<ObjAttribute>) super.getAttributes(); } /** * Finds attribute declared by this ObjEntity, * excluding inherited attributes. * * @param name of the attribute * @return declared attribute or null if no attribute is found * * @see ObjEntity#getAttribute(String) * * @since 4.0 */ public ObjAttribute getDeclaredAttribute(String name) { return (ObjAttribute) super.getAttribute(name); } /** * Returns a named Relationship that either belongs to this ObjEntity or is * inherited. Returns null if no matching attribute is found. */ @Override public ObjRelationship getRelationship(String name) { ObjRelationship relationship = (ObjRelationship) super.getRelationship(name); if (relationship != null) { return relationship; } if (superEntityName == null) { return null; } ObjEntity superEntity = getSuperEntity(); return (superEntity != null) ? superEntity.getRelationship(name) : null; } @Override public SortedMap<String, ObjRelationship> getRelationshipMap() { if (superEntityName == null) { return getRelationshipMapInternal(); } SortedMap<String, ObjRelationship> relationshipMap = new TreeMap<>(); appendRelationships(relationshipMap); return relationshipMap; } /** * Recursively appends all relationships in the entity inheritance * hierarchy. */ final void appendRelationships(Map<String, ObjRelationship> map) { map.putAll(getRelationshipMapInternal()); ObjEntity superEntity = getSuperEntity(); if (superEntity != null) { superEntity.appendRelationships(map); } } @Override public Collection<ObjRelationship> getRelationships() { return getRelationshipMap().values(); } @SuppressWarnings("unchecked") final SortedMap<String, ObjRelationship> getRelationshipMapInternal() { return (SortedMap<String, ObjRelationship>) super.getRelationshipMap(); } /** * Returns a Collection of all relationships that belong to this ObjEntity, * excluding inherited attributes. * * @since 1.1 */ @SuppressWarnings("unchecked") public Collection<ObjRelationship> getDeclaredRelationships() { return (Collection<ObjRelationship>) super.getRelationships(); } /** * Returns ObjAttribute of this entity that maps to <code>dbAttribute</code> * parameter. Returns null if no such attribute is found. */ public ObjAttribute getAttributeForDbAttribute(DbAttribute dbAttribute) { for (ObjAttribute next : getAttributeMap().values()) { if (next instanceof EmbeddedAttribute) { ObjAttribute embeddedAttribute = ((EmbeddedAttribute) next) .getAttributeForDbPath(dbAttribute.getName()); if (embeddedAttribute != null) { return embeddedAttribute; } } else { if (next.getDbAttribute() == dbAttribute) { return next; } } } return null; } /** * Returns the names of DbAtributes that comprise the primary key of the * parent DbEntity. * * @since 3.0 */ public Collection<String> getPrimaryKeyNames() { DbEntity dbEntity = getDbEntity(); // abstract entities may have no DbEntity mapping if (dbEntity == null) { return Collections.emptyList(); } Collection<DbAttribute> pkAttributes = dbEntity.getPrimaryKeys(); Collection<String> names = new ArrayList<>(pkAttributes.size()); for (DbAttribute pk : pkAttributes) { names.add(pk.getName()); } return Collections.unmodifiableCollection(names); } /** * Returns ObjRelationship of this entity that maps to * <code>dbRelationship</code> parameter. Returns null if no such * relationship is found. */ public ObjRelationship getRelationshipForDbRelationship(DbRelationship dbRelationship) { for (ObjRelationship objRel : getRelationshipMap().values()) { List<DbRelationship> relList = objRel.getDbRelationships(); if (relList.size() != 1) { continue; } if (relList.get(0) == dbRelationship) { return objRel; } } return null; } /** * Clears all the mapping between this obj entity and its current db entity. * Clears mapping between entities, attributes and relationships. */ public void clearDbMapping() { if (dbEntityName == null) { return; } for (ObjAttribute attribute : getAttributeMap().values()) { DbAttribute dbAttr = attribute.getDbAttribute(); if (dbAttr != null) { attribute.setDbAttributePath(null); } } for (ObjRelationship relationship : getRelationships()) { relationship.clearDbRelationships(); } dbEntityName = null; } /** * Returns <code>true</code> if this ObjEntity represents a set of read-only * objects. * * @return boolean */ public boolean isReadOnly() { return readOnly; } public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } /** * Returns true if this entity directly or indirectly inherits from a given * entity, false otherwise. * * @since 1.1 */ public boolean isSubentityOf(ObjEntity entity) { if (entity == null) { return false; } if (entity == this) { return false; } ObjEntity superEntity = getSuperEntity(); if (superEntity == entity) { return true; } return (superEntity != null) ? superEntity.isSubentityOf(entity) : false; } /** * @since 3.0 */ @Override @SuppressWarnings("unchecked") public PathComponent<ObjAttribute, ObjRelationship> 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<PathComponent<ObjAttribute, ObjRelationship>> resolvePath(final Expression pathExp, final Map aliasMap) { if (pathExp.getType() == Expression.OBJ_PATH) { return new Iterable<PathComponent<ObjAttribute, ObjRelationship>>() { public Iterator iterator() { return new PathComponentIterator(ObjEntity.this, (String) pathExp.getOperand(0), aliasMap); } }; } throw new ExpressionException("Invalid expression type: '" + pathExp.expName() + "', OBJ_PATH is expected."); } @Override public Iterator<CayenneMapEntry> resolvePathComponents(Expression pathExp) throws ExpressionException { // resolve DB_PATH if we can if (pathExp.getType() == Expression.DB_PATH) { if (getDbEntity() == null) { throw new ExpressionException("Can't resolve DB_PATH '" + pathExp + "', DbEntity is not set."); } return getDbEntity().resolvePathComponents(pathExp); } if (pathExp.getType() == Expression.OBJ_PATH) { return new PathIterator((String) pathExp.getOperand(0)); } throw new ExpressionException("Invalid expression type: '" + pathExp.expName() + "', OBJ_PATH is expected."); } /** * Transforms an Expression to an analogous expression in terms of the * underlying DbEntity. * * @since 1.1 */ public Expression translateToDbPath(Expression expression) { if (expression == null) { return null; } if (getDbEntity() == null) { throw new CayenneRuntimeException("Can't translate expression to DB_PATH, no DbEntity for '%s'.", getName()); } // converts all OBJ_PATH expressions to DB_PATH expressions // and pass control to the DB entity return expression.transform(new DBPathConverter()); } /** * Transforms an 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; } if (getDbEntity() == null) { throw new CayenneRuntimeException("Can't transform expression, no DbEntity for '%s'.", getName()); } // converts all OBJ_PATH expressions to DB_PATH expressions // and pass control to the DB entity DBPathConverter transformer = new DBPathConverter(); String dbPath = transformer.toDbPath(createPathIterator(relationshipPath)); Expression dbClone = expression.transform(transformer); return getDbEntity().translateToRelatedEntity(dbClone, dbPath); } private PathComponentIterator createPathIterator(String path) { return new PathComponentIterator(ObjEntity.this, path, new HashMap<String, String>()); // TODO: do we need aliases here? } /** * @since 4.0 */ public Set<String> getCallbackMethods() { Set<String> res = new LinkedHashSet<>(); for (CallbackDescriptor descriptor : getCallbackMap().getCallbacks()) { res.addAll(descriptor.getCallbackMethods()); } return res; } final class DBPathConverter implements Transformer { // TODO: make it a public method - resolveDBPathComponents or // something... // seems generally useful String toDbPath(PathComponentIterator objectPathComponents) { StringBuilder buf = new StringBuilder(); while (objectPathComponents.hasNext()) { PathComponent<Attribute, Relationship> component = objectPathComponents.next(); Iterator<?> dbSubpath; if (component.getAttribute() != null) { dbSubpath = ((ObjAttribute) component.getAttribute()).getDbPathIterator(); } else if (component.getRelationship() != null) { dbSubpath = ((ObjRelationship) component.getRelationship()).getDbRelationships().iterator(); } else { throw new CayenneRuntimeException("Unknown path component: %s", component); } while (dbSubpath.hasNext()) { CayenneMapEntry subComponent = (CayenneMapEntry) dbSubpath.next(); if (buf.length() > 0) { buf.append(Entity.PATH_SEPARATOR); } buf.append(subComponent.getName()); // use OUTER join for all components of the path is Obj path is OUTER if (component.getJoinType() == JoinType.LEFT_OUTER) { buf.append(OUTER_JOIN_INDICATOR); } } } return buf.toString(); } public Object transform(Object input) { if (!(input instanceof Expression)) { return input; } Expression expression = (Expression) input; if (expression.getType() != Expression.OBJ_PATH) { return input; } // convert obj_path to db_path String converted = toDbPath(createPathIterator((String) expression.getOperand(0))); Expression exp = ExpressionFactory.expressionOfType(Expression.DB_PATH); exp.setOperand(0, converted); return exp; } } /** * Returns the name of the underlying DbEntity. * * @since 1.1 */ public String getDbEntityName() { return dbEntityName; } /** * Sets the name of underlying DbEntity. * * @since 1.1 */ public void setDbEntityName(String string) { dbEntityName = string; } /** * ObjEntity property changed. 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 objEntityChanged(EntityEvent e) { if ((e == null) || (e.getEntity() != this)) { // not our concern return; } // handle entity name changes if (e.getId() == EntityEvent.CHANGE && e.isNameChange()) { String oldName = e.getOldName(); String newName = e.getNewName(); DataMap map = getDataMap(); if (map != null) { ObjEntity oe = (ObjEntity) e.getEntity(); for (ObjRelationship relationship : oe.getRelationships()) { relationship = relationship.getReverseRelationship(); if (null != relationship && relationship.targetEntityName.equals(oldName)) { relationship.targetEntityName = newName; } } } } } /** New entity has been created/added. */ public void objEntityAdded(EntityEvent e) { // does nothing currently } /** Entity has been removed. */ public void objEntityRemoved(EntityEvent e) { // does nothing currently } /** * Returns true if the default lifecycle listeners should not be notified of * this entity lifecycle events. * * @since 3.0 * @deprecated since 4.0 unused, as listeners are no longer mapped in a * DataMap. */ @Deprecated public boolean isExcludingDefaultListeners() { return excludingDefaultListeners; } /** * @deprecated since 4.0 unused, as listeners are no longer mapped in a * DataMap. */ @Deprecated public void setExcludingDefaultListeners(boolean excludingDefaultListeners) { this.excludingDefaultListeners = excludingDefaultListeners; } /** * Returns true if the lifeycle listeners defined on the superclasses should * not be notified of this entity lifecycle events. * * @since 3.0 * @deprecated since 4.0 unused, as listeners are no longer mapped in a * DataMap. */ @Deprecated public boolean isExcludingSuperclassListeners() { return excludingSuperclassListeners; } /** * @deprecated since 4.0 unused, as listeners are no longer mapped in a * DataMap. */ @Deprecated public void setExcludingSuperclassListeners(boolean excludingSuperclassListeners) { this.excludingSuperclassListeners = excludingSuperclassListeners; } }