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; } } ; }