
org.ow2.bonita.pvm.internal.wire.WireContext Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.ow2.bonita.pvm.internal.wire;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.ow2.bonita.env.Context;
import org.ow2.bonita.env.Environment;
import org.ow2.bonita.env.PvmEnvironmentFactory;
import org.ow2.bonita.pvm.activity.Activity;
import org.ow2.bonita.pvm.internal.model.ProcessElementImpl;
import org.ow2.bonita.pvm.internal.util.Closable;
import org.ow2.bonita.pvm.internal.util.DefaultObservable;
import org.ow2.bonita.pvm.internal.util.Observable;
import org.ow2.bonita.pvm.internal.wire.descriptor.AbstractDescriptor;
import org.ow2.bonita.pvm.internal.wire.descriptor.ObjectDescriptor;
import org.ow2.bonita.pvm.internal.wire.operation.FieldOperation;
import org.ow2.bonita.pvm.internal.wire.operation.InvokeOperation;
import org.ow2.bonita.pvm.internal.wire.operation.Operation;
import org.ow2.bonita.pvm.internal.wire.operation.PropertyOperation;
import org.ow2.bonita.pvm.internal.wire.operation.SubscribeOperation;
import org.ow2.bonita.pvm.internal.wire.xml.WireParser;
import org.ow2.bonita.util.ExceptionManager;
import org.ow2.bonita.util.Log;
/**
* object factory that creates, initializes, wires and caches objects based on
* {@link Descriptor descriptors} (aka IoC container).
*
* General principle
*
*
* As input, a WireContext takes a {@link WireDefinition}. The WireDefinition
* contains named {@link Descriptor}s that know how to create objects and wire
* them together. Each object has a name. The WireContext will maintain a cache
* (map) of the created objects. So that upon subsequent requests, the same
* object can be given from the cache.
*
*
* 
*
* Purpose
*
*
* A WireContext is used often in combination with {@link Environment} to
* decouple the processDefinition virtual machine from its environment. In the
* {@link PvmEnvironmentFactory}, both the environment-factory context and the
* environment contexts are WireContexts. The PVM will use the persistence
* service, asynchronous message service, timer service and other services
* through specified abstractions in the environment.
*
*
*
* Another usage of the WireContext is construction and configuration of user
* code objects in a persistable way. {@link Activity}s and
* {@link org.ow2.bonita.pvm.activity.ExternalActivity} and other user code can
* be instantiated with a WireContext. That way, they can be persisted in a
* fixed schema.
*
*
*
* Each {@link ProcessElementImpl} has configuration properties. Consider this
* extra metadata that can be associated to elements in a processDefinition
* definition. In that respect, it's somewhat similar to what annotations are in
* Java. Because of the wire persistence, all these configuration properties fit
* into the same process model and in its database schema.
*
*
* Xml
*
* Mostly often, {@link Descriptor}s and WireContext's are not used directly.
* Instead, the wire XML is used in a configuration file. The {@link WireParser
* wire XML parser} contains the documentation on the XML grammer. The
* {@link WireParser} will produce a {@link WireDefinition} with a bunch of
* {@link Descriptor}s in it.
*
*
Object lifecycle
*
*
* Objects are build in 2 phases: construction and initialization. The
* motivation for splitting these phases is to resolve many of the circular
* dependencies. Imagine 2 objects that have a bidirectional reference. By
* splitting the construction from the initialization phase, the objects can
* both be constructed first, and then during initialization, they will be
* injected into each other.
*
*
* Construction
*
* Construction of the object is all that needs to be done until a reference to
* the object is available.
*
*
*
* In the case of dynamically created objects ({@link ObjectDescriptor}), the
* simplest case this is accomplished with a constructor. But also static or
* non-static factory methods can be used to obtain a reference to an object.
*
*
*
* In case of immutable objects, the descriptor can just provide a reference to
* a singleton object.
*
*
* Initialization
*
* Initialization is optional and it is comprised of everything that needs to be
* done with an object after a reference to the object is available.
* {@link AbstractDescriptor} contains an empty default initialization method.
*
*
*
* For objects {@link ObjectDescriptor}s, this means that a a sequence of
* {@link Operation}s can be applied to the object. Following operations
* implementations are already available and can be applied to an object during
* initialization:
*
*
*
* - {@link FieldOperation}: injects another object into a field
* - {@link PropertyOperation}: injects another object with a setter
* method.
* - {@link InvokeOperation}: invokes a method.
* - {@link SubscribeOperation}: subscribes to an {@link Observable
* observable}.
*
*
* Environment
*
*
* When an environment is injected into a WireContext, lookup of all referenced
* object names will be done first in this WireContext, but if the object name
* is not defined there, the environment will be searched in the environment's
* default search order.
*
*
* Events
*
* Several objects will fire events to which can be subscribed:
*
*
*
* The WireContext itself fires the {@link #EVENT_OPEN} and {@link #EVENT_OPEN}
* events.
*
*
*
* The {@link Descriptor}s will fire the events
* {@link Descriptor#EVENT_CONSTRUCTING}, {@link Descriptor#EVENT_INITIALIZING},
* {@link Descriptor#EVENT_CONSTRUCTED}, {@link Descriptor#EVENT_SET} and
* {@link Descriptor#EVENT_REMOVE}.
*
*
*
* And last but not least, the objects created by the WireContext can be
* {@link Observable} themselves.
*
*
* Eager initialization
*
*
* By default, all objects in a WireContext are lazily constructued and
* initialized. Eager initialization is specified on a named object and it means
* that the object is constructed and initialized during construction of the
* WireContext. You an only specify eager initialization when the object has a
* name.
*
*
* Specifying how an object should be initialized.
*
*
* The initialization can be specified with the
* {@link AbstractDescriptor#setInit(char)} method.
*
* The possible value for init
parameter is one of :
*
* - {@link AbstractDescriptor#INIT_LAZY}: for lazy creation and delayed
* initialization
* - {@link AbstractDescriptor#INIT_REQUIRED}: for lazy creation and immediate
* initialization
* - {@link AbstractDescriptor#INIT_EAGER}: for eager creation and delayed
* initialization
* - {@link AbstractDescriptor#INIT_IMMEDIATE}: for eager creation and
* immediate initialization
*
*
* @author Tom Baeyens
* @author Guillaume Porcher (documentation)
*/
public class WireContext extends DefaultObservable implements Context,
Closable, Serializable {
private static final long serialVersionUID = 1L;
private static Log log = Log.getLog(WireContext.class.getName());
// events ///////////////////////////////////////////////////////////////////
/**
* is fired when a new wiring environment is being opened. No event info
* provided.
*/
public static final String EVENT_OPEN = "open";
/**
* is fired when the wiring environment is being closed. No event info
* provided.
*/
public static final String EVENT_CLOSE = "close";
// member fields ////////////////////////////////////////////////////////////
protected String name = "wire-context";
protected transient ClassLoader classLoader;
protected WireDefinition wireDefinition;
/** objects that are being instantiated or constructed */
Set underConstruction = null;
/**
* objects that are constructed, but waiting for the initialization operations
* (like e.g. injections) to be performed
*/
Map pendingInitializations = null;
/**
* objects on which the initialization operations (like e.g. injections) are
* being performed
*/
Map underInitialization = null;
/** fully created and initialized objects */
Map cache = null;
/** exceptions throw by descriptor invocations */
Map exceptions = null;
public WireContext() {
}
public WireContext(WireDefinition wireDefinition) {
this(wireDefinition, null, null, false);
}
/**
* when this {@link Context} is used in an {@link Environment}, it needs a
* name.
*/
public WireContext(WireDefinition wireDefinition, String name) {
this.wireDefinition = wireDefinition;
this.name = name;
this.classLoader = (wireDefinition != null ? wireDefinition
.getClassLoader() : null);
create();
}
/**
* allows for postponing the creation of this wire context.
*
* @param delayCreate
* specifies if creation should be postponed till {@link #create()}
* is called explicitly. If delayCreate is set to false, creation is
* done as part of the constructor. If delayCreate is set to true,
* the {@link #create()} method needs to be called explicitly by the
* client after construction is complete. The use case is creation of
* environment where the transactionName needs to be set and the
* scope needs to be added to the environment before the creation of
* this wire scope is done.
* @see PvmEnvironmentFactory#openEnvironment()
*/
public WireContext(WireDefinition wireDefinition, String name,
Environment environment, boolean delayCreate) {
this.wireDefinition = wireDefinition;
this.name = name;
this.classLoader = (wireDefinition != null ? wireDefinition
.getClassLoader() : null);
if (!delayCreate) {
create();
}
}
/** convenience method that wires the object for a given descriptor. */
public static Object create(Descriptor descriptor) {
WireContext wireContext = new WireContext();
return wireContext.create(descriptor, false);
}
/**
* initializes the eager objects and then fires the create event. This method
* only needs to be called explicitly in case delayCreate
is true
* in {@link #WireContext(WireDefinition, String, Environment, boolean)}.
*/
public void create() {
log.trace("creating " + name);
initializeEagerObjects();
fire(EVENT_OPEN, null);
}
/**
* Initializes all the eager objects defined in the {@link #wireDefinition}.
*/
void initializeEagerObjects() {
if (wireDefinition != null) {
List eagerInitObjectNames = wireDefinition.getEagerInitNames();
if (eagerInitObjectNames != null) {
for (String eagerInitObjectName : eagerInitObjectNames) {
Descriptor descriptor = wireDefinition
.getDescriptor(eagerInitObjectName);
if (descriptor.isEagerInit()) {
log.debug("eagerly initializing " + eagerInitObjectName);
get(eagerInitObjectName, descriptor.isDelayable());
}
}
while ((!hasObjectUnderConstruction())
&& (!hasObjectUnderInitialization())
&& (hasPendingInitializations())) {
processPendingInitializations();
}
}
}
}
public String toString() {
return (name != null ? name : super.toString());
}
// environment methods
// //////////////////////////////////////////////////////////
/**
* the list of object names defined in this context. This means the union of
* the object names that are defined in the {@link #wireDefinition} and the
* objects that are just {@link #set(String, Object)}. If there are no keys,
* an empty set will be returned.
*/
public Set keys() {
Set keys = new HashSet();
if (cache != null)
keys.addAll(cache.keySet());
if (wireDefinition != null) {
Map descriptors = wireDefinition.getDescriptors();
if (descriptors != null) {
keys.addAll(descriptors.keySet());
}
}
return keys;
}
/**
* checks if the given objectName is defined, either by means of a descriptor
* or by an explicit {@link #set(String, Object)}.
*/
public boolean has(String objectName) {
return (hasCached(objectName) || (wireDefinition != null ? wireDefinition
.hasDescriptor(objectName) : false));
}
/**
* retrieves the object for the given objectName, ensuring it is constructed
* and initialized.
*
* @return the object found, or null if the object was not found.
*/
public Object get(String objectName) {
return get(objectName, false);
}
/**
* adds an object to this context, which means storing it in the cache. This
* doesn't have to be an object that is defined by the {@link WireDefinition}.
* If an object is set under a certain objectName that also is associated with
* a descriptor, the object provided in this set invocation will be delivered
* upon subsequent {@link #get(String)} requests.
*
* @return previous value of the object with the name objectName in the
* {@link #cache}
* @throws WireException
* when the objectName is null
*/
public synchronized Object set(String objectName, Object object) {
if (objectName == null) {
String message = ExceptionManager.getInstance().getFullMessage("bp_WC_1");
throw new WireException(message);
}
if (cache == null) {
cache = new HashMap();
}
fireObjectEvent(Descriptor.EVENT_SET, objectName, object);
return cache.put(objectName, object);
}
/**
* removes an object from the context and fires the remove event.
*
* @return previous object associated with the given name, or null if there
* was no mapping for this name.
*/
public Object remove(String objectName) {
Object removed = null;
if (cache != null) {
removed = cache.remove(objectName);
fireObjectEvent(Descriptor.EVENT_REMOVE, objectName, removed);
}
return removed;
}
/** clears the {@link #cache}. */
public synchronized void clear() {
if (cache != null) {
Set objectsInCache = new HashSet(cache.keySet());
for (String object : objectsInCache) {
remove(object);
}
}
}
/**
* fires the close event then removes the listeners, and cleans up the
* constructed objects of the context (cleans up the object in the cache and
* the object in construction).
*
* @see #EVENT_CLOSE
*/
public synchronized void close() {
log.trace("closing " + name + "...");
// fire the close event
fire(EVENT_CLOSE, null);
}
// object access helper methods /////////////////////////////////////////////
/**
* gets the object having the name objectName
in this context.
*
* @param isDelayable
* indicates wether initialization is delayable. When isDelayable is
* set to false the returned object will be constructed and
* initialized. When isDelayable is set to true, the returned object
* will be constructed, but not necessarily initialized.
* @return the object found or created, or null if the object was not found
* and cannot be created.
* @throws WireException
* if a circular dependency was found during the object creation.
*/
public synchronized Object get(String objectName, boolean isDelayable) {
if (hasException(objectName)) {
String message = ExceptionManager.getInstance().getFullMessage("bp_WC_2", objectName);
throw new WireException(message, exceptions.get(objectName));
}
// first check if the object is in the cache
if (hasCached(objectName)) {
Object object = cache.get(objectName);
log.trace("delivering " + objectName);
return object;
}
// then check if it is constructed, but not yet in the cache (pending or
// under initialization)
Object constructed = getConstructed(objectName);
if (isDelayable && (null != constructed)) {
Object object = constructed;
log.trace("providing already constructed " + objectName);
return object;
}
// then check if we can create the object from a descriptor
if (wireDefinition.hasDescriptor(objectName)) {
if (isUnderConstruction(objectName) || isUnderInitialization(objectName)) {
String message = ExceptionManager.getInstance().getFullMessage("bp_WC_3", objectName);
throw new WireException(message);
}
return create(objectName, isDelayable);
}
// then check if we can find it in the environment (if one is available)
Environment environment = Environment.getCurrent();
if (environment != null) {
log.trace("delivering " + objectName + " from environment");
return environment.get(objectName);
}
log.trace("delivering null for undefined object " + objectName);
return null;
}
/**
* creates a new object for the given objectName as defined in the
* {@link #wireDefinition}.
*
* @param isDelayable
* indicates wether initialization is delayable. When isDelayable is
* set to false the returned object will be constructed and
* initialized. When isDelayable is set to true, the returned object
* will be constructed, but not necessarily initialized.
*/
protected Object create(String objectName, boolean isDelayable) {
Descriptor descriptor = wireDefinition.getDescriptor(objectName);
return create(descriptor, isDelayable);
}
/**
* creates a new object for the given descriptor.
*
* @param isDelayable
* indicates wether initialization is delayable. When isDelayable is
* set to false the returned object will be constructed and
* initialized. When isDelayable is set to true, the returned object
* will be constructed, but not necessarily initialized.
*/
public Object create(Descriptor descriptor, boolean isDelayable) {
Object object = null;
object = construct(descriptor);
initialize(object, descriptor, isDelayable);
processPendingInitializations();
return object;
}
Object construct(Descriptor descriptor) {
Object object;
String objectName = descriptor.getName();
if (objectName != null) {
fireObjectEvent(Descriptor.EVENT_CONSTRUCTING, objectName, null);
if (underConstruction == null) {
underConstruction = new HashSet();
}
underConstruction.add(objectName);
log.trace("constructing " + objectName);
}
try {
object = descriptor.construct(this);
} catch (RuntimeException e) {
addException(descriptor, e);
throw e;
}
if (objectName != null) {
underConstruction.remove(objectName);
}
return object;
}
// initialization ///////////////////////////////////////////////////////////
private enum InitializationType {
NONE, IMMEDIATE, DELAYEBLE
}
void initialize(Object object, Descriptor descriptor, boolean isDelayable) {
InitializationType initializationType = getInitializationType(object,
descriptor, isDelayable);
if (initializationType == InitializationType.IMMEDIATE) {
performInitialization(object, descriptor);
} else if (initializationType == InitializationType.DELAYEBLE) {
addPendingInitialization(object, descriptor);
} else {
String objectName = descriptor.getName();
if (objectName != null) {
set(objectName, object);
}
}
}
InitializationType getInitializationType(Object object,
Descriptor descriptor, boolean isDelayable) {
if (object == null) {
return InitializationType.NONE;
}
if (isDelayable && descriptor.isDelayable()) {
return InitializationType.DELAYEBLE;
}
return InitializationType.IMMEDIATE;
}
void performInitialization(Object object, Descriptor descriptor) {
String objectName = descriptor.getName();
if (objectName != null) {
fireObjectEvent(Descriptor.EVENT_INITIALIZING, objectName, object);
if (underInitialization == null) {
underInitialization = new HashMap();
}
underInitialization.put(objectName, object);
log.trace("initializing " + objectName);
}
try {
descriptor.initialize(object, this);
} catch (RuntimeException e) {
addException(descriptor, e);
throw e;
}
if (objectName != null) {
underInitialization.remove(objectName);
// event constructed is fired before the object is put in the cache
// because that generates a set event
fireObjectEvent(Descriptor.EVENT_CONSTRUCTED, objectName, object);
set(objectName, object);
}
}
void addPendingInitialization(Object object, Descriptor descriptor) {
if (pendingInitializations == null) {
pendingInitializations = new HashMap();
}
pendingInitializations.put(descriptor.getName(), new PendingInitialization(
object, descriptor));
}
void processPendingInitializations() {
if (pendingInitializations != null) {
Collection pendingInitializationValues = new HashSet(
pendingInitializations.values());
for (PendingInitialization pi : pendingInitializationValues) {
// move pi from pending initializations to under initialization
String objectName = pi.initializable.getName();
pi = pendingInitializations.remove(objectName);
if (pi != null) {
// initialize
performInitialization(pi.object, pi.initializable);
}
}
}
}
boolean hasPendingInitializations() {
return ((pendingInitializations != null) && (!pendingInitializations
.isEmpty()));
}
/**
* container for an storing waiting objects and their initializable in the
* list {@link #pendingInitializations}, while waiting for initialization.
*/
class PendingInitialization implements Serializable {
private static final long serialVersionUID = 1L;
Object object;
Descriptor initializable;
public PendingInitialization(Object object, Descriptor descriptor) {
this.object = object;
this.initializable = descriptor;
}
public String toString() {
String objectName = initializable.getName();
return "PendingInitialization["
+ (objectName != null ? objectName + "|" : "") + object + "]";
}
}
/**
* checks if the given objectName is available in the cache, which means it
* already has been constructed from a wire definition or it has been
* {@link #set(String, Object)} explicitely.
*/
public boolean hasCached(String objectName) {
return (cache != null) && (cache.containsKey(objectName));
}
/** finds the object in all stages after construction. */
Object getConstructed(String objectName) {
Object constructed = null;
if ((pendingInitializations != null)
&& (pendingInitializations.containsKey(objectName))) {
constructed = pendingInitializations.get(objectName).object;
} else if ((underInitialization != null)
&& (underInitialization.containsKey(objectName))) {
constructed = underInitialization.get(objectName);
}
return constructed;
}
/** fires a {@link WireObjectEventInfo}. */
protected void fireObjectEvent(String eventName, String objectName,
Object object) {
WireObjectEventInfo wireEvent = null;
// first fire the event on the descriptor for object specific listeners
if (wireDefinition != null) {
Map descriptors = wireDefinition.getDescriptors();
if (descriptors != null) {
Descriptor descriptor = descriptors.get(objectName);
if (descriptor != null) {
if (wireEvent == null) {
wireEvent = new WireObjectEventInfo(eventName, objectName, object);
}
descriptor.fire(eventName, wireEvent);
}
}
}
// then fire the event on this wiring environment for global listeners
if ((listeners != null) && (wireEvent == null)) {
wireEvent = new WireObjectEventInfo(eventName, objectName, object);
}
fire(eventName, wireEvent);
}
boolean hasObjectUnderConstruction() {
return ((underConstruction != null) && (!underConstruction.isEmpty()));
}
boolean hasObjectUnderInitialization() {
return ((underInitialization != null) && (!underInitialization.isEmpty()));
}
boolean isUnderConstruction(String objectName) {
return ((underConstruction != null) && (underConstruction
.contains(objectName)));
}
boolean isUnderInitialization(String objectName) {
return ((underInitialization != null) && (underInitialization
.containsKey(objectName)));
}
/**
* the class loader to use to create objects or if none was explicitely set in
* this wire context, the default context class loader for the current thread.
*/
public ClassLoader getClassLoader() {
// if there is a specific classloader specified
if (classLoader != null) {
return classLoader;
}
// otherwise, use the current environment classloader
return Thread.currentThread().getContextClassLoader();
}
public boolean hasClassLoader() {
return classLoader != null;
}
// search by class //////////////////////////////////////////////////////////
/**
* searches for the first descriptor that defines an object of the given type.
* In case of multiple objects of the same type, the first object that is
* declared of the given type will be found. Also super classes and interfaces
* are taken into account. Not all descriptor types will be type sensitive,
* only:
*
*
* | ObjectDescriptor | object |
* | HibernatePersistenceServiceDescriptor | business-calendar |
* | TransactionDescriptor | transaction |
* | PropertiesDescriptor | properties |
* | BusinessCalendarDescriptor | business-calendar |
* </ul>
*
*/
@SuppressWarnings("unchecked")
public T get(Class type) {
if (wireDefinition != null) {
String name = wireDefinition.getDescriptorName(type);
if (name != null) {
return (T) get(name);
}
}
return null;
}
protected boolean hasException(String objectName) {
return ((exceptions != null) && (exceptions.containsKey(objectName)));
}
protected void addException(Descriptor descriptor, Exception exception) {
if (exceptions == null) {
exceptions = new HashMap();
}
exceptions.put(descriptor.getName(), exception);
}
// getters and setters //////////////////////////////////////////////////////
public String getName() {
return name;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public WireDefinition getWireDefinition() {
return wireDefinition;
}
public void setWireDefinition(WireDefinition wireDefinition) {
this.wireDefinition = wireDefinition;
}
}