All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cayenne.map.EntityResolver Maven / Gradle / Ivy

There is a newer version: 4.2.1
Show newest version
/*****************************************************************
 *   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.ObjectId;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.reflect.ClassDescriptor;
import org.apache.cayenne.reflect.ClassDescriptorMap;
import org.apache.cayenne.reflect.FaultFactory;
import org.apache.cayenne.reflect.LifecycleCallbackRegistry;
import org.apache.cayenne.reflect.SingletonFaultFactory;
import org.apache.cayenne.reflect.generic.DataObjectDescriptorFactory;
import org.apache.cayenne.reflect.valueholder.ValueHolderDescriptorFactory;
import org.apache.cayenne.util.Util;
import org.apache.commons.collections.collection.CompositeCollection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Represents a virtual shared namespace for zero or more DataMaps. Unlike DataMap,
 * EntityResolver is intended to work as a runtime container of mapping. DataMaps can be
 * added or removed dynamically at runtime.
 * 

* EntityResolver is thread-safe. *

* * @since 1.1 */ public class EntityResolver implements MappingNamespace, Serializable { static final ObjEntity DUPLICATE_MARKER = new ObjEntity(); protected static final Log logger = LogFactory.getLog(EntityResolver.class); protected static AtomicLong incrementer = new AtomicLong(); protected boolean indexedByClass; protected Collection maps; protected transient Map queryCache; protected transient Map embeddableCache; protected transient Map resultsCache; protected transient Map dbEntityCache; protected transient Map objEntityCache; protected transient Map procedureCache; protected transient Map entityInheritanceCache; protected EntityResolver clientEntityResolver; // must be transient, as resolver may get deserialized in another VM, and descriptor // recompilation will be desired. protected transient ClassDescriptorMap classDescriptorMap; // callbacks are not serializable protected transient LifecycleCallbackRegistry callbackRegistry; protected EntityListenerFactory entityListenerFactory; /** * Creates new EntityResolver. */ public EntityResolver() { init(); } /** * Initialization of EntityResolver. Used in constructor and in Java deserialization * process */ private void init() { this.indexedByClass = true; this.maps = new ArrayList(3); this.embeddableCache = new HashMap(); this.queryCache = new HashMap(); this.dbEntityCache = new HashMap(); this.objEntityCache = new HashMap(); this.procedureCache = new HashMap(); this.entityInheritanceCache = new HashMap(); this.resultsCache = new HashMap(); } /** * Creates new EntityResolver that indexes a collection of DataMaps. */ public EntityResolver(Collection dataMaps) { this(); this.maps.addAll(dataMaps); // Take a copy this.constructCache(); } /** * Updates missing mapping artifacts that can be guessed from other mapping * information. This implementation creates missing reverse relationships, marking * newly created relationships as "runtime". * * @since 3.0 */ public void applyDBLayerDefaults() { // connect DB layer for (DataMap map : getDataMaps()) { for (DbEntity entity : map.getDbEntities()) { // iterate by copy to avoid concurrency modification errors on reflexive // relationships Object[] relationships = entity.getRelationships().toArray(); for (int i = 0; i < relationships.length; i++) { DbRelationship relationship = (DbRelationship) relationships[i]; if (relationship.getReverseRelationship() == null) { DbRelationship reverse = relationship.createReverseRelationship(); Entity targetEntity = reverse.getSourceEntity(); reverse.setName(getUniqueRelationshipName(targetEntity)); reverse.setRuntime(true); targetEntity.addRelationship(reverse); logger.info("added runtime complimentary DbRelationship from " + targetEntity.getName() + " to " + reverse.getTargetEntityName()); } } } } } /** * Updates missing mapping artifacts that can be guessed from other mapping * information. This implementation creates missing reverse relationships, marking * newly created relationships as "runtime". * * @since 3.0 */ public void applyObjectLayerDefaults() { // connect object layer for (DataMap map : getDataMaps()) { for (ObjEntity entity : map.getObjEntities()) { // iterate by copy to avoid concurrency modification errors on reflexive // relationships Object[] relationships = entity.getRelationships().toArray(); for (int i = 0; i < relationships.length; i++) { ObjRelationship relationship = (ObjRelationship) relationships[i]; if (relationship.getReverseRelationship() == null) { ObjRelationship reverse = relationship .createReverseRelationship(); Entity targetEntity = reverse.getSourceEntity(); reverse.setName(getUniqueRelationshipName(targetEntity)); reverse.setRuntime(true); targetEntity.addRelationship(reverse); logger.info("added runtime complimentary ObjRelationship from " + targetEntity.getName() + " to " + reverse.getTargetEntityName()); } } } } } private String getUniqueRelationshipName(Entity entity) { String name; do { name = "runtimeRelationship" + incrementer.getAndIncrement(); } while(entity.getRelationship(name) != null); return name; } /** * Compiles internal callback registry. */ synchronized void initCallbacks() { if (callbackRegistry == null) { LifecycleCallbackRegistry callbackRegistry = new LifecycleCallbackRegistry( this); // load default callbacks for (DataMap map : maps) { for (EntityListener listener : map.getDefaultEntityListeners()) { Object listenerInstance = createListener(listener, null); if (listenerInstance == null) { continue; } CallbackDescriptor[] callbacks = listener .getCallbackMap() .getCallbacks(); for (CallbackDescriptor callback : callbacks) { for (String method : callback.getCallbackMethods()) { // note that callbacks[i].getCallbackType() == i callbackRegistry.addDefaultListener(callback .getCallbackType(), listenerInstance, method); } } } } // load entity callbacks for (ObjEntity entity : getObjEntities()) { Class entityClass = entity.getJavaClass(); // external listeners go first, entity's own callbacks go next for (EntityListener listener : entity.getEntityListeners()) { Object listenerInstance = createListener(listener, entity); if (listenerInstance == null) { continue; } CallbackDescriptor[] callbacks = listener .getCallbackMap() .getCallbacks(); for (CallbackDescriptor callback : callbacks) { for (String method : callback.getCallbackMethods()) { callbackRegistry.addListener( callback.getCallbackType(), entityClass, listenerInstance, method); } } } CallbackDescriptor[] callbacks = entity.getCallbackMap().getCallbacks(); for (CallbackDescriptor callback : callbacks) { for (String method : callback.getCallbackMethods()) { callbackRegistry.addListener( callback.getCallbackType(), entityClass, method); } } } this.callbackRegistry = callbackRegistry; } } /** * Creates a listener instance. */ private Object createListener(EntityListener listener, ObjEntity entity) { if (entityListenerFactory != null) { return entityListenerFactory.createListener(listener, entity); } Class listenerClass; try { listenerClass = Util.getJavaClass(listener.getClassName()); } catch (ClassNotFoundException e) { throw new CayenneRuntimeException("Invalid listener class: " + listener.getClassName(), e); } try { return listenerClass.newInstance(); } catch (Exception e) { throw new CayenneRuntimeException("Listener class " + listener.getClassName() + " default constructor call failed", e); } } /** * Returns a {@link LifecycleCallbackRegistry} for handling callbacks. Registry is * lazily initialized on first call. * * @since 3.0 */ public LifecycleCallbackRegistry getCallbackRegistry() { if (callbackRegistry == null) { initCallbacks(); } return callbackRegistry; } /** * Sets a lifecycle callbacks registry of the EntityResolver. Users rarely if ever * need to call this method as Cayenne would instantiate a registry itself as needed * based on mapped configuration. * * @since 3.0 */ public void setCallbackRegistry(LifecycleCallbackRegistry callbackRegistry) { this.callbackRegistry = callbackRegistry; } /** * Returns ClientEntityResolver with mapping information that only includes entities * available on CWS Client Tier. * * @since 1.2 */ public EntityResolver getClientEntityResolver() { if (clientEntityResolver == null) { synchronized (this) { if (clientEntityResolver == null) { EntityResolver resolver = new ClientEntityResolver(); // translate to client DataMaps for (DataMap map : getDataMaps()) { DataMap clientMap = map.getClientDataMap(this); if (clientMap != null) { resolver.addDataMap(clientMap); } } clientEntityResolver = resolver; } } } return clientEntityResolver; } /** * Returns all DbEntities. */ public Collection getDbEntities() { CompositeCollection c = new CompositeCollection(); for (DataMap map : getDataMaps()) { c.addComposited(map.getDbEntities()); } return c; } public Collection getObjEntities() { CompositeCollection c = new CompositeCollection(); for (DataMap map : getDataMaps()) { c.addComposited(map.getObjEntities()); } return c; } /** * @since 3.0 */ public Collection getEmbeddables() { CompositeCollection c = new CompositeCollection(); for (DataMap map : getDataMaps()) { c.addComposited(map.getEmbeddables()); } return c; } /** * @since 3.0 */ public Collection getResultSets() { CompositeCollection c = new CompositeCollection(); for (DataMap map : getDataMaps()) { c.addComposited(map.getResults()); } return c; } public Collection getProcedures() { CompositeCollection c = new CompositeCollection(); for (DataMap map : getDataMaps()) { c.addComposited(map.getProcedures()); } return c; } public Collection getQueries() { CompositeCollection c = new CompositeCollection(); for (DataMap map : getDataMaps()) { c.addComposited(map.getQueries()); } return c; } public DbEntity getDbEntity(String name) { return _lookupDbEntity(name); } public ObjEntity getObjEntity(String name) { return _lookupObjEntity(name); } public Procedure getProcedure(String name) { return lookupProcedure(name); } public Query getQuery(String name) { return lookupQuery(name); } /** * @since 3.0 */ public Embeddable getEmbeddable(String className) { Embeddable result = embeddableCache.get(className); if (result == null) { // reconstruct cache just in case some of the datamaps // have changed and now contain the required information constructCache(); result = embeddableCache.get(className); } return result; } /** * @since 3.0 */ public SQLResult getResult(String name) { SQLResult result = resultsCache.get(name); if (result == null) { // reconstruct cache just in case some of the datamaps // have changed and now contain the required information constructCache(); result = resultsCache.get(name); } return result; } /** * Returns ClassDescriptor for the ObjEntity matching the name. Returns null if no * matching entity exists. * * @since 1.2 */ public synchronized ClassDescriptor getClassDescriptor(String entityName) { if (entityName == null) { throw new IllegalArgumentException("Null entityName"); } return getClassDescriptorMap().getDescriptor(entityName); } public synchronized void addDataMap(DataMap map) { if (!maps.contains(map)) { maps.add(map); map.setNamespace(this); clearCache(); } } /** * Removes all entity mappings from the cache. Cache can be rebuilt either explicitly * by calling constructCache, or on demand by calling any of the * lookup... methods. */ public synchronized void clearCache() { queryCache.clear(); dbEntityCache.clear(); objEntityCache.clear(); procedureCache.clear(); entityInheritanceCache.clear(); resultsCache.clear(); embeddableCache.clear(); clientEntityResolver = null; } /** * Creates caches of DbEntities by ObjEntity, DataObject class, and ObjEntity name * using internal list of maps. */ protected synchronized void constructCache() { clearCache(); // rebuild index // index DbEntities separately and before ObjEntities to avoid infinite loops when // looking up DbEntities during ObjEntity index op for (DataMap map : maps) { for (DbEntity de : map.getDbEntities()) { dbEntityCache.put(de.getName(), de); } } for (DataMap map : maps) { // index ObjEntities for (ObjEntity oe : map.getObjEntities()) { // index by name objEntityCache.put(oe.getName(), oe); // index by class.. use class name as a key to avoid class loading here... if (indexedByClass) { String className = oe.getJavaClassName(); if (className == null) { continue; } String classKey = classKey(className); // allow duplicates, but put a special marker indicating that this // entity can't be looked up by class Object existing = objEntityCache.get(classKey); if (existing != null) { if (existing != DUPLICATE_MARKER) { objEntityCache.put(classKey, DUPLICATE_MARKER); } } else { objEntityCache.put(classKey, oe); } } } // index stored procedures for (Procedure proc : map.getProcedures()) { procedureCache.put(proc.getName(), proc); } // index embeddables embeddableCache.putAll(map.getEmbeddableMap()); // index queries for (Query query : map.getQueries()) { String name = query.getName(); Object existingQuery = queryCache.put(name, query); if (existingQuery != null && query != existingQuery) { throw new CayenneRuntimeException("More than one Query for name" + name); } } } // restart the map iterator to index inheritance for (DataMap map : maps) { // index ObjEntity inheritance for (ObjEntity oe : map.getObjEntities()) { // build inheritance tree EntityInheritanceTree node = entityInheritanceCache.get(oe.getName()); if (node == null) { node = new EntityInheritanceTree(oe); entityInheritanceCache.put(oe.getName(), node); } String superOEName = oe.getSuperEntityName(); if (superOEName != null) { EntityInheritanceTree superNode = entityInheritanceCache .get(superOEName); if (superNode == null) { // do direct entity lookup to avoid recursive cache rebuild ObjEntity superOE = objEntityCache.get(superOEName); if (superOE != null) { superNode = new EntityInheritanceTree(superOE); entityInheritanceCache.put(superOEName, superNode); } else { // bad mapping? Or most likely some classloader issue logger.warn("No super entity mapping for '" + superOEName + "'"); continue; } } superNode.addChildNode(node); } } } } /** * Returns a DataMap matching the name. */ public synchronized DataMap getDataMap(String mapName) { if (mapName == null) { return null; } for (DataMap map : maps) { if (mapName.equals(map.getName())) { return map; } } return null; } public synchronized void setDataMaps(Collection maps) { this.maps.clear(); this.maps.addAll(maps); clearCache(); } /** * Returns an unmodifiable collection of DataMaps. */ public Collection getDataMaps() { return Collections.unmodifiableCollection(maps); } /** * Returns EntityInheritanceTree representing inheritance hierarchy that starts with a * given ObjEntity as root, and includes all its subentities. * * @deprecated since 3.1 use {@link #lookupInheritanceTree(String)}. */ @Deprecated public EntityInheritanceTree lookupInheritanceTree(ObjEntity entity) { return lookupInheritanceTree(entity.getName()); } /** * Returns EntityInheritanceTree representing inheritance hierarchy that starts with a * given ObjEntity as root, and includes all its subentities. Returns non-null object * for all existing entities, even those that don't have super or subclasses. * * @since 3.0 */ public EntityInheritanceTree lookupInheritanceTree(String entityName) { EntityInheritanceTree tree = entityInheritanceCache.get(entityName); if (tree == null) { // since we keep inheritance trees for all entities, null means // unknown entity... // rebuild cache just in case some of the datamaps // have changed and now contain the required information constructCache(); tree = entityInheritanceCache.get(entityName); } return tree; } /** * Looks in the DataMap's that this object was created with for the ObjEntity that * maps to the services the specified class * * @return the required ObjEntity or null if there is none that matches the specifier */ public synchronized ObjEntity lookupObjEntity(Class aClass) { if (!indexedByClass) { throw new CayenneRuntimeException("Class index is disabled."); } return _lookupObjEntity(classKey(aClass.getName())); } /** * Looks in the DataMap's that this object was created with for the ObjEntity that * services the specified data Object * * @return the required ObjEntity, or null if none matches the specifier */ public synchronized ObjEntity lookupObjEntity(Object object) { if (object instanceof ObjEntity) { return (ObjEntity) object; } if (object instanceof Persistent) { ObjectId id = ((Persistent) object).getObjectId(); if (id != null) { return _lookupObjEntity(id.getEntityName()); } } else if (object instanceof Class) { return lookupObjEntity((Class) object); } return lookupObjEntity(object.getClass()); } public Procedure lookupProcedure(Query q) { return q.getMetaData(this).getProcedure(); } public Procedure lookupProcedure(String procedureName) { Procedure result = procedureCache.get(procedureName); if (result == null) { // reconstruct cache just in case some of the datamaps // have changed and now contain the required information constructCache(); result = procedureCache.get(procedureName); } return result; } /** * Returns a named query or null if no query exists for a given name. */ public synchronized Query lookupQuery(String name) { Query result = queryCache.get(name); if (result == null) { // reconstruct cache just in case some of the datamaps // have changed and now contain the required information constructCache(); result = queryCache.get(name); } return result; } public synchronized void removeDataMap(DataMap map) { if (maps.remove(map)) { clearCache(); } } public boolean isIndexedByClass() { return indexedByClass; } public void setIndexedByClass(boolean b) { indexedByClass = b; } /** * Generates a map key for the object class. * * @since 3.0 */ protected String classKey(String className) { // need to ensure that there is no conflict with entity names... I guess such // prefix is enough to guarantee that: return "^cl^" + className; } /** * Internal usage only - provides the type-unsafe implementation which services the * four typesafe public lookupDbEntity methods Looks in the DataMap's that this object * was created with for the ObjEntity that maps to the specified object. Object may be * a Entity name, ObjEntity, DataObject class (Class object for a class which * implements the DataObject interface), or a DataObject instance itself * * @return the required DbEntity, or null if none matches the specifier */ protected DbEntity _lookupDbEntity(Object object) { if (object instanceof DbEntity) { return (DbEntity) object; } Object result = dbEntityCache.get(object); if (result == null) { // reconstruct cache just in case some of the datamaps // have changed and now contain the required information constructCache(); result = dbEntityCache.get(object); } if (result == DUPLICATE_MARKER) { throw new CayenneRuntimeException( "Can't perform lookup. There is more than one DbEntity mapped to " + object); } return (DbEntity) result; } /** * Internal usage only - provides the type-unsafe implementation which services the * three typesafe public lookupObjEntity methods Looks in the DataMap's that this * object was created with for the ObjEntity that maps to the specified object. Object * may be a Entity name, DataObject instance or DataObject class (Class object for a * class which implements the DataObject interface) * * @return the required ObjEntity or null if there is none that matches the specifier */ protected ObjEntity _lookupObjEntity(String key) { Object result = objEntityCache.get(key); if (result == null) { // reconstruct cache just in case some of the datamaps // have changed and now contain the required information constructCache(); result = objEntityCache.get(key); } if (result == DUPLICATE_MARKER) { throw new CayenneRuntimeException( "Can't perform lookup. There is more than one ObjEntity mapped to " + key); } return (ObjEntity) result; } /** * Returns an object that compiles and stores {@link ClassDescriptor} instances for * all entities. * * @since 3.0 */ public ClassDescriptorMap getClassDescriptorMap() { if (classDescriptorMap == null) { ClassDescriptorMap classDescriptorMap = new ClassDescriptorMap(this); FaultFactory faultFactory = new SingletonFaultFactory(); // add factories in reverse of the desired chain order classDescriptorMap.addFactory(new ValueHolderDescriptorFactory( classDescriptorMap)); classDescriptorMap.addFactory(new DataObjectDescriptorFactory( classDescriptorMap, faultFactory)); // since ClassDescriptorMap is not synchronized, we need to prefill it with // entity proxies here. for (DataMap map : maps) { for (String entityName : map.getObjEntityMap().keySet()) { classDescriptorMap.getDescriptor(entityName); } } this.classDescriptorMap = classDescriptorMap; } return classDescriptorMap; } /** * Sets an optional {@link EntityListenerFactory} that should be used to create entity * listeners. Note that changing the factory does not affect already created * listeners. So refresh the existing listners, call "setCallbackRegistry(null)" after * setting the listener. * * @since 3.0 */ public void setEntityListenerFactory(EntityListenerFactory entityListenerFactory) { this.entityListenerFactory = entityListenerFactory; } /** * Java default deserialization seems not to invoke constructor by default - invoking * it manually */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { init(); in.defaultReadObject(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy