/*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jbpm.services.cdi.impl.manager;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.UnsatisfiedResolutionException;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.drools.compiler.kie.util.InjectionHelper;
import org.drools.core.util.StringUtils;
import org.jbpm.process.audit.AbstractAuditLogger;
import org.jbpm.process.audit.AuditLoggerFactory;
import org.jbpm.process.audit.JPAWorkingMemoryDbLogger;
import org.jbpm.process.audit.event.AuditEventBuilder;
import org.jbpm.runtime.manager.api.qualifiers.Agenda;
import org.jbpm.runtime.manager.api.qualifiers.Process;
import org.jbpm.runtime.manager.api.qualifiers.Task;
import org.jbpm.runtime.manager.api.qualifiers.WorkingMemory;
import org.jbpm.runtime.manager.impl.DefaultRegisterableItemsFactory;
import org.jbpm.runtime.manager.impl.RuntimeEngineImpl;
import org.jbpm.runtime.manager.impl.jpa.EntityManagerFactoryManager;
import org.kie.api.builder.model.KieSessionModel;
import org.kie.api.event.process.ProcessEventListener;
import org.kie.api.event.rule.AgendaEventListener;
import org.kie.api.event.rule.RuleRuntimeEventListener;
import org.kie.api.executor.ExecutorService;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.manager.RegisterableItemsFactory;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.manager.RuntimeManager;
import org.kie.api.runtime.process.WorkItemHandler;
import org.kie.api.task.TaskLifeCycleEventListener;
import org.kie.internal.runtime.conf.AuditMode;
import org.kie.internal.runtime.conf.DeploymentDescriptor;
import org.kie.internal.runtime.manager.EventListenerProducer;
import org.kie.internal.runtime.manager.GlobalProducer;
import org.kie.internal.runtime.manager.WorkItemHandlerProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of <code>RegisterableItemsFactory</code> dedicated to CDI environments that allows us to get
* injections of following components:
* <ul>
* <li><code>ExternalTaskEventListener</code> - required bean</li>
* <li><code>WorkItemHandlerProducer</code> - optional bean (0 or more)</li>
* <li><code>EventListenerProducer<ProcessEventListener>></code> - optional bean (0 or more)</li>
* <li><code>EventListenerProducer<AgendaEventListener>></code> - optional bean (0 or more)</li>
* <li><code>EventListenerProducer<WorkingMemoryEventListener>></code> - optional bean (0 or more)</li>
* <li><code>RuntimeFinder</code> - optional required only when single CDI bean is going to manage many
* <code>RuntimeManager</code> instances</li>
* </ul>
* In addition to that, <code>AbstractAuditLogger</code> can be set after the bean has been injected if the default
* is not sufficient. Although this factory extends <code>DefaultRegisterableItemsFactory</code>, it will not
* use any of the listeners and handlers that come from the super class. It relies mainly on CDI injections
* where the only exception from this rule is <code>AbstractAuditLogger</code>
* <br/>
* Even though this is a fully qualified bean for injection, it provides helper methods to build its instances
* using <code>BeanManager</code> in case more independent instances are required.
* <ul>
* <li>getFactory(BeanManager, AbstractAuditLogger)</li>
* <li>getFactory(BeanManager, AbstractAuditLogger, KieContainer, String)</li>
* </ul>
*/
public class InjectableRegisterableItemsFactory extends DefaultRegisterableItemsFactory {
private static final String DEFAULT_KIE_SESSION = "defaultKieSession";
private static final Logger logger = LoggerFactory.getLogger(InjectableRegisterableItemsFactory.class);
// optional injections
@Inject
@Any
private Instance<GlobalProducer> globalProducer;
@Inject
@Any
private Instance<WorkItemHandlerProducer> workItemHandlerProducer;
@Inject
@Process
private Instance<EventListenerProducer<ProcessEventListener>> processListenerProducer;
@Inject
@Agenda
private Instance<EventListenerProducer<AgendaEventListener>> agendaListenerProducer;
@Inject
@WorkingMemory
private Instance<EventListenerProducer<RuleRuntimeEventListener>> wmListenerProducer;
@Inject
@Task
private Instance<EventListenerProducer<TaskLifeCycleEventListener>> taskListenerProducer;
@Inject
private Instance<ExecutorService> executorService;
private AbstractAuditLogger auditlogger;
// to handle kmodule approach
private KieContainer kieContainer;
private String ksessionName;
@Override
public Map<String, WorkItemHandler> getWorkItemHandlers(RuntimeEngine runtime) {
Map<String, WorkItemHandler> handler = new HashMap<String, WorkItemHandler>();
handler.put("Human Task", getHTWorkItemHandler(runtime));
RuntimeManager manager = ((RuntimeEngineImpl)runtime).getManager();
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("ksession", runtime.getKieSession());
parameters.put("taskService", runtime.getTaskService());
parameters.put("runtimeManager", manager);
parameters.put("kieContainer", getRuntimeManager().getKieContainer());
try {
parameters.put("executorService", executorService.get());
} catch (Exception e) {
logger.debug("Executor service not available due to {}", e.getMessage());
}
if (kieContainer != null) {
// add classloader as one of the parameters so it can be easily referenced
parameters.put("classLoader", kieContainer.getClassLoader());
KieSessionModel ksessionModel = null;
if(StringUtils.isEmpty(ksessionName)) {
ksessionModel = ((KieContainerImpl)kieContainer).getKieProject().getDefaultKieSession();
if (ksessionModel == null) {
ksessionModel = ((KieContainerImpl)kieContainer).getKieSessionModel(DEFAULT_KIE_SESSION);
}
} else {
ksessionModel = ((KieContainerImpl)kieContainer).getKieSessionModel(ksessionName);
}
if (ksessionModel == null) {
throw new IllegalStateException("Cannot find ksession, either it does not exist or there are multiple default ksession in kmodule.xml");
}
try {
InjectionHelper.wireListnersAndWIHs(ksessionModel, runtime.getKieSession(), parameters);
} catch (Throwable e) {
// use fallback mechanism
InjectionHelper.wireListnersAndWIHs(ksessionModel, runtime.getKieSession());
}
}
try {
for (WorkItemHandlerProducer producer : workItemHandlerProducer) {
handler.putAll(producer.getWorkItemHandlers(manager.getIdentifier(), parameters));
}
} catch (Exception e) {
// do nothing as work item handler is considered optional
logger.warn("Exception while evalutating work item handler prodcuers {}", e.getMessage());
}
// add handlers from descriptor
handler.putAll(getWorkItemHandlersFromDescriptor(runtime));
return handler;
}
@Override
public List<ProcessEventListener> getProcessEventListeners(RuntimeEngine runtime) {
List<ProcessEventListener> defaultListeners = new ArrayList<ProcessEventListener>();
if(auditlogger != null) {
defaultListeners.add(auditlogger);
} else if (getAuditBuilder() != null) {
AbstractAuditLogger aLogger = getAuditLoggerInstance(runtime);
if (aLogger != null) {
defaultListeners.add(aLogger);
}
}
try {
for (EventListenerProducer<ProcessEventListener> producer : processListenerProducer) {
defaultListeners.addAll(producer.getEventListeners(((RuntimeEngineImpl)runtime).getManager().getIdentifier(), getParametersMap(runtime)));
}
} catch (Exception e) {
logger.warn("Exception while evaluating ProcessEventListener producers {}", e.getMessage());
}
// add listeners from descriptor
defaultListeners.addAll(getEventListenerFromDescriptor(runtime, ProcessEventListener.class));
return defaultListeners;
}
@Override
public List<RuleRuntimeEventListener> getRuleRuntimeEventListeners(RuntimeEngine runtime) {
List<RuleRuntimeEventListener> defaultListeners = new ArrayList<RuleRuntimeEventListener>();
try {
for (EventListenerProducer<RuleRuntimeEventListener> producer : wmListenerProducer) {
defaultListeners.addAll(producer.getEventListeners(((RuntimeEngineImpl)runtime).getManager().getIdentifier(), getParametersMap(runtime)));
}
} catch (Exception e) {
logger.warn("Exception while evaluating WorkingMemoryEventListener producers {}", e.getMessage());
}
// add listeners from descriptor
defaultListeners.addAll(getEventListenerFromDescriptor(runtime, RuleRuntimeEventListener.class));
return defaultListeners;
}
@Override
public List<AgendaEventListener> getAgendaEventListeners(RuntimeEngine runtime) {
List<AgendaEventListener> defaultListeners = new ArrayList<AgendaEventListener>();
try {
for (EventListenerProducer<AgendaEventListener> producer : agendaListenerProducer) {
defaultListeners.addAll(producer.getEventListeners(((RuntimeEngineImpl)runtime).getManager().getIdentifier(), getParametersMap(runtime)));
}
} catch (Exception e) {
logger.warn("Exception while evaluating WorkingMemoryEventListener producers {}", e.getMessage());
}
// add listeners from descriptor
defaultListeners.addAll(getEventListenerFromDescriptor(runtime, AgendaEventListener.class));
return defaultListeners;
}
@Override
public List<TaskLifeCycleEventListener> getTaskListeners() {
List<TaskLifeCycleEventListener> defaultListeners = new ArrayList<TaskLifeCycleEventListener>();
try {
for ( EventListenerProducer<TaskLifeCycleEventListener> producer : taskListenerProducer ) {
defaultListeners.addAll( producer.getEventListeners(null, null) );
}
} catch ( Exception e ) {
logger.warn( "Cannot add listeners to task service due to {}", e.getMessage() );
}
// add listeners from descriptor
defaultListeners.addAll(getTaskListenersFromDescriptor());
return defaultListeners;
}
@Override
public Map<String, Object> getGlobals(RuntimeEngine runtime) {
Map<String, Object> globals = new HashMap<String, Object>();
RuntimeManager manager = ((RuntimeEngineImpl)runtime).getManager();
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("ksession", runtime.getKieSession());
parameters.put("taskService", runtime.getTaskService());
parameters.put("runtimeManager", manager);
parameters.put("kieContainer", getRuntimeManager().getKieContainer());
try {
parameters.put("executorService", executorService.get());
} catch (Exception e) {
logger.debug("Executor service not available due to {}", e.getMessage());
}
try {
for (GlobalProducer producer : globalProducer) {
globals.putAll(producer.getGlobals(manager.getIdentifier(), parameters));
}
} catch (Exception e) {
// do nothing as work item handler is considered optional
logger.warn("Exception while evalutating globals prodcuers {}", e.getMessage());
}
// add globals from descriptor
globals.putAll(getGlobalsFromDescriptor(runtime));
return globals;
}
/**
* Allows us to create an instance of this class dynamically via <code>BeanManager</code>. This is useful in case multiple
* independent instances are required on runtime and that need cannot be satisfied with regular CDI practices.
* @param beanManager - bean manager instance of the container
* @param auditlogger - <code>AbstractAuditLogger</code> logger instance to be used, might be null
* @return new instance of the factory
*/
public static RegisterableItemsFactory getFactory(BeanManager beanManager, AbstractAuditLogger auditlogger) {
InjectableRegisterableItemsFactory instance = getInstanceByType(beanManager, InjectableRegisterableItemsFactory.class, new Annotation[]{});
instance.setAuditlogger(auditlogger);
return instance;
}
/**
* Allows us to create instance of this class dynamically via <code>BeanManager</code>. This is useful in case multiple
* independent instances are required on runtime and that need cannot be satisfied with regular CDI practices.
* @param beanManager - bean manager instance of the container
* @param auditlogger - <code>AbstractAuditLogger</code> logger instance to be used, might be null
* @param kieContainer - <code>KieContainer</code> that the factory is built for
* @param ksessionName - name of the ksession defined in kmodule to be used,
* if not given default ksession from kmodule will be used.
* @return
*/
public static RegisterableItemsFactory getFactory(BeanManager beanManager, AbstractAuditLogger auditlogger, KieContainer kieContainer, String ksessionName) {
InjectableRegisterableItemsFactory instance = getInstanceByType(beanManager, InjectableRegisterableItemsFactory.class, new Annotation[]{});
instance.setAuditlogger(auditlogger);
instance.setKieContainer(kieContainer);
instance.setKsessionName(ksessionName);
return instance;
}
/**
* Allows to create instance of this class dynamically via <code>BeanManager</code>. This is useful in case multiple
* independent instances are required on runtime and that need cannot be satisfied with regular CDI practices.
* @param beanManager - bean manager instance of the container
* @param eventBuilder - <code>AuditEventBuilder</code> logger builder instance to be used, might be null
* @return new instance of the factory
*/
public static RegisterableItemsFactory getFactory(BeanManager beanManager, AuditEventBuilder eventBuilder) {
InjectableRegisterableItemsFactory instance = getInstanceByType(beanManager, InjectableRegisterableItemsFactory.class, new Annotation[]{});
instance.setAuditBuilder(eventBuilder);
return instance;
}
/**
* Allows to create instance of this class dynamically via <code>BeanManager</code>. This is useful in case multiple
* independent instances are required on runtime and that need cannot be satisfied with regular CDI practices.
* @param beanManager - bean manager instance of the container
* @param eventBuilder - <code>AbstractAuditLogger</code> logger builder instance to be used, might be null
* @param kieContainer - <code>KieContainer</code> that the factory is built for
* @param ksessionName - name of the ksession defined in kmodule to be used,
* if not given default ksession from kmodule will be used.
* @return
*/
public static RegisterableItemsFactory getFactory(BeanManager beanManager, AuditEventBuilder eventBuilder, KieContainer kieContainer, String ksessionName) {
InjectableRegisterableItemsFactory instance = getInstanceByType(beanManager, InjectableRegisterableItemsFactory.class, new Annotation[]{});
instance.setAuditBuilder(eventBuilder);
instance.setKieContainer(kieContainer);
instance.setKsessionName(ksessionName);
return instance;
}
protected static <T> T getInstanceByType(BeanManager manager, Class<T> type, Annotation... bindings) {
final Bean<?> bean = manager.resolve(manager.getBeans(type, bindings));
if (bean == null) {
throw new UnsatisfiedResolutionException("Unable to resolve a bean for " + type + " with bindings " + Arrays.asList(bindings));
}
CreationalContext<?> cc = manager.createCreationalContext(null);
return type.cast(manager.getReference(bean, type, cc));
}
public AbstractAuditLogger getAuditlogger() {
return auditlogger;
}
public void setAuditlogger(AbstractAuditLogger auditlogger) {
this.auditlogger = auditlogger;
}
public KieContainer getKieContainer() {
return kieContainer;
}
public void setKieContainer(KieContainer kieContainer) {
this.kieContainer = kieContainer;
}
public String getKsessionName() {
return ksessionName;
}
public void setKsessionName(String ksessionName) {
this.ksessionName = ksessionName;
}
/**
* Provides AuditLogger implementation, JPA or JMS.
* JPA is the default one and JMS requires to have configuration file (.properties)
* to be available on classpath under 'jbpm.audit.jms.properties' name.
* This file must have following properties defined:
* <ul>
* <li>jbpm.audit.jms.connection.factory.jndi - JNDI name of the connection factory to look up - type String</li>
* <li>jbpm.audit.jms.queue.jndi - JNDI name of the queue to look up - type String</li>
* </ul>
* @return instance of the audit logger
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected AbstractAuditLogger getAuditLoggerInstance(RuntimeEngine engine) {
DeploymentDescriptor descriptor = getRuntimeManager().getDeploymentDescriptor();
AbstractAuditLogger auditLogger = null;
if ("true".equals(System.getProperty("jbpm.audit.jms.enabled")) || descriptor.getAuditMode() == AuditMode.JMS) {
try {
Properties properties = new Properties();
properties.load(getRuntimeManager().getEnvironment().getClassLoader().getResourceAsStream("/jbpm.audit.jms.properties"));
auditLogger = AuditLoggerFactory.newJMSInstance((Map)properties);
} catch (IOException e) {
logger.error("Unable to load jms audit properties from {}", "/jbpm.audit.jms.properties", e);
}
auditLogger.setBuilder(getAuditBuilder(engine));
} else if (descriptor.getAuditMode() == AuditMode.JPA){
if (descriptor.getPersistenceUnit().equals(descriptor.getAuditPersistenceUnit())) {
auditLogger = AuditLoggerFactory.newJPAInstance(engine.getKieSession().getEnvironment());
} else {
auditLogger = new JPAWorkingMemoryDbLogger(EntityManagerFactoryManager.get().getOrCreate(descriptor.getAuditPersistenceUnit()));
}
auditLogger.setBuilder(getAuditBuilder(engine));
}
return auditLogger;
}
}