org.apache.cayenne.access.DataDomain Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cayenne-client-nodeps
Show all versions of cayenne-client-nodeps
Cayenne Object Persistence Framework
/*****************************************************************
* 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.access;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.collections.Transformer;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.log4j.Logger;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.QueryResponse;
import org.apache.cayenne.event.EventManager;
import org.apache.cayenne.graph.CompoundDiff;
import org.apache.cayenne.graph.GraphDiff;
import org.apache.cayenne.map.AshwoodEntitySorter;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.map.EntitySorter;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.QueryChain;
import org.apache.cayenne.util.Util;
/**
* DataDomain performs query routing functions in Cayenne. DataDomain creates single data
* source abstraction hiding multiple physical data sources from the user. When a child
* DataContext sends a query to the DataDomain, it is transparently routed to an
* appropriate DataNode.
*
* For more information see Cayenne User Guide.
*
*
* @author Andrus Adamchik
*/
public class DataDomain implements QueryEngine, DataChannel {
private static final Logger logObj = Logger.getLogger(DataDomain.class);
public static final String SHARED_CACHE_ENABLED_PROPERTY = "cayenne.DataDomain.sharedCache";
public static final boolean SHARED_CACHE_ENABLED_DEFAULT = true;
public static final String VALIDATING_OBJECTS_ON_COMMIT_PROPERTY = "cayenne.DataDomain.validatingObjectsOnCommit";
public static final boolean VALIDATING_OBJECTS_ON_COMMIT_DEFAULT = true;
public static final String USING_EXTERNAL_TRANSACTIONS_PROPERTY = "cayenne.DataDomain.usingExternalTransactions";
public static final boolean USING_EXTERNAL_TRANSACTIONS_DEFAULT = false;
/**
* Defines a property name for storing an optional DataContextFactory.
*
* @since 1.2
*/
public static final String DATA_CONTEXT_FACTORY_PROPERTY = "cayenne.DataDomain.dataContextFactory";
/** Stores mapping of data nodes to DataNode name keys. */
protected Map nodes = Collections.synchronizedMap(new TreeMap());
protected Map nodesByDataMapName = Collections.synchronizedMap(new HashMap());
/**
* Properties configured for DataDomain. These include properties of the DataRowStore
* and remote notifications.
*/
protected Map properties = Collections.synchronizedMap(new TreeMap());
protected EntityResolver entityResolver;
protected DataRowStore sharedSnapshotCache;
protected TransactionDelegate transactionDelegate;
protected DataContextFactory dataContextFactory;
protected String name;
// these are initializable from properties...
protected boolean sharedCacheEnabled;
protected boolean validatingObjectsOnCommit;
protected boolean usingExternalTransactions;
/**
* @since 1.2
*/
protected EventManager eventManager;
/**
* @since 1.2
*/
EntitySorter entitySorter;
/**
* Creates a DataDomain and assigns it a name.
*/
public DataDomain(String name) {
DataContextFaults.init();
setName(name);
resetProperties();
}
/**
* Creates new DataDomain.
*
* @param name DataDomain name. Domain can be located using its name in the
* Configuration object.
* @param properties A Map containing domain configuration properties.
*/
public DataDomain(String name, Map properties) {
DataContextFaults.init();
setName(name);
initWithProperties(properties);
}
/**
* @since 1.2
*/
// TODO: andrus, 4/12/2006 - after 1.2 API freeze is over, replace DataNode
// EntitySorter with this one ... maybe even make it a part of server-side
// EntityResolver?
EntitySorter getEntitySorter() {
if (entitySorter == null) {
synchronized (this) {
if (entitySorter == null) {
// backwards compatibility mode... only possible in a single-node case
// see TODO above
if (nodes.size() == 1) {
entitySorter = ((DataNode) nodes.values().iterator().next())
.getEntitySorter();
}
else {
entitySorter = new AshwoodEntitySorter(getDataMaps());
}
}
}
}
return entitySorter;
}
/**
* Exists as a backdoor to override domain sorter until the sorter API is moved from
* DataNode.
*
* @since 1.2
*/
void setEntitySorter(EntitySorter entitySorter) {
this.entitySorter = entitySorter;
}
/**
* @since 1.1
*/
protected void resetProperties() {
if (properties != null) {
properties.clear();
}
sharedCacheEnabled = SHARED_CACHE_ENABLED_DEFAULT;
validatingObjectsOnCommit = VALIDATING_OBJECTS_ON_COMMIT_DEFAULT;
usingExternalTransactions = USING_EXTERNAL_TRANSACTIONS_DEFAULT;
dataContextFactory = null;
}
/**
* Reinitializes domain state with a new set of properties.
*
* @since 1.1
*/
public void initWithProperties(Map properties) {
// create map with predictable modification and synchronization behavior
Map localMap = new HashMap();
if (properties != null) {
localMap.putAll(properties);
}
this.properties = localMap;
Object sharedCacheEnabled = localMap.get(SHARED_CACHE_ENABLED_PROPERTY);
Object validatingObjectsOnCommit = localMap
.get(VALIDATING_OBJECTS_ON_COMMIT_PROPERTY);
Object usingExternalTransactions = localMap
.get(USING_EXTERNAL_TRANSACTIONS_PROPERTY);
Object dataContextFactory = localMap.get(DATA_CONTEXT_FACTORY_PROPERTY);
if (logObj.isDebugEnabled()) {
logObj.debug("DataDomain property "
+ SHARED_CACHE_ENABLED_PROPERTY
+ " = "
+ sharedCacheEnabled);
logObj.debug("DataDomain property "
+ VALIDATING_OBJECTS_ON_COMMIT_PROPERTY
+ " = "
+ validatingObjectsOnCommit);
logObj.debug("DataDomain property "
+ USING_EXTERNAL_TRANSACTIONS_PROPERTY
+ " = "
+ usingExternalTransactions);
logObj.debug("DataDomain property "
+ DATA_CONTEXT_FACTORY_PROPERTY
+ " = "
+ dataContextFactory);
}
// init ivars from properties
this.sharedCacheEnabled = (sharedCacheEnabled != null)
? "true".equalsIgnoreCase(sharedCacheEnabled.toString())
: SHARED_CACHE_ENABLED_DEFAULT;
this.validatingObjectsOnCommit = (validatingObjectsOnCommit != null)
? "true".equalsIgnoreCase(validatingObjectsOnCommit.toString())
: VALIDATING_OBJECTS_ON_COMMIT_DEFAULT;
this.usingExternalTransactions = (usingExternalTransactions != null)
? "true".equalsIgnoreCase(usingExternalTransactions.toString())
: USING_EXTERNAL_TRANSACTIONS_DEFAULT;
if (dataContextFactory != null
&& !Util.isEmptyString(dataContextFactory.toString())) {
try {
Class factoryClass = Class.forName(
dataContextFactory.toString(),
true,
Thread.currentThread().getContextClassLoader());
if (!DataContextFactory.class.isAssignableFrom(factoryClass)) {
throw new CayenneRuntimeException(
"Error loading DataContextFactory - factory class does not implement 'DataContextFactory': "
+ factoryClass.getName());
}
this.dataContextFactory = (DataContextFactory) factoryClass.newInstance();
}
catch (CayenneRuntimeException e) {
throw e;
}
catch (Exception e) {
throw new CayenneRuntimeException("Error loading DataContextFactory: "
+ dataContextFactory, e);
}
}
else {
this.dataContextFactory = null;
}
}
/**
* Returns EventManager used by this DataDomain.
*
* @since 1.2
*/
public EventManager getEventManager() {
return eventManager;
}
/**
* Sets EventManager used by this DataDomain.
*
* @since 1.2
*/
public void setEventManager(EventManager eventManager) {
this.eventManager = eventManager;
if (sharedSnapshotCache != null) {
sharedSnapshotCache.setEventManager(eventManager);
}
}
/**
* Returns "name" property value.
*/
public String getName() {
return name;
}
/**
* Sets "name" property to a new value.
*/
public synchronized void setName(String name) {
this.name = name;
if (sharedSnapshotCache != null) {
this.sharedSnapshotCache.setName(name);
}
}
/**
* Returns true
if DataContexts produced by this DataDomain are using
* shared DataRowStore. Returns false
if each DataContext would work
* with its own DataRowStore.
*/
public boolean isSharedCacheEnabled() {
return sharedCacheEnabled;
}
public void setSharedCacheEnabled(boolean sharedCacheEnabled) {
this.sharedCacheEnabled = sharedCacheEnabled;
}
/**
* Returns whether child DataContexts default behavior is to perform object validation
* before commit is executed.
*
* @since 1.1
*/
public boolean isValidatingObjectsOnCommit() {
return validatingObjectsOnCommit;
}
/**
* Sets the property defining whether child DataContexts should perform object
* validation before commit is executed.
*
* @since 1.1
*/
public void setValidatingObjectsOnCommit(boolean flag) {
this.validatingObjectsOnCommit = flag;
}
/**
* Returns whether this DataDomain should internally commit all transactions, or let
* container do that.
*
* @since 1.1
*/
public boolean isUsingExternalTransactions() {
return usingExternalTransactions;
}
/**
* Sets a property defining whether this DataDomain should internally commit all
* transactions, or let container do that.
*
* @since 1.1
*/
public void setUsingExternalTransactions(boolean flag) {
this.usingExternalTransactions = flag;
}
/**
* @since 1.1
* @return a Map of properties for this DataDomain. There is no guarantees of specific
* synchronization behavior of this map.
*/
public Map getProperties() {
return properties;
}
/**
* @since 1.1
* @return TransactionDelegate associated with this DataDomain, or null if no delegate
* exist.
*/
public TransactionDelegate getTransactionDelegate() {
return transactionDelegate;
}
/**
* Initializes TransactionDelegate used by all DataContexts associated with this
* DataDomain.
*
* @since 1.1
*/
public void setTransactionDelegate(TransactionDelegate transactionDelegate) {
this.transactionDelegate = transactionDelegate;
}
/**
* Returns snapshots cache for this DataDomain, lazily initializing it on the first
* call.
*/
public synchronized DataRowStore getSharedSnapshotCache() {
if (sharedSnapshotCache == null && sharedCacheEnabled) {
this.sharedSnapshotCache = new DataRowStore(name, properties, eventManager);
}
return sharedSnapshotCache;
}
/**
* Shuts down the previous cache instance, sets cache to the new DataSowStore instance
* and updates two properties of the new DataSowStore: name and eventManager.
*/
public synchronized void setSharedSnapshotCache(DataRowStore snapshotCache) {
if (this.sharedSnapshotCache != snapshotCache) {
if (this.sharedSnapshotCache != null) {
this.sharedSnapshotCache.shutdown();
}
this.sharedSnapshotCache = snapshotCache;
if (snapshotCache != null) {
snapshotCache.setEventManager(getEventManager());
snapshotCache.setName(getName());
}
}
}
public DataContextFactory getDataContextFactory() {
return dataContextFactory;
}
public void setDataContextFactory(DataContextFactory dataContextFactory) {
this.dataContextFactory = dataContextFactory;
}
/** Registers new DataMap with this domain. */
public void addMap(DataMap map) {
getEntityResolver().addDataMap(map);
entitySorter = null;
}
/** Returns DataMap matching name
parameter. */
public DataMap getMap(String mapName) {
return getEntityResolver().getDataMap(mapName);
}
/**
* Removes named DataMap from this DataDomain and any underlying DataNodes that
* include it.
*/
public synchronized void removeMap(String mapName) {
DataMap map = getMap(mapName);
if (map == null) {
return;
}
// remove from data nodes
Iterator it = nodes.values().iterator();
while (it.hasNext()) {
DataNode node = (DataNode) it.next();
node.removeDataMap(mapName);
}
// remove from EntityResolver
getEntityResolver().removeDataMap(map);
entitySorter = null;
// reindex nodes to remove references on removed map entities
reindexNodes();
}
/**
* Removes a DataNode from DataDomain. Any maps previously associated with this node
* within domain will still be kept around, however they wan't be mapped to any node.
*/
public synchronized void removeDataNode(String nodeName) {
DataNode removed = (DataNode) nodes.remove(nodeName);
if (removed != null) {
removed.setEntityResolver(null);
Iterator it = nodesByDataMapName.values().iterator();
while (it.hasNext()) {
if (it.next() == removed) {
it.remove();
}
}
}
}
/**
* Returns a collection of registered DataMaps.
*/
public Collection getDataMaps() {
return getEntityResolver().getDataMaps();
}
/**
* Returns an unmodifiable collection of DataNodes associated with this domain.
*/
public Collection getDataNodes() {
return Collections.unmodifiableCollection(nodes.values());
}
/**
* Closes all data nodes, removes them from the list of available nodes.
*/
public void reset() {
synchronized (nodes) {
nodes.clear();
nodesByDataMapName.clear();
if (entityResolver != null) {
entityResolver.clearCache();
entityResolver = null;
}
}
}
/**
* Clears the list of internal DataMaps. In most cases it is wise to call "reset"
* before doing that.
*/
public void clearDataMaps() {
getEntityResolver().setDataMaps(Collections.EMPTY_LIST);
}
/**
* Adds new DataNode.
*/
public synchronized void addNode(DataNode node) {
// add node to name->node map
nodes.put(node.getName(), node);
node.setEntityResolver(this.getEntityResolver());
// add node to "ent name->node" map
Iterator nodeMaps = node.getDataMaps().iterator();
while (nodeMaps.hasNext()) {
DataMap map = (DataMap) nodeMaps.next();
this.addMap(map);
this.nodesByDataMapName.put(map.getName(), node);
}
entitySorter = null;
}
/**
* Creates and returns a new DataContext. If this DataDomain is configured to use
* shared cache, returned DataContext will use shared cache as well. Otherwise a new
* instance of DataRowStore will be used as its local cache.
*/
public DataContext createDataContext() {
return createDataContext(isSharedCacheEnabled());
}
/**
* Creates a new DataContext.
*
* @param useSharedCache determines whether resulting DataContext should use shared
* vs. local cache. This setting overrides default behavior configured for
* this DataDomain via {@link #SHARED_CACHE_ENABLED_PROPERTY}.
* @since 1.1
*/
public DataContext createDataContext(boolean useSharedCache) {
// for new dataRowStores use the same name for all stores
// it makes it easier to track the event subject
DataRowStore snapshotCache = (useSharedCache)
? getSharedSnapshotCache()
: new DataRowStore(name, properties, eventManager);
DataContext context;
if (null == dataContextFactory) {
context = new DataContext((DataChannel) this, new ObjectStore(snapshotCache));
}
else {
context = dataContextFactory.createDataContext(this, new ObjectStore(
snapshotCache));
}
context.setValidatingObjectsOnCommit(isValidatingObjectsOnCommit());
return context;
}
/**
* Creates and returns a new inactive transaction. Returned transaction is bound to
* the current execution thread.
*
* If there is a TransactionDelegate, adds the delegate to the newly created
* Transaction. Behavior of the returned Transaction depends on
* "usingInternalTransactions" property setting.
*
*
* @since 1.1
*/
public Transaction createTransaction() {
return (isUsingExternalTransactions()) ? Transaction
.externalTransaction(getTransactionDelegate()) : Transaction
.internalTransaction(getTransactionDelegate());
}
/**
* Returns registered DataNode whose name matches name
parameter.
*/
public DataNode getNode(String nodeName) {
return (DataNode) nodes.get(nodeName);
}
/**
* Updates internal index of DataNodes stored by the entity name.
*/
public synchronized void reindexNodes() {
nodesByDataMapName.clear();
Iterator nodes = this.getDataNodes().iterator();
while (nodes.hasNext()) {
DataNode node = (DataNode) nodes.next();
Iterator nodeMaps = node.getDataMaps().iterator();
while (nodeMaps.hasNext()) {
DataMap map = (DataMap) nodeMaps.next();
addMap(map);
nodesByDataMapName.put(map.getName(), node);
}
}
}
/**
* Returns a DataNode that should handle queries for all entities in a DataMap.
*
* @since 1.1
*/
public DataNode lookupDataNode(DataMap map) {
synchronized (nodesByDataMapName) {
DataNode node = (DataNode) nodesByDataMapName.get(map.getName());
if (node == null) {
reindexNodes();
return (DataNode) nodesByDataMapName.get(map.getName());
}
else {
return node;
}
}
}
/**
* @since 1.1
* @deprecated since 1.2, as the corresponding interface method is deprecated.
*/
public void performQueries(
Collection queries,
OperationObserver callback,
Transaction transaction) {
if (queries.isEmpty()) {
return;
}
Transaction old = Transaction.getThreadTransaction();
Transaction.bindThreadTransaction(transaction);
try {
new DataDomainLegacyQueryAction(this, new QueryChain(queries), callback)
.execute();
}
finally {
Transaction.bindThreadTransaction(old);
}
}
/**
* Sets EntityResolver. If not set explicitly, DataDomain creates a default
* EntityResolver internally on demand.
*
* @since 1.1
*/
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
}
// creates default entity resolver if there is none set yet
private synchronized void createEntityResolver() {
if (entityResolver == null) {
// entity resolver will be self-indexing as we add all our maps
// to it as they are added to the DataDomain
entityResolver = new EntityResolver();
}
}
/**
* @return PrimaryKeyHelper
* @deprecated unused since 1.2
*/
public synchronized org.apache.cayenne.access.util.PrimaryKeyHelper getPrimaryKeyHelper() {
return new org.apache.cayenne.access.util.PrimaryKeyHelper(this);
}
/**
* Shutdowns all owned data nodes. Invokes DataNode.shutdown().
*/
public void shutdown() {
if (sharedSnapshotCache != null) {
this.sharedSnapshotCache.shutdown();
}
Collection dataNodes = getDataNodes();
for (Iterator i = dataNodes.iterator(); i.hasNext();) {
DataNode node = (DataNode) i.next();
try {
node.shutdown();
}
catch (Exception ex) {
}
}
}
/**
* Routes queries to appropriate DataNodes for execution.
*/
public void performQueries(final Collection queries, final OperationObserver callback) {
runInTransaction(new Transformer() {
public Object transform(Object input) {
new DataDomainLegacyQueryAction(
DataDomain.this,
new QueryChain(queries),
callback).execute();
return null;
}
});
}
// ****** DataChannel methods:
/**
* Runs query returning generic QueryResponse.
*
* @since 1.2
*/
public QueryResponse onQuery(final ObjectContext context, final Query query) {
// transaction note:
// we don't wrap this code in transaction to reduce transaction scope to
// just the DB operation for better performance ... query action will start a
// transaction itself when and if needed
return new DataDomainQueryAction(context, DataDomain.this, query).execute();
}
/**
* Returns an EntityResolver that stores mapping information for this domain.
*/
public EntityResolver getEntityResolver() {
if (entityResolver == null) {
createEntityResolver();
}
return entityResolver;
}
/**
* Only handles commit-type synchronization, ignoring any other type.
*
* @since 1.2
*/
public GraphDiff onSync(
final ObjectContext originatingContext,
final GraphDiff changes,
int syncType) {
switch (syncType) {
case DataChannel.ROLLBACK_CASCADE_SYNC:
return onSyncRollback(originatingContext);
// "cascade" and "no_cascade" are the same from the DataDomain
// perspective,
// including transaction handling logic
case DataChannel.FLUSH_NOCASCADE_SYNC:
case DataChannel.FLUSH_CASCADE_SYNC:
return (GraphDiff) runInTransaction(new Transformer() {
public Object transform(Object input) {
return onSyncFlush(originatingContext, changes);
}
});
default:
throw new CayenneRuntimeException("Invalid synchronization type: "
+ syncType);
}
}
GraphDiff onSyncRollback(ObjectContext originatingContext) {
// if there is a transaction in progress, roll it back
Transaction transaction = Transaction.getThreadTransaction();
if (transaction != null) {
transaction.setRollbackOnly();
}
return new CompoundDiff();
}
GraphDiff onSyncFlush(ObjectContext originatingContext, GraphDiff childChanges) {
if (!(originatingContext instanceof DataContext)) {
throw new CayenneRuntimeException(
"No support for committing ObjectContexts that are not DataContexts yet. "
+ "Unsupported context: "
+ originatingContext);
}
return new DataDomainFlushAction(this).flush(
(DataContext) originatingContext,
childChanges);
}
/**
* Executes Transformer.transform() method in a transaction. Transaction policy is to
* check for the thread transaction, and use it if one exists. If it doesn't, a new
* transaction is created, with a scope limited to this method.
*/
// WARNING: (andrus) if we ever decide to make this method protected or public, we
// need to change the signature to avoid API dependency on commons-collections
Object runInTransaction(Transformer operation) {
// user or container-managed or nested transaction
if (Transaction.getThreadTransaction() != null) {
return operation.transform(null);
}
// Cayenne-managed transaction
Transaction transaction = createTransaction();
Transaction.bindThreadTransaction(transaction);
try {
// implicit begin..
Object result = operation.transform(null);
transaction.commit();
return result;
}
catch (Exception ex) {
transaction.setRollbackOnly();
// must rethrow
if (ex instanceof CayenneRuntimeException) {
throw (CayenneRuntimeException) ex;
}
else {
throw new CayenneRuntimeException(ex);
}
}
finally {
Transaction.bindThreadTransaction(null);
if (transaction.getStatus() == Transaction.STATUS_MARKED_ROLLEDBACK) {
try {
transaction.rollback();
}
catch (Exception rollbackEx) {
// although we don't expect an exception here, print the stack, as
// there have been some Cayenne bugs already (CAY-557) that were
// masked by this 'catch' clause.
QueryLogger.logQueryError(rollbackEx);
}
}
}
}
public String toString() {
return new ToStringBuilder(this).append("name", name).toString();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy