/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*/
package com.sun.sgs.impl.service.data;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedObjectRemoval;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.TransactionAbortedException;
import com.sun.sgs.app.TransactionNotActiveException;
import com.sun.sgs.impl.kernel.StandardProperties;
import com.sun.sgs.impl.service.data.store.DataStoreImpl;
import com.sun.sgs.impl.service.data.store.DataStoreProfileProducer;
import com.sun.sgs.impl.service.data.store.net.DataStoreClient;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import com.sun.sgs.impl.sharedutil.PropertiesWrapper;
import com.sun.sgs.impl.util.AbstractKernelRunnable;
import com.sun.sgs.impl.util.TransactionContextFactory;
import com.sun.sgs.impl.util.TransactionContextMap;
import com.sun.sgs.kernel.ComponentRegistry;
import com.sun.sgs.kernel.NodeType;
import com.sun.sgs.kernel.TransactionScheduler;
import com.sun.sgs.profile.ProfileCollector;
import com.sun.sgs.service.DataService;
import com.sun.sgs.service.Transaction;
import com.sun.sgs.service.TransactionProxy;
import com.sun.sgs.service.store.DataStore;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.JMException;
/**
* Provides an implementation of <code>DataService</code> based on {@link
* DataStoreImpl}. <p>
*
* The {@link #DataServiceImpl constructor} requires the <a
* href="../../../impl/kernel/doc-files/config-properties.html#com.sun.sgs.app.name">
* <code>com.sun.sgs.app.name</code></a> property, and supports both these
* public configuration <a
* href="../../../impl/kernel/doc-files/config-properties.html#DataService">
* properties</a> and the following additional properties: <p>
*
* <dl style="margin-left: 1em">
*
* <dt> <i>Property:</i> <code><b>{@value #DATA_STORE_CLASS_PROPERTY}
* </b></code> <br>
* <i>Default:</i>
* <code>com.sun.sgs.impl.service.data.store.net.DataStoreClient</code>
* unless the {@code com.sun.sgs.node.type} property is {@code singleNode},
* which defaults to
* <code>com.sun.sgs.impl.service.data.store.DataStoreImpl</code>
*
* <dd style="padding-top: .5em">The name of the class that implements {@link
* DataStore}. The class should be public, not abstract, and should
* provide a public constructor with {@link Properties}, {@link
* ComponentRegistry}, and {@link TransactionProxy} parameters. <p>
*
* <dt> <i>Property:</i> <code><b>{@value #DETECT_MODIFICATIONS_PROPERTY}
* </b></code> <br>
* <i>Default:</i> <code>true</code>
*
* <dd style="padding-top: .5em">Whether to automatically detect modifications
* to managed objects. If set to something other than <code>true</code>,
* then applications need to call {@link DataManager#markForUpdate
* DataManager.markForUpdate} or {@link ManagedReference#getForUpdate
* ManagedReference.getForUpdate} for any modified objects to make sure
* that the modifications are recorded by the
* <code>DataService</code>. <p>
*
* <dt> <i>Property:</i> <code><b>{@value #DEBUG_CHECK_INTERVAL_PROPERTY}
* </b></code> <br>
* <i>Default:</i> <code>Integer.MAX_VALUE</code>
*
* <dd style="padding-top: .5em">The number of <code>DataService</code>
* operations to skip between checks of the consistency of the managed
* references table. Note that the number of operations is measured
* separately for each transaction. This property is intended for use in
* debugging. <p>
*
* <dt> <i>Property:</i> <code><b>{@value #OPTIMISTIC_WRITE_LOCKS}
* </b></code><br>
* <i>Default:</i> <code>false</code>
*
* <dd style="padding-top: .5em">Whether to wait until commit time to obtain
* write locks. If <code>false</code>, which is the default, the service
* acquires write locks as soon as it knows that an object is being
* modified. If <code>true</code>, the service delays obtaining write
* locks until commit time, which may improve performance in some cases,
* typically when there is low contention. Note that setting this flag to
* <code>true</code> does not delay write locks when removing objects.<p>
*
* <dt> <i>Property:</i> <code><b>{@value #TRACK_STALE_OBJECTS_PROPERTY}
* </b></code> <br>
* <i>Default:</i> <code>false</code>
*
* <dd style="padding-top: .5em">Whether to track references to stale managed
* objects. If <code>true</code>, the <code>DataService</code> keeps
* track of persistent or removed objects from completed transactions and
* throws {@link TransactionNotActiveException} if the application refers
* to those objects from another transaction. <p>
*
* </dl> <p>
*
* The constructor also passes the properties to the {@link DataStoreImpl}
* constructor, which supports additional properties. <p>
*
* This class uses the {@link Logger} named
* <code>com.sun.sgs.impl.service.data.DataServiceImpl</code> to log
* information at the following logging levels: <p>
*
* <ul>
* <li> {@link Level#SEVERE SEVERE} - Initialization failures
* <li> {@link Level#CONFIG CONFIG} - Constructor properties, data service
* headers
* <li> {@link Level#FINE FINE} - Task scheduling operations, managed reference
* table checks
* <li> {@link Level#FINER FINER} - Transaction operations
* <li> {@link Level#FINEST FINEST} - Name, object, and reference operations
* </ul> <p>
*
* It also uses an additional {@code Logger} named {@code
* com.sun.sgs.impl.service.data.DataServiceImpl.detect.modifications} to log
* information about managed objects that are found to be modified but were not
* marked for update. Note that this logging output will only be performed if
* the {@code
* com.sun.sgs.impl.service.data.DataServiceImpl.detect.modifications} property
* is {@code true}. <p>
*
* <ul>
* <li> {@code FINEST} - Modified object was not marked for update
* </ul> <p>
*
* In addition, operations that throw a {@link TransactionAbortedException}
* will log the failure to the {@code Logger} named {@code
* com.sun.sgs.impl.service.data.DataServiceImpl.abort}, to make it easier to
* debug concurrency conflicts by just logging aborts.
*/
public final class DataServiceImpl implements DataService {
/** The name of this class. */
private static final String CLASSNAME =
"com.sun.sgs.impl.service.data.DataServiceImpl";
/**
* The property that specifies the number of operations to skip between
* checks of the consistency of the managed references table.
*/
public static final String DEBUG_CHECK_INTERVAL_PROPERTY =
CLASSNAME + ".debug.check.interval";
/**
* The property that specifies whether to automatically detect
* modifications to objects.
*/
public static final String DETECT_MODIFICATIONS_PROPERTY =
CLASSNAME + ".detect.modifications";
/**
* The property that specifies the name of the class that implements
* {@link DataStore}.
*/
public static final String DATA_STORE_CLASS_PROPERTY =
CLASSNAME + ".data.store.class";
/** The property that specifies to use optimistic write locking. */
public static final String OPTIMISTIC_WRITE_LOCKS =
CLASSNAME + ".optimistic.write.locks";
/** The property that specifies whether to track stale objects. */
public static final String TRACK_STALE_OBJECTS_PROPERTY =
CLASSNAME + ".track.stale.objects";
/** The logger for this class. */
static final LoggerWrapper logger =
new LoggerWrapper(Logger.getLogger(CLASSNAME));
/** The logger for transaction abort exceptions. */
private static final LoggerWrapper abortLogger =
new LoggerWrapper(Logger.getLogger(CLASSNAME + ".abort"));
/** Synchronize on this object when accessing the contextMap field. */
private static final Object contextMapLock = new Object();
/**
* The transaction context map, or null if configure has not been called.
*/
private static TransactionContextMap<Context> contextMap = null;
/** The name of this application. */
private final String appName;
/** The underlying data store. */
private final DataStore store;
/** The local node ID. */
private final long nodeId;
/** Table that stores information about classes used in serialization. */
private final ClassesTable classesTable;
/** The transaction context factory. */
private final TransactionContextFactory<Context> contextFactory;
/** Whether to delay obtaining write locks. */
final boolean optimisticWriteLocks;
/** Whether to track stale objects. */
private final boolean trackStaleObjects;
/** The data service profiling information. */
private final DataServiceStats serviceStats;
/**
* Synchronize on this object before accessing the state,
* debugCheckInterval, or detectModifications fields.
*/
private final Object stateLock = new Object();
/** The possible states of this instance. */
enum State {
/** Before constructor has completed */
UNINITIALIZED,
/** After construction and before shutdown */
RUNNING,
/** After start of a call to shutdown and before call finishes */
SHUTTING_DOWN,
/** After shutdown has completed successfully */
SHUTDOWN
}
/** The current state of this instance. */
private State state = State.UNINITIALIZED;
/**
* The number of operations to skip between checks of the consistency of
* the managed reference table.
*/
private int debugCheckInterval;
/** Whether to detect object modifications automatically. */
private boolean detectModifications;
/**
* Defines the transaction context map for this class. This class checks
* the service state and the reference table whenever the context is
* accessed. No check is needed for joinTransaction, though, because the
* check is already being made when obtaining the context factory.
*/
private static final class ContextMap
extends TransactionContextMap<Context>
{
ContextMap(TransactionProxy proxy) {
super(proxy);
}
@Override public Context getContext() {
return check(super.getContext());
}
@Override public void checkContext(Context context) {
check(context);
super.checkContext(context);
}
@Override public Context checkTransaction(Transaction txn) {
return check(super.checkTransaction(txn));
}
private static Context check(Context context) {
context.checkState();
context.maybeCheckReferenceTable();
return context;
}
}
/** Defines the transaction context factory for this class. */
private final class ContextFactory
extends TransactionContextFactory<Context>
{
ContextFactory(TransactionContextMap<Context> contextMap) {
super(contextMap, CLASSNAME);
}
@Override protected Context createContext(Transaction txn) {
/*
* Prevent joining a new transaction during shutdown, even though
* other operations will have been allowed to proceed.
*/
synchronized (stateLock) {
if (state == State.SHUTTING_DOWN) {
throw new IllegalStateException(
"Service is shutting down");
}
}
return new Context(
DataServiceImpl.this, store, txn, debugCheckInterval,
detectModifications, classesTable, trackStaleObjects);
}
}
/**
* Creates an instance of this class configured with the specified
* properties and services. See the {@link DataServiceImpl class
* documentation} for the list of supported properties.
*
* @param properties the properties for configuring this service
* @param systemRegistry the registry of available system components
* @param txnProxy the transaction proxy
* @throws IllegalArgumentException if the <code>com.sun.sgs.app.name
* </code> property is not specified, if the value of the <code>
* com.sun.sgs.impl.service.data.DataServiceImpl.debug.check.interval
* </code> property is not a valid integer, or if the data store
* constructor detects an illegal property value
* @throws Exception if a problem occurs creating the service
*/
public DataServiceImpl(Properties properties,
ComponentRegistry systemRegistry,
TransactionProxy txnProxy)
throws Exception
{
logger.log(Level.CONFIG, "Creating DataServiceImpl");
DataStore storeToShutdown = null;
try {
PropertiesWrapper wrappedProps = new PropertiesWrapper(properties);
appName = wrappedProps.getProperty(StandardProperties.APP_NAME);
if (appName == null) {
throw new IllegalArgumentException(
"The " + StandardProperties.APP_NAME +
" property must be specified");
} else if (systemRegistry == null) {
throw new NullPointerException(
"The systemRegistry argument must not be null");
} else if (txnProxy == null) {
throw new NullPointerException(
"The txnProxy argument must not be null");
}
debugCheckInterval = wrappedProps.getIntProperty(
DEBUG_CHECK_INTERVAL_PROPERTY, Integer.MAX_VALUE);
detectModifications = wrappedProps.getBooleanProperty(
DETECT_MODIFICATIONS_PROPERTY, Boolean.TRUE);
String dataStoreClassName = wrappedProps.getProperty(
DATA_STORE_CLASS_PROPERTY);
optimisticWriteLocks = wrappedProps.getBooleanProperty(
OPTIMISTIC_WRITE_LOCKS, Boolean.FALSE);
trackStaleObjects = wrappedProps.getBooleanProperty(
TRACK_STALE_OBJECTS_PROPERTY, Boolean.FALSE);
NodeType nodeType =
wrappedProps.getEnumProperty(StandardProperties.NODE_TYPE,
NodeType.class,
NodeType.singleNode);
DataStore baseStore;
if (dataStoreClassName != null) {
baseStore = wrappedProps.getClassInstanceProperty(
DATA_STORE_CLASS_PROPERTY, DataStore.class,
new Class[] { Properties.class, ComponentRegistry.class,
TransactionProxy.class },
properties, systemRegistry, txnProxy);
logger.log(Level.CONFIG, "Using data store {0}", baseStore);
} else if (nodeType == NodeType.singleNode) {
baseStore = new DataStoreImpl(
properties, systemRegistry, txnProxy);
} else {
baseStore = new DataStoreClient(
properties, systemRegistry, txnProxy);
}
storeToShutdown = baseStore;
ProfileCollector collector =
systemRegistry.getComponent(ProfileCollector.class);
store = new DataStoreProfileProducer(baseStore, collector);
nodeId = store.getLocalNodeId();
// create our service profiling info and register our MBean
serviceStats = new DataServiceStats(collector);
try {
collector.registerMBean(serviceStats,
DataServiceStats.MXBEAN_NAME);
} catch (JMException e) {
logger.logThrow(Level.CONFIG, e, "Could not register MBean");
}
classesTable = new ClassesTable(store);
synchronized (contextMapLock) {
if (contextMap == null) {
contextMap = new ContextMap(txnProxy);
}
}
contextFactory = new ContextFactory(contextMap);
synchronized (stateLock) {
state = State.RUNNING;
}
systemRegistry.getComponent(TransactionScheduler.class).runTask(
new AbstractKernelRunnable("BindDataServiceHeader") {
public void run() {
DataServiceHeader header;
try {
header = (DataServiceHeader)
getServiceBinding(CLASSNAME + ".header");
logger.log(Level.CONFIG,
"Found existing header {0}",
header);
} catch (NameNotBoundException e) {
header = new DataServiceHeader(appName);
setServiceBinding(
CLASSNAME + ".header", header);
logger.log(Level.CONFIG,
"Created new header {0}", header);
}
}
},
txnProxy.getCurrentOwner());
storeToShutdown = null;
logger.log(Level.CONFIG,
"Created DataServiceImpl with properties:" +
"\n " + DATA_STORE_CLASS_PROPERTY + "=" +
dataStoreClassName +
"\n " + DEBUG_CHECK_INTERVAL_PROPERTY + "=" +
debugCheckInterval +
"\n " + DETECT_MODIFICATIONS_PROPERTY + "=" +
detectModifications +
"\n " + OPTIMISTIC_WRITE_LOCKS + "=" +
optimisticWriteLocks +
"\n " + TRACK_STALE_OBJECTS_PROPERTY + "=" +
trackStaleObjects);
} catch (RuntimeException e) {
getExceptionLogger(e).logThrow(
Level.SEVERE, e, "DataService initialization failed");
throw e;
} catch (Error e) {
logger.logThrow(
Level.SEVERE, e, "DataService initialization failed");
throw e;
} finally {
if (storeToShutdown != null) {
storeToShutdown.shutdown();
}
}
}
/* -- Implement Service -- */
/** {@inheritDoc} */
public String getName() {
return toString();
}
/** {@inheritDoc} */
public void ready() throws Exception {
store.ready();
}
/* -- Implement DataManager -- */
/** {@inheritDoc} */
public ManagedObject getBinding(String name) {
serviceStats.getBindingOp.report();
return getBindingInternal(name, false, false, "getBinding");
}
/** {@inheritDoc} */
public ManagedObject getBindingForUpdate(String name) {
serviceStats.getBindingForUpdateOp.report();
return getBindingInternal(name, false, true, "getBindingForUpdate");
}
/** {@inheritDoc} */
public void setBinding(String name, Object object) {
serviceStats.setBindingOp.report();
setBindingInternal(name, object, false);
}
/** {@inheritDoc} */
public void removeBinding(String name) {
serviceStats.removeBindingOp.report();
removeBindingInternal(name, false);
}
/** {@inheritDoc} */
public String nextBoundName(String name) {
serviceStats.nextBoundNameOp.report();
return nextBoundNameInternal(name, false);
}
/** {@inheritDoc} */
public void removeObject(Object object) {
serviceStats.removeObjOp.report();
Context context = null;
ManagedReferenceImpl<?> ref = null;
try {
checkManagedObject(object);
context = getContext();
ref = context.findReference(object);
if (object instanceof ManagedObjectRemoval) {
context.removingObject((ManagedObjectRemoval) object);
/*
* Get the context again in case something changed as a
* result of the call to removingObject.
*/
getContext();
}
if (ref != null) {
ref.removeObject();
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST,
"removeObject tid:{0,number,#}, type:{1}," +
" oid:{2,number,#} returns",
contextTxnId(context), typeName(object), refId(ref));
}
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e,
"removeObject tid:{0,number,#}, type:{1}," +
" oid:{2,number,#} throws",
contextTxnId(context), typeName(object), refId(ref));
}
throw e;
}
}
/** {@inheritDoc} */
public void markForUpdate(Object object) {
serviceStats.markForUpdateOp.report();
Context context = null;
ManagedReferenceImpl<?> ref = null;
try {
checkManagedObject(object);
context = getContext();
ref = context.findReference(object);
if (ref != null) {
ref.markForUpdate();
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST,
"markForUpdate tid:{0,number,#}, type:{1}," +
" oid:{2,number,#} returns",
contextTxnId(context), typeName(object), refId(ref));
}
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e,
"markForUpdate tid:{0,number,#}, type:{1}," +
" oid:{2,number,#} throws",
contextTxnId(context), typeName(object), refId(ref));
}
throw e;
}
}
/** {@inheritDoc} */
public <T> ManagedReference<T> createReference(T object) {
serviceStats.createRefOp.report();
Context context = null;
try {
checkManagedObject(object);
context = getContext();
ManagedReference<T> result = context.getReference(object);
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST,
"createReference tid:{0,number,#}, type:{1}" +
" returns oid:{2,number,#}",
contextTxnId(context), typeName(object), refId(result));
}
return result;
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e,
"createReference tid:{0,number,#}, type:{1} throws",
contextTxnId(context), typeName(object));
}
throw e;
}
}
/** {@inheritDoc} */
public BigInteger getObjectId(Object object) {
serviceStats.getObjectIdOp.report();
Context context = null;
try {
checkManagedObject(object);
context = getContext();
BigInteger result = context.getReference(object).getId();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"getObjectId tid:{0,number,#}, type:{1}" +
" returns oid:{2,number,#}",
contextTxnId(context), typeName(object), result);
}
return result;
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e,
"getObjectId tid:{0,number,#}, type:{1} throws",
contextTxnId(context), typeName(object));
}
throw e;
}
}
/* -- Implement DataService -- */
/** {@inheritDoc} */
public long getLocalNodeId() {
serviceStats.getLocalNodeIdOp.report();
return nodeId;
}
/** {@inheritDoc} */
public ManagedObject getServiceBinding(String name) {
serviceStats.getServiceBindingOp.report();
return getBindingInternal(name, true, false, "getServiceBinding");
}
/** {@inheritDoc} */
public ManagedObject getServiceBindingForUpdate(String name) {
serviceStats.getServiceBindingForUpdateOp.report();
return getBindingInternal(
name, true, true, "getServiceBindingForUpdate");
}
/** {@inheritDoc} */
public void setServiceBinding(String name, Object object) {
serviceStats.setServiceBindingOp.report();
setBindingInternal(name, object, true);
}
/** {@inheritDoc} */
public void removeServiceBinding(String name) {
serviceStats.removeServiceBindingOp.report();
removeBindingInternal(name, true);
}
/** {@inheritDoc} */
public String nextServiceBoundName(String name) {
serviceStats.nextServiceBoundNameOp.report();
return nextBoundNameInternal(name, true);
}
/** {@inheritDoc} */
public ManagedReference<?> createReferenceForId(BigInteger id) {
serviceStats.createRefForIdOp.report();
Context context = null;
try {
context = getContext();
ManagedReference<?> result = context.getReference(getOid(id));
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"createReferenceForId tid:{0,number,#}," +
" oid:{1,number,#} returns",
contextTxnId(context), id);
}
return result;
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e,
"createReferenceForId tid:{0,number,#}," +
" oid:{1,number,#} throws",
contextTxnId(context), id);
}
throw e;
}
}
/** {@inheritDoc} */
public BigInteger nextObjectId(BigInteger objectId) {
serviceStats.nextObjIdOp.report();
try {
long oid = (objectId == null) ? -1 : getOid(objectId);
Context context = getContext();
long nextOid = context.nextObjectId(oid);
BigInteger result =
(nextOid == -1) ? null : BigInteger.valueOf(nextOid);
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST, "nextObjectId objectId:{0} returns {1}",
objectId, result);
}
return result;
} catch (RuntimeException e) {
getExceptionLogger(e).logThrow(
Level.FINEST, e, "nextObjectId objectId:{0} throws", objectId);
throw e;
}
}
/* -- Generic binding methods -- */
/**
* Implement getBinding, getBindingForUpdate, getServiceBinding, and
* getServiceBindingForUpdate.
*/
private ManagedObject getBindingInternal(String name,
boolean serviceBinding,
boolean forUpdate,
String methodName)
{
Context context = null;
try {
if (name == null) {
throw new NullPointerException("The name must not be null");
}
context = getContext();
ManagedObject result;
try {
result = context.getBinding(
getInternalName(name, serviceBinding), forUpdate);
} catch (NameNotBoundException e) {
throw new NameNotBoundException(
"Name '" + name + "' is not bound");
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST,
"{0} tid:{1,number,#}, name:{2} returns type:{3}," +
" oid:{4,number,#}",
methodName, contextTxnId(context), name, typeName(result),
objectId(context, result));
}
return result;
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e,
"{0} tid:{1,number,#}, name:{2} throws",
methodName, contextTxnId(context), name);
}
throw e;
}
}
/** Implement setBinding and setServiceBinding. */
private void setBindingInternal(
String name, Object object, boolean serviceBinding)
{
Context context = null;
try {
if (name == null) {
throw new NullPointerException("The name must not be null");
}
checkManagedObject(object);
context = getContext();
context.setBinding(getInternalName(name, serviceBinding), object);
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST,
"{0} tid:{1,number,#}, name:{2}, type:{3}," +
" oid:{4,number,#} returns",
serviceBinding ? "setServiceBinding" : "setBinding",
contextTxnId(context), name, typeName(object),
objectId(context, object));
}
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e,
"{0} tid:{1,number,#}, name:{2}, type:{3}," +
" oid:{4,number,#} throws",
serviceBinding ? "setServiceBinding" : "setBinding",
contextTxnId(context), name, typeName(object),
objectId(context, object));
}
throw e;
}
}
/** Implement removeBinding and removeServiceBinding. */
private void removeBindingInternal(String name, boolean serviceBinding) {
Context context = null;
try {
if (name == null) {
throw new NullPointerException("The name must not be null");
}
context = getContext();
try {
context.removeBinding(getInternalName(name, serviceBinding));
} catch (NameNotBoundException e) {
throw new NameNotBoundException(
"Name '" + name + "' is not bound");
}
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST, "{0} tid:{1,number,#}, name:{2} returns",
serviceBinding ? "removeServiceBinding" : "removeBinding",
contextTxnId(context), name);
}
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e, "{0} tid:{1,number,#}, name:{2} throws",
serviceBinding ? "removeServiceBinding" : "removeBinding",
contextTxnId(context), name);
}
throw e;
}
}
/** Implement nextBoundName and nextServiceBoundName. */
private String nextBoundNameInternal(String name, boolean serviceBinding) {
Context context = null;
try {
context = getContext();
String result = getExternalName(
context.nextBoundName(getInternalName(name, serviceBinding)),
serviceBinding);
if (logger.isLoggable(Level.FINEST)) {
logger.log(
Level.FINEST, "{0} tid:{1,number,#}, name:{2} returns {3}",
serviceBinding ? "nextServiceBoundName" : "nextBoundName",
contextTxnId(context), name, result);
}
return result;
} catch (RuntimeException e) {
LoggerWrapper exceptionLogger = getExceptionLogger(e);
if (exceptionLogger.isLoggable(Level.FINEST)) {
exceptionLogger.logThrow(
Level.FINEST, e, "{0} tid:{1,number,#}, name:{2} throws",
serviceBinding ? "nextServiceBoundName" : "nextBoundName",
contextTxnId(context), name);
}
throw e;
}
}
/* -- Other public methods -- */
/**
* Returns a string representation of this instance.
*
* @return a string representation of this instance
*/
public String toString() {
return "DataServiceImpl[appName:\"" + appName +
", store:" + store + "\"]";
}
/**
* Specifies the number of operations to skip between checks of the
* consistency of the managed references table.
*
* @param debugCheckInterval the number of operations to skip between
* checks of the consistency of the managed references table
*/
public void setDebugCheckInterval(int debugCheckInterval) {
synchronized (stateLock) {
this.debugCheckInterval = debugCheckInterval;
}
}
/**
* Specifies whether to automatically detect modifications to objects.
*
* @param detectModifications whether to detect modifications
*/
public void setDetectModifications(boolean detectModifications) {
synchronized (stateLock) {
this.detectModifications = detectModifications;
}
}
/* -- Other methods -- */
/**
* {@inheritDoc}
*/
public void shutdown() {
synchronized (stateLock) {
while (state == State.SHUTTING_DOWN) {
try {
stateLock.wait();
} catch (InterruptedException e) {
// loop until shutdown is complete
logger.log(Level.FINEST, "DataService shutdown " +
"interrupt ignored");
}
}
if (state == State.SHUTDOWN) {
return; // return silently
}
state = State.SHUTTING_DOWN;
}
store.shutdown();
synchronized (stateLock) {
state = State.SHUTDOWN;
stateLock.notifyAll();
}
}
/**
* Obtains information associated with the current transaction, throwing a
* TransactionNotActiveException exception if there is no current
* transaction, and throwing IllegalStateException if there is a problem
* with the state of the transaction or if this service has not been
* configured with a transaction proxy. Joins the transaction if that has
* not been done already.
*/
private Context getContext() {
Context context = getContextFactory().joinTransaction();
context.maybeCheckReferenceTable();
return context;
}
/**
* Returns the transaction context factory, first checking the state of the
* service.
*/
private TransactionContextFactory<Context> getContextFactory() {
checkState();
return contextFactory;
}
/**
* Returns the transaction context map, checking to be sure it has been
* initialized.
*/
private static TransactionContextMap<Context> getContextMap() {
synchronized (contextMapLock) {
if (contextMap == null) {
throw new IllegalStateException("Service is not configured");
}
return contextMap;
}
}
/** Checks that the current state is RUNNING or SHUTTING_DOWN. */
void checkState() {
synchronized (stateLock) {
switch (state) {
case UNINITIALIZED:
throw new IllegalStateException("Service is not constructed");
case RUNNING:
case SHUTTING_DOWN:
break;
case SHUTDOWN:
throw new IllegalStateException("Service is shut down");
default:
throw new AssertionError();
}
}
}
/**
* Checks that the specified context is currently active. Throws
* TransactionNotActiveException if there is no current transaction or if
* the current transaction doesn't match the context.
*/
static void checkContext(Context context) {
getContextMap().checkContext(context);
}
/**
* Obtains the currently active context, throwing
* TransactionNotActiveException if none is active. Does not join the
* transaction.
*/
static Context getContextNoJoin() {
return getContextMap().getContext();
}
/**
* Returns the name that should be used for a service or application
* binding. If name is null, then returns a name that will sort earlier
* than any non-null name.
*/
private static String getInternalName(
String name, boolean serviceBinding)
{
if (name == null) {
return serviceBinding ? "s" : "a";
} else {
return (serviceBinding ? "s." : "a.") + name;
}
}
/**
* Returns the external name for a service or application binding name.
* Returns null if the name does not have the proper prefix, or is null.
*/
private static String getExternalName(
String name, boolean serviceBinding)
{
if (name == null) {
return null;
}
String prefix = serviceBinding ? "s." : "a.";
/*
* If this is an application binding, then the name could start with
* "s." if we've moved past all of the application bindings.
* Otherwise, the prefix should be correct. -tjb@sun.com (12/14/2006)
*/
assert name.startsWith(prefix) ||
(!serviceBinding && name.startsWith("s."))
: "Name has wrong prefix";
return name.startsWith(prefix) ? name.substring(2) : null;
}
/**
* Converts a BigInteger object ID into a long, throwing an exception if
* the argument is invalid.
*/
private static long getOid(BigInteger objectId) {
if (objectId == null) {
throw new NullPointerException("The object ID must not be null");
} else if (objectId.bitLength() > 63 || objectId.signum() < 0) {
throw new IllegalArgumentException(
"The object ID is invalid: " + objectId);
}
return objectId.longValue();
}
/**
* Returns the transaction ID associated with the context, or null if the
* context is null.
*/
private static BigInteger contextTxnId(Context context) {
return (context != null) ? context.getTxnId() : null;
}
/**
* Returns the object ID for the reference, or null if the reference is
* null.
*/
private static BigInteger refId(ManagedReference<?> ref) {
return (ref != null) ? ref.getId() : null;
}
/** Returns the type name of the object. */
static String typeName(Object object) {
return (object == null) ? "null" : object.getClass().getName();
}
/**
* Returns the object ID of the object, or null if the object is null or
* has not assigned an ID, or if the context is null. Returns an ID even
* if the object is removed.
*/
private static BigInteger objectId(Context context, Object object) {
return (context != null) ? refId(context.safeFindReference(object))
: null;
}
/**
* Checks that the argument is a legal managed object: non-null,
* serializable, and implements ManagedObject.
*/
private static void checkManagedObject(Object object) {
if (object == null) {
throw new NullPointerException("The object must not be null");
} else if (!(object instanceof Serializable)) {
throw new IllegalArgumentException(
"The object must be serializable: " + object);
} else if (!(object instanceof ManagedObject)) {
throw new IllegalArgumentException(
"The object must implement ManagedObject: " + object);
}
}
/**
* Returns the logger that should be used to log the specified exception.
* In particular, use the abortLogger for TransactionAbortedException, and
* the class logger for other runtime exceptions.
*/
static LoggerWrapper getExceptionLogger(RuntimeException exception) {
return exception instanceof TransactionAbortedException
? abortLogger : logger;
}
}