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

org.ow2.bonita.env.WireContext Maven / Gradle / Ivy

The newest version!
/*
 * 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.env;

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 java.util.logging.Level;
import java.util.logging.Logger;

import org.ow2.bonita.definition.activity.ExternalActivity;
import org.ow2.bonita.env.descriptor.AbstractDescriptor;
import org.ow2.bonita.env.descriptor.ObjectDescriptor;
import org.ow2.bonita.env.operation.FieldOperation;
import org.ow2.bonita.env.operation.InvokeOperation;
import org.ow2.bonita.env.operation.Operation;
import org.ow2.bonita.env.operation.PropertyOperation;
import org.ow2.bonita.env.operation.SubscribeOperation;
import org.ow2.bonita.env.xml.WireParser;
import org.ow2.bonita.util.Closable;
import org.ow2.bonita.util.DefaultObservable;
import org.ow2.bonita.util.ExceptionManager;
import org.ow2.bonita.util.Observable;

/**
 * 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 ExternalActivity}s and {@link org.ow2.bonita.definition.activity.ExternalActivity} and other user code can be * instantiated with a WireContext. That way, they can be persisted in a fixed 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 final Logger LOG = Logger.getLogger(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() { super(); } public WireContext(final WireDefinition wireDefinition) { this(wireDefinition, null, false); } /** * when this {@link Context} is used in an {@link Environment}, it needs a name. */ public WireContext(final WireDefinition wireDefinition, final String name) { super(); this.wireDefinition = wireDefinition; this.name = name; 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(final WireDefinition wireDefinition, final String name, final boolean delayCreate) { super(); this.wireDefinition = wireDefinition; this.name = name; classLoader = wireDefinition != null ? wireDefinition.getClassLoader() : null; if (!delayCreate) { create(); } } /** convenience method that wires the object for a given descriptor. */ public static Object create(final Descriptor descriptor) { final 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() { if (LOG.isLoggable(Level.FINE)) { LOG.fine("creating " + name); } initializeEagerObjects(); fire(EVENT_OPEN, null); } /** * Initializes all the eager objects defined in the {@link #wireDefinition}. */ void initializeEagerObjects() { if (wireDefinition != null) { final List eagerInitObjectNames = wireDefinition.getEagerInitNames(); if (eagerInitObjectNames != null) { for (final String eagerInitObjectName : eagerInitObjectNames) { final Descriptor descriptor = wireDefinition.getDescriptor(eagerInitObjectName); if (descriptor.isEagerInit()) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("eagerly initializing " + eagerInitObjectName); } get(eagerInitObjectName, descriptor.isDelayable()); } } while (!hasObjectUnderConstruction() && !hasObjectUnderInitialization() && hasPendingInitializations()) { processPendingInitializations(); } } } } @Override 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. */ @Override public Set keys() { final Set keys = new HashSet(); if (cache != null) { keys.addAll(cache.keySet()); } if (wireDefinition != null) { final 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)}. */ @Override public boolean has(final 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. */ @Override public Object get(final 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 */ @Override public synchronized Object set(final String objectName, final Object object) { if (objectName == null) { final 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(final 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) { final Set objectsInCache = new HashSet(cache.keySet()); for (final 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 */ @Override public synchronized void close() { if (LOG.isLoggable(Level.FINE)) { LOG.fine("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(final String objectName, final boolean isDelayable) { if (hasException(objectName)) { final 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)) { final Object object = cache.get(objectName); if (LOG.isLoggable(Level.FINE)) { LOG.fine("delivering " + objectName); } return object; } // then check if it is constructed, but not yet in the cache (pending or // under initialization) final Object constructed = getConstructed(objectName); if (isDelayable && null != constructed) { final Object object = constructed; if (LOG.isLoggable(Level.FINE)) { LOG.fine("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)) { final 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) final Environment environment = Environment.getCurrent(); if (environment != null) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("delivering " + objectName + " from environment"); } return environment.get(objectName); } if (LOG.isLoggable(Level.FINE)) { LOG.fine("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(final String objectName, final boolean isDelayable) { final 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(final Descriptor descriptor, final boolean isDelayable) { Object object = null; object = construct(descriptor); initialize(object, descriptor, isDelayable); processPendingInitializations(); return object; } Object construct(final Descriptor descriptor) { Object object; final String objectName = descriptor.getName(); if (objectName != null) { fireObjectEvent(Descriptor.EVENT_CONSTRUCTING, objectName, null); if (underConstruction == null) { underConstruction = new HashSet(); } underConstruction.add(objectName); if (LOG.isLoggable(Level.FINE)) { LOG.fine("constructing " + objectName); } } try { object = descriptor.construct(this); } catch (final RuntimeException e) { addException(descriptor, e); throw e; } if (objectName != null) { underConstruction.remove(objectName); } return object; } // initialization /////////////////////////////////////////////////////////// private enum InitializationType { NONE, IMMEDIATE, DELAYEBLE } void initialize(final Object object, final Descriptor descriptor, final boolean isDelayable) { final InitializationType initializationType = getInitializationType(object, descriptor, isDelayable); if (initializationType == InitializationType.IMMEDIATE) { performInitialization(object, descriptor); } else if (initializationType == InitializationType.DELAYEBLE) { addPendingInitialization(object, descriptor); } else { final String objectName = descriptor.getName(); if (objectName != null) { set(objectName, object); } } } InitializationType getInitializationType(final Object object, final Descriptor descriptor, final boolean isDelayable) { if (object == null) { return InitializationType.NONE; } if (isDelayable && descriptor.isDelayable()) { return InitializationType.DELAYEBLE; } return InitializationType.IMMEDIATE; } void performInitialization(final Object object, final Descriptor descriptor) { final String objectName = descriptor.getName(); if (objectName != null) { fireObjectEvent(Descriptor.EVENT_INITIALIZING, objectName, object); if (underInitialization == null) { underInitialization = new HashMap(); } underInitialization.put(objectName, object); if (LOG.isLoggable(Level.FINE)) { LOG.fine("initializing " + objectName); } } try { descriptor.initialize(object, this); } catch (final 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(final Object object, final Descriptor descriptor) { if (pendingInitializations == null) { pendingInitializations = new HashMap(); } pendingInitializations.put(descriptor.getName(), new PendingInitialization(object, descriptor)); } void processPendingInitializations() { if (pendingInitializations != null) { final Collection pendingInitializationValues = new HashSet( pendingInitializations.values()); for (PendingInitialization pi : pendingInitializationValues) { // move pi from pending initializations to under initialization final 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(final Object object, final Descriptor descriptor) { this.object = object; initializable = descriptor; } @Override public String toString() { final 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(final String objectName) { return cache != null && cache.containsKey(objectName); } /** finds the object in all stages after construction. */ Object getConstructed(final 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(final String eventName, final String objectName, final Object object) { WireObjectEventInfo wireEvent = null; // first fire the event on the descriptor for object specific listeners if (wireDefinition != null) { final Map descriptors = wireDefinition.getDescriptors(); if (descriptors != null) { final Descriptor descriptor = descriptors.get(objectName); if (descriptor != 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(final String objectName) { return underConstruction != null && underConstruction.contains(objectName); } boolean isUnderInitialization(final 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>
   * 
*/ @Override @SuppressWarnings("unchecked") public T get(final Class type) { if (wireDefinition != null) { final String name = wireDefinition.getDescriptorName(type); if (name != null) { return (T) get(name); } } return null; } protected boolean hasException(final String objectName) { return exceptions != null && exceptions.containsKey(objectName); } protected void addException(final Descriptor descriptor, final Exception exception) { if (exceptions == null) { exceptions = new HashMap(); } exceptions.put(descriptor.getName(), exception); } // getters and setters ////////////////////////////////////////////////////// @Override public String getName() { return name; } public void setClassLoader(final ClassLoader classLoader) { this.classLoader = classLoader; } public WireDefinition getWireDefinition() { return wireDefinition; } public void setWireDefinition(final WireDefinition wireDefinition) { this.wireDefinition = wireDefinition; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy