package org.camunda.bpm.extension.osgi.itest.eventing;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Hashtable;
import javax.inject.Inject;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.ProcessEngineConfiguration;
import org.camunda.bpm.engine.impl.bpmn.parser.BpmnParseListener;
import org.camunda.bpm.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration;
import org.camunda.bpm.engine.repository.DeploymentBuilder;
import org.camunda.bpm.extension.osgi.el.OSGiExpressionManager;
import org.camunda.bpm.extension.osgi.engine.ProcessEngineFactoryWithELResolver;
import org.camunda.bpm.extension.osgi.eventing.api.OSGiEventBridgeActivator;
import org.camunda.bpm.extension.osgi.eventing.api.Topics;
import org.camunda.bpm.extension.osgi.itest.OSGiTestEnvironment;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.OptionUtils;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerMethod;
import org.ops4j.pax.exam.util.Filter;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;
import org.osgi.service.log.LogReaderService;
/**
* @author Ronny Bräunlich
*/
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerMethod.class)
public class OSGiEventBridgeIntegrationTest extends OSGiTestEnvironment {
public static final String BUNDLE_SYMBOLIC_NAME = "org.camunda.bpm.extension.osgi.eventing";
@Inject
private BundleContext bundleContext;
@Inject
private LogReaderService logReaderService;
@Inject
@Filter(timeout = 30000L)
private OSGiEventBridgeActivator eventBridgeActivator;
private ErrorLogListener logListener;
@Override
@Configuration
public Option[] createConfiguration() {
Option[] eventing = options(
mavenBundle("org.apache.felix", "org.apache.felix.eventadmin").versionAsInProject(),
mavenBundle("org.apache.felix", "org.apache.felix.dependencymanager").versionAsInProject(),
mavenBundle("org.apache.felix", "org.apache.felix.log").versionAsInProject(),
mavenBundle("org.camunda.bpm.extension.osgi", "camunda-bpm-osgi-eventing-api").versionAsInProject(),
mavenBundle("org.camunda.bpm.extension.osgi", "camunda-bpm-osgi-eventing").versionAsInProject()
);
return OptionUtils.combine(eventing, super.createConfiguration());
}
@Before
public void setUp() {
logListener = createErrorLogListener();
}
@Test
public void shouldRegisterService() {
assertThat(eventBridgeActivator, is(notNullValue()));
}
@Test
public void testEventBrigde() throws FileNotFoundException {
TestEventHandler eventHandler = new TestEventHandler();
registerEventHandler(eventHandler);
ProcessEngine processEngine = createProcessEngine();
deployProcess(processEngine, "testProcess", "src/test/resources/testprocess.bpmn");
processEngine.getRuntimeService().startProcessInstanceByKey("Process_1");
processEngine.close();
checkLogListener();
assertThat(eventHandler.isCalled(), is(true));
}
/**
* We don't want to receive any more events after shutting the bundle down.
*/
@Test
public void shutdownDuringRunningProcess() throws Exception {
TestEventHandler eventHandler = new TestEventHandler();
registerEventHandler(eventHandler);
final ProcessEngine processEngine = createProcessEngine();
deployProcess(processEngine, "longRunningTestProcess", "src/test/resources/eventing/longRunningTestProcess.bpmn");
stopEventingBundle();
processEngine.getRuntimeService().startProcessInstanceByKey("slowProcess");
processEngine.close();
checkLogListener();
assertThat(eventHandler.endCalled(), is(false));
}
/**
* If we stop the eventing bundle and restart it afterwards we want to receive
* events again.
*
* @throws FileNotFoundException
* @throws BundleException
* @throws InterruptedException
*/
@Test
public void restartEventingBundleAfterShutdown() throws FileNotFoundException, BundleException, InterruptedException {
final ProcessEngine processEngine = createProcessEngine();
deployProcess(processEngine, "longRunningTestProcess", "src/test/resources/eventing/longRunningTestProcess.bpmn");
stopEventingBundle();
processEngine.getRuntimeService().startProcessInstanceByKey("slowProcess");
TestEventHandler eventHandler = new TestEventHandler();
registerEventHandler(eventHandler);
startEventingBundle();
processEngine.getRuntimeService().startProcessInstanceByKey("slowProcess");
processEngine.close();
checkLogListener();
assertThat(eventHandler.endCalled(), is(true));
}
private void startEventingBundle() throws BundleException {
for (Bundle bundle : bundleContext.getBundles()) {
if (bundle.getSymbolicName().equals(BUNDLE_SYMBOLIC_NAME)) {
bundle.start();
break;
}
}
}
private ProcessEngine createProcessEngine() {
StandaloneInMemProcessEngineConfiguration configuration = new StandaloneInMemProcessEngineConfiguration();
configuration.setCustomPreBPMNParseListeners(Collections.<BpmnParseListener> singletonList(eventBridgeActivator));
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
ProcessEngineFactoryWithELResolver engineFactory = new ProcessEngineFactoryWithELResolver();
engineFactory.setProcessEngineConfiguration(configuration);
engineFactory.setBundle(bundleContext.getBundle());
engineFactory.setExpressionManager(new OSGiExpressionManager());
engineFactory.init();
return engineFactory.getObject();
}
private void registerEventHandler(TestEventHandler eventHandler) {
Dictionary<String, String> props = new Hashtable<String, String>();
props.put(EventConstants.EVENT_TOPIC, Topics.ALL_EVENTING_EVENTS_TOPIC);
bundleContext.registerService(EventHandler.class.getName(), eventHandler, props);
}
private void checkLogListener() {
if (logListener.getErrorMessage() != null) {
fail(logListener.getErrorMessage());
}
}
/**
* we have to use a LogListener to find Errors in the log
*/
private ErrorLogListener createErrorLogListener() {
ErrorLogListener logListener = new ErrorLogListener();
logReaderService.addLogListener(logListener);
return logListener;
}
private void deployProcess(ProcessEngine processEngine, String processName, String fileLocation) throws FileNotFoundException {
DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();
deploymentBuilder.name(processName).addInputStream(processName + ".bpmn", new FileInputStream(new File(fileLocation))).deploy();
}
private void stopEventingBundle() throws BundleException {
for (Bundle bundle : bundleContext.getBundles()) {
if (bundle.getSymbolicName().equals(BUNDLE_SYMBOLIC_NAME)) {
bundle.stop();
break;
}
}
}
/**
* If an exception happens during event distribution the EventAdmin will log
* this with OSGi logging if present, so we need a listener to make sure no
* exceptions were thrown.
*/
private static class ErrorLogListener implements LogListener {
private String errorMessage;
@Override
public void logged(LogEntry entry) {
if (entry.getMessage().contains("EventAdmin") && entry.getException() != null) {
this.errorMessage = entry.getMessage();
entry.getException().printStackTrace();
}
}
public String getErrorMessage() {
return errorMessage;
}
}
;
}