org.jbpm.JbpmConfiguration 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.jbpm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jbpm.configuration.ObjectFactory;
import org.jbpm.configuration.ObjectFactoryImpl;
import org.jbpm.configuration.ObjectFactoryParser;
import org.jbpm.configuration.ValueInfo;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.instantiation.ProcessClassLoaderFactory;
import org.jbpm.job.executor.JobExecutor;
import org.jbpm.persistence.db.DbPersistenceServiceFactory;
import org.jbpm.svc.ServiceFactory;
import org.jbpm.svc.Services;
import org.jbpm.util.ClassLoaderUtil;
/**
* configuration of one jBPM instance.
*
* During process execution, jBPM might need to use some services. A JbpmConfiguration contains
* the knowledge on how to create those services.
*
*
* A JbpmConfiguration is a thread safe object and serves as a factory for
* {@link org.jbpm.JbpmContext}s, which means one JbpmConfiguration can be used to create
* {@link org.jbpm.JbpmContext}s for all threads. The single JbpmConfiguration can be maintained
* in a static member or in the JNDI tree if that is available.
*
*
* A JbpmConfiguration can be obtained in following ways:
*
*
* - from a resource (by default
jbpm.cfg.xml
is used):
*
*
* JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
*
*
* or
*
*
* String myXmlResource = "...";
* JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance(myXmlResource);
*
*
*
* - from an XML string:
*
*
* JbpmConfiguration jbpmConfiguration = JbpmConfiguration.parseXmlString(
* "<jbpm-configuration>" +
* ...
* "</jbpm-configuration>");
*
*
*
* - By specifying a custom implementation of an object factory. This can be used to specify a
* JbpmConfiguration in other bean-style notations such as used by JBoss Microcontainer or
* Spring.
*
*
* ObjectFactory of = new <i>MyCustomObjectFactory</i>();
* JbpmConfiguration.Configs.setDefaultObjectFactory(of);
* JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
*
*
*
*
*
* JbpmConfigurations can be configured using a spring-like XML notation (in relax ng compact
* notation):
*
*
*
* datatypes xs = "http://www.w3.org/2001/XMLSchema-datatypes"
*
* start = element beans { element object* }
*
* object = {
* jbpm-context |
* bean |
* ref |
* map |
* list |
* string |
* int |
* long |
* float |
* double |
* char |
* bool |
* true |
* false |
* null
* }
*
* jbpm-context = element jbpm-context {
* ( attribute name {xsd:string},
* service*,
* save-operations?
* )
* }
*
* service = element service {
* ( attribute name {xsd:string},
* ( attribute factory {xsd:string} ) |
* ( factory )
* )
* }
*
* factory = element factory {
* ( bean |
* ref
* )
* }
*
* save-operations = element save-operations {
* ( save-operation* )
* }
*
* save-operation = element save-operation {
* ( ( attribute class {xsd:string} ) |
* ( bean |
* ref
* )
* )
* }
*
* bean = element bean {
* ( attribute ref-name {xsd:string} ) |
* ( attribute name {xsd:string}?,
* attribute class {xsd:string}?,
* attribute singleton { "true" | "false" }?,
* constructor*,
* field*,
* property*
* )
* }
*
* ref = element ref {
* ( attribute bean (xsd:string) )
* }
*
* constructor = element constructor {
* attribute class {xsd:string}?,
* ( attribute factory {xsd:string},
* attribute method {xsd:string}
* )?,
* parameter*
* }
*
* parameter = element parameter {
* attribute class {xsd:string},
* object
* }
*
* field = element field {
* attribute name {xsd:string},
* object
* }
*
* property = element property {
* ( attribute name {xsd:string} |
* attribute setter {xsd:string}
* ),
* object
* }
*
* map = element map {
* entry*
* }
*
* entry = element entry {
* key,
* value
* }
*
* key = element key {
* object
* }
*
* value = element value {
* object
* }
*
* list = element list {
* object*
* }
*
* string = element string {xsd:string}
* int = element integer {xsd:integer}
* long = element long {xsd:long}
* float = element float {xsd:string}
* double = element string {xsd:double}
* char = element char {xsd:character}
* bool = element bool { "true" | "false" }
* true = element true {}
* false = element false {}
* null = element null {}
*
*
*
*/
public class JbpmConfiguration implements Serializable {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_RESOURCE = "jbpm.cfg.xml";
static final String OBJECT_NAME = "jbpm.configuration";
private static ObjectFactory defaultObjectFactory;
private static final Map instances = new HashMap<>();
private static final ThreadLocal> threadLocalConfigurationStack = new ThreadLocal<>();
private final ObjectFactory objectFactory;
private final String resourceName;
private transient final ThreadLocal> threadLocalContextStack = new ThreadLocal<>();
private JobExecutor jobExecutor;
private volatile boolean isClosed;
public JbpmConfiguration(ObjectFactory objectFactory) {
this(objectFactory, null);
}
private JbpmConfiguration(ObjectFactory objectFactory, String resourceName) {
if (objectFactory == null) throw new IllegalArgumentException("object factory is null");
this.objectFactory = objectFactory;
this.resourceName = resourceName;
}
ObjectFactory getObjectFactory() {
return objectFactory;
}
String getResourceName() {
return resourceName;
}
public static void setDefaultObjectFactory(ObjectFactory objectFactory) {
defaultObjectFactory = objectFactory;
}
public static JbpmConfiguration getInstance() {
return getInstance(null);
}
public static JbpmConfiguration getInstance(String resource) {
if (resource == null) {
resource = DEFAULT_RESOURCE;
}
JbpmConfiguration instance;
synchronized (instances) {
// look for configuration in cache
instance = (JbpmConfiguration) instances.get(resource);
if (instance == null) {
// configuration does not exist or was evicted, construct it
if (defaultObjectFactory != null) {
log.info("configuring from default object factory");
instance = new JbpmConfiguration(defaultObjectFactory);
}
else {
log.info("configuring from resource: " + resource);
InputStream jbpmCfgXmlStream = ClassLoaderUtil.getStream(resource, false);
/*
* if a custom resource is specified, but not found in the classpath, log a warning;
* otherwise, users who want to load custom stuff will not receive any feedback when
* their resource cannot be found
*/
if (jbpmCfgXmlStream == null && !DEFAULT_RESOURCE.equals(resource)) {
log.warn("configuration resource not found: " + resource);
}
ObjectFactory objectFactory = parseObjectFactory(jbpmCfgXmlStream);
instance = createJbpmConfiguration(objectFactory, resource);
}
// put configuration in cache
instances.put(resource, instance);
}
}
return instance;
}
public static boolean hasInstance(String resource) {
return instances.containsKey(resource != null ? resource : DEFAULT_RESOURCE);
}
protected static ObjectFactory parseObjectFactory(InputStream inputStream) {
ObjectFactoryParser objectFactoryParser = new ObjectFactoryParser();
ObjectFactoryImpl objectFactory = new ObjectFactoryImpl();
objectFactoryParser.parseElementsFromResource("org/jbpm/default.jbpm.cfg.xml", objectFactory);
if (inputStream != null) {
objectFactoryParser.parseElementsStream(inputStream, objectFactory);
}
return objectFactory;
}
private static ObjectFactory loadDefaultObjectFactory() {
log.info("loading default configuration");
return ObjectFactoryParser.parseResource("org/jbpm/default.jbpm.cfg.xml");
}
public static JbpmConfiguration parseXmlString(String xml) {
ObjectFactory objectFactory;
if (xml != null) {
log.info("configuring from xml string");
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
objectFactory = parseObjectFactory(inputStream);
}
else {
objectFactory = loadDefaultObjectFactory();
}
return createJbpmConfiguration(objectFactory);
}
protected static JbpmConfiguration createJbpmConfiguration(ObjectFactory objectFactory) {
return createJbpmConfiguration(objectFactory, null);
}
private static JbpmConfiguration createJbpmConfiguration(ObjectFactory objectFactory,
String resourceName) {
JbpmConfiguration jbpmConfiguration = new JbpmConfiguration(objectFactory, resourceName);
// make the configuration available to other objects
if (objectFactory instanceof ObjectFactoryImpl) {
ObjectFactoryImpl objectFactoryImpl = (ObjectFactoryImpl) objectFactory;
objectFactoryImpl.addObjectInfo(new ValueInfo("jbpmConfiguration", jbpmConfiguration));
objectFactoryImpl.addObjectInfo(new ValueInfo(OBJECT_NAME, jbpmConfiguration));
}
return jbpmConfiguration;
}
public static JbpmConfiguration parseInputStream(InputStream inputStream) {
ObjectFactory objectFactory;
if (inputStream != null) {
log.info("configuring from " + inputStream);
objectFactory = parseObjectFactory(inputStream);
}
else {
objectFactory = loadDefaultObjectFactory();
}
return createJbpmConfiguration(objectFactory);
}
public static JbpmConfiguration parseResource(String resource) {
ObjectFactory objectFactory;
if (resource != null) {
log.info("configuring from resource: " + resource);
InputStream inputStream = ClassLoaderUtil.getStream(resource, false);
if (inputStream == null) {
throw new IllegalArgumentException("resource not found: " + resource);
}
objectFactory = parseObjectFactory(inputStream);
try {
inputStream.close();
}
catch (IOException e) {
log.warn("failed to close resource: " + resource, e);
}
}
else {
objectFactory = loadDefaultObjectFactory();
}
return createJbpmConfiguration(objectFactory, resource);
}
public JbpmContext createJbpmContext() {
return createJbpmContext(JbpmContext.DEFAULT_JBPM_CONTEXT_NAME);
}
public JbpmContext createJbpmContext(String name) {
ensureOpen();
JbpmContext jbpmContext = (JbpmContext) objectFactory.createObject(name);
pushJbpmContext(jbpmContext);
return jbpmContext;
}
public ServiceFactory getServiceFactory(String serviceName) {
return getServiceFactory(serviceName, JbpmContext.DEFAULT_JBPM_CONTEXT_NAME);
}
public ServiceFactory getServiceFactory(String serviceName, String jbpmContextName) {
JbpmContext jbpmContext = createJbpmContext(jbpmContextName);
try {
return jbpmContext.getServices().getServiceFactory(serviceName);
}
finally {
jbpmContext.close();
}
}
private DbPersistenceServiceFactory getPersistenceServiceFactory(String jbpmContextName) {
return (DbPersistenceServiceFactory) getServiceFactory(Services.SERVICENAME_PERSISTENCE, jbpmContextName);
}
public static ClassLoader getProcessClassLoader(ProcessDefinition processDefinition) {
ProcessClassLoaderFactory factory = (ProcessClassLoaderFactory) Configs.getObject("process.class.loader.factory");
return factory.getProcessClassLoader(processDefinition);
}
/**
* access to configuration information through the current {@link JbpmContext}
*/
public static class Configs {
private Configs() {
// hide default constructor to prevent instantiation
}
public static ObjectFactory getObjectFactory() {
JbpmContext jbpmContext = JbpmContext.getCurrentJbpmContext();
return jbpmContext != null ? jbpmContext.getObjectFactory()
: getInstance().getObjectFactory();
}
/**
* @deprecated call {@link JbpmConfiguration#setDefaultObjectFactory(ObjectFactory)} instead
*/
public static void setDefaultObjectFactory(ObjectFactory objectFactory) {
JbpmConfiguration.setDefaultObjectFactory(objectFactory);
}
public static boolean hasObject(String name) {
ObjectFactory objectFactory = getObjectFactory();
return objectFactory.hasObject(name);
}
public static synchronized Object getObject(String name) {
ObjectFactory objectFactory = getObjectFactory();
return objectFactory.createObject(name);
}
public static String getString(String name) {
return (String) getObject(name);
}
public static long getLong(String name) {
return ((Long) getObject(name)).longValue();
}
public static int getInt(String name) {
return ((Integer) getObject(name)).intValue();
}
public static boolean getBoolean(String name) {
return ((Boolean) getObject(name)).booleanValue();
}
}
public void cleanSchema() {
cleanSchema(JbpmContext.DEFAULT_JBPM_CONTEXT_NAME);
}
public void cleanSchema(String jbpmContextName) {
getPersistenceServiceFactory(jbpmContextName).cleanSchema();
}
public void createSchema() {
createSchema(JbpmContext.DEFAULT_JBPM_CONTEXT_NAME);
}
public void createSchema(String jbpmContextName) {
getPersistenceServiceFactory(jbpmContextName).createSchema();
}
public void dropSchema() {
dropSchema(JbpmContext.DEFAULT_JBPM_CONTEXT_NAME);
}
public void dropSchema(String jbpmContextName) {
getPersistenceServiceFactory(jbpmContextName).dropSchema();
}
private void ensureOpen() {
if (isClosed) throw new JbpmException(this + " is closed");
}
public boolean isClosed() {
return isClosed;
}
public void close() {
close(JbpmContext.DEFAULT_JBPM_CONTEXT_NAME);
}
public void close(String jbpmContextName) {
// prevent configuration from being closed more than once
if (isClosed) return;
synchronized (this) {
// stop job executor
if (jobExecutor != null) {
try {
jobExecutor.stopAndJoin();
}
catch (InterruptedException e) {
// reassert interruption and continue
Thread.currentThread().interrupt();
}
jobExecutor = null;
}
// close remaining contexts
List contextStack = threadLocalContextStack.get();
if (contextStack != null && !contextStack.isEmpty()) {
log.warn("closing "
+ contextStack.size()
+ " open contexts;"
+ " make sure to close JbpmContext after use");
// copy to array because JbpmContext.close() pops the context off the stack
JbpmContext[] jbpmContexts = (JbpmContext[]) contextStack.toArray(new JbpmContext[contextStack.size()]);
for (int i = 0; i < jbpmContexts.length; i++) {
jbpmContexts[i].close();
}
}
// close service factories
JbpmContext jbpmContext = createJbpmContext(jbpmContextName);
try {
Map serviceFactories = jbpmContext.getServices().getServiceFactories();
if (serviceFactories != null) {
for (Iterator i = serviceFactories.values().iterator(); i.hasNext();) {
Object sf = i.next();
if (sf instanceof ServiceFactory) {
ServiceFactory serviceFactory = (ServiceFactory) sf;
serviceFactory.close();
}
}
}
}
finally {
jbpmContext.close();
}
// release thread-local context stack
threadLocalContextStack.set(null);
}
// closing service factories requires open configuration
isClosed = true;
// remove from configuration cache
if (resourceName != null) {
synchronized (instances) {
instances.remove(resourceName);
}
}
}
static JbpmConfiguration getCurrentJbpmConfiguration() {
List stack = (List) threadLocalConfigurationStack.get();
return (stack == null || stack.isEmpty()) ? null
: (JbpmConfiguration) stack.get(stack.size() - 1);
}
static void clearInstances() {
synchronized (instances) {
instances.clear();
}
}
public JbpmContext getCurrentJbpmContext() {
ensureOpen();
List stack = (List) threadLocalContextStack.get();
return (stack == null || stack.isEmpty()) ? null
: (JbpmContext) stack.get(stack.size() - 1);
}
void pushJbpmContext(JbpmContext jbpmContext) {
// first push the configuration
List configStack = threadLocalConfigurationStack.get();
if (configStack == null) {
configStack = new ArrayList<>();
threadLocalConfigurationStack.set(configStack);
}
configStack.add(this);
// then push the context
List contextStack = threadLocalContextStack.get();
if (contextStack == null) {
contextStack = new ArrayList<>();
threadLocalContextStack.set(contextStack);
}
contextStack.add(jbpmContext);
}
private static void remove(ThreadLocal threadLocal) {
threadLocal.remove();
}
void popJbpmContext(JbpmContext jbpmContext) {
boolean threadSafetyFlag = false;
boolean creationOrderFlag = false;
// first pop the context
List contextStack = (List) threadLocalContextStack.get();
int contextIndex;
if (contextStack == null || (contextIndex = contextStack.lastIndexOf(jbpmContext)) == -1) {
threadSafetyFlag = true;
}
else {
if (contextIndex != contextStack.size() - 1) {
creationOrderFlag = true;
}
// prevent context from remaining in the stack, no matter what
contextStack.remove(contextIndex);
// if context stack gets empty, remove thread-local variable
if (contextStack.isEmpty()) remove(threadLocalContextStack);
}
// then pop the configuration
List configStack = (List) threadLocalConfigurationStack.get();
int configIndex;
if (configStack == null || (configIndex = configStack.lastIndexOf(this)) == -1) {
threadSafetyFlag = true;
}
else {
if (configIndex != configStack.size() - 1) {
creationOrderFlag = true;
}
// prevent configuration from remaining in the stack, no matter what
configStack.remove(configIndex);
// if configuration stack gets empty, remove thread-local variable
if (configStack.isEmpty()) remove(threadLocalConfigurationStack);
}
if (threadSafetyFlag) {
log.warn(jbpmContext
+ " was not closed in the thread that created it;"
+ " JbpmContext is not safe for access from multiple threads!");
}
else if (creationOrderFlag) {
log.warn(jbpmContext
+ " was not closed in a block-structured manner;"
+ " check try-finally clauses around JbpmContext blocks");
}
}
public void startJobExecutor() {
getJobExecutor().start();
}
public JobExecutor getJobExecutor() {
ensureOpen();
synchronized (this) {
if (jobExecutor == null) {
jobExecutor = (JobExecutor) objectFactory.createObject("jbpm.job.executor");
}
}
return jobExecutor;
}
public String toString() {
return "JbpmConfiguration"
+ (resourceName != null ? '(' + resourceName + ')'
: '@' + Integer.toHexString(hashCode()));
}
private static final Log log = LogFactory.getLog(JbpmConfiguration.class);
}