/*
* 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.jbpm.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.jbpm.api.activity.ActivityBehaviour;
import org.jbpm.internal.log.Log;
import org.jbpm.pvm.internal.env.Context;
import org.jbpm.pvm.internal.env.EnvironmentImpl;
import org.jbpm.pvm.internal.model.ProcessElementImpl;
import org.jbpm.pvm.internal.processengine.ProcessEngineImpl;
import org.jbpm.pvm.internal.util.Closable;
import org.jbpm.pvm.internal.util.DefaultObservable;
import org.jbpm.pvm.internal.util.Observable;
import org.jbpm.pvm.internal.wire.descriptor.AbstractDescriptor;
import org.jbpm.pvm.internal.wire.descriptor.ObjectDescriptor;
import org.jbpm.pvm.internal.wire.operation.FieldOperation;
import org.jbpm.pvm.internal.wire.operation.InvokeOperation;
import org.jbpm.pvm.internal.wire.operation.Operation;
import org.jbpm.pvm.internal.wire.operation.PropertyOperation;
import org.jbpm.pvm.internal.wire.operation.SubscribeOperation;
import org.jbpm.pvm.internal.wire.xml.WireParser;
/**
* 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 EnvironmentImpl} to
* decouple the processDefinition virtual machine from its environment. In the
* {@link ProcessEngineImpl}, both the process-engine 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 ActivityBehaviour}s and {@link org.jbpm.api.activity.ExternalActivityBehaviour}
* 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}.
*
*
* EnvironmentImpl
*
* 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 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, false);
}
/** when this {@link Context} is used in an {@link EnvironmentImpl}, it
* needs a name. */
public WireContext(WireDefinition wireDefinition, String name) {
this.wireDefinition = wireDefinition;
this.name = name;
create();
}
/**
* allows for postponing the creation of this wire context.
* @param delayCreate specifies if creation should be postponed till {@link #createTime()} 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 #createTime()} 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 ProcessEngineImpl#openEnvironment()
*/
public WireContext(WireDefinition wireDefinition, String name, boolean delayCreate) {
this.wireDefinition = wireDefinition;
this.name = name;
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, EnvironmentImpl, 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) throw new WireException("objectName is null");
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)) {
throw new WireException("getting "+objectName+" previously resulted in an exception", 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
boolean hasDescriptor = (wireDefinition!=null ? wireDefinition.hasDescriptor(objectName) : false);
if (hasDescriptor) {
if (isUnderConstruction(objectName) || isUnderInitialization(objectName)) {
throw new WireException("circular dependency for "+objectName);
}
return create(objectName, isDelayable);
}
// then check if we can find it in the environment (if one is available)
EnvironmentImpl environment = EnvironmentImpl.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))
) ;
}
// 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 |
*
*
*/
public T get(Class type) {
if (wireDefinition!=null) {
String name = wireDefinition.getDescriptorName(type);
if (name!=null) {
log.trace("found "+type.getName()+" in "+this);
return type.cast(get(name));
} else {
log.trace(type.getName()+" not found in "+this+" "+System.identityHashCode(this));
}
}
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 WireDefinition getWireDefinition() {
return wireDefinition;
}
public void setWireDefinition(WireDefinition wireDefinition) {
this.wireDefinition = wireDefinition;
}
}