/* * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Thomas Roger * Florent Guillaume */ package org.nuxeo.ecm.core.event.test; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.nuxeo.ecm.core.event.Event; import org.nuxeo.ecm.core.event.EventService; import org.nuxeo.ecm.core.event.EventServiceAdmin; import org.nuxeo.ecm.core.event.impl.EventContextImpl; import org.nuxeo.ecm.core.event.impl.EventImpl; import org.nuxeo.ecm.core.event.impl.EventListenerDescriptor; import org.nuxeo.ecm.core.event.impl.EventServiceImpl; import org.nuxeo.ecm.core.event.impl.PostCommitEventExecutor; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.test.NXRuntimeTestCase; import java.net.URL; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class TestEventServiceComponent extends NXRuntimeTestCase { protected int initialThreadCount; @Override @Before public void setUp() throws Exception { super.setUp(); Framework.getProperties().setProperty(PostCommitEventExecutor.TIMEOUT_MS_PROP, "300"); // 0.3s deployBundle("org.nuxeo.runtime.jtajca"); deployBundle("org.nuxeo.ecm.core.event"); fireFrameworkStarted(); // 2 quartz threads launched by the event contribs above Thread.sleep(100); initialThreadCount = Thread.activeCount(); DummyPostCommitEventListener.handledCountReset(); DummyPostCommitEventListener.eventCountReset(); } @Override @After public void tearDown() throws Exception { Event commit = new EventImpl("commit", new EventContextImpl()); commit.setIsCommitEvent(true); EventService service = Framework.getService(EventService.class); service.fireEvent(commit); service.waitForAsyncCompletion(); super.tearDown(); } @Test public void testDisablingListener() throws Exception { URL url = EventListenerTest.class.getClassLoader().getResource("test-disabling-listeners1.xml"); deployTestContrib("org.nuxeo.ecm.core.event", url); EventService service = Framework.getService(EventService.class); EventServiceImpl serviceImpl = (EventServiceImpl) service; List<EventListenerDescriptor> eventListenerDescriptors = serviceImpl.getEventListenerList().getSyncPostCommitListenersDescriptors(); assertEquals(1, eventListenerDescriptors.size()); EventListenerDescriptor eventListenerDescriptor = eventListenerDescriptors.get(0); assertTrue(eventListenerDescriptor.isEnabled()); url = EventListenerTest.class.getClassLoader().getResource("test-disabling-listeners2.xml"); deployTestContrib("org.nuxeo.ecm.core.event", url); eventListenerDescriptors = serviceImpl.getEventListenerList().getSyncPostCommitListenersDescriptors(); assertEquals(1, eventListenerDescriptors.size()); eventListenerDescriptor = eventListenerDescriptors.get(0); assertFalse(eventListenerDescriptor.isEnabled()); } @Test public void testAsync() throws Exception { URL url = getClass().getClassLoader().getResource("test-async-listeners.xml"); deployTestContrib("org.nuxeo.ecm.core.event", url); EventService service = Framework.getService(EventService.class); // send two events, only one of which is recognized by the listener // (the other is filtered out of the bundle passed to this listener) Event test1 = new EventImpl("testasync", new EventContextImpl()); service.fireEvent(test1); assertEquals(DummyPostCommitEventListener.handledCount(), 0); Event test2 = new EventImpl("testnotmached", new EventContextImpl()); test2.setIsCommitEvent(true); service.fireEvent(test2); service.waitForAsyncCompletion(); Thread.sleep(100); // TODO async completion has race conditions assertEquals(1, DummyPostCommitEventListener.handledCount()); assertEquals(1, DummyPostCommitEventListener.eventCount()); // check new information from sync event are retrieved in postcommit // listener assertEquals("bar", DummyPostCommitEventListener.properties.get("foo")); } @Test public void testAsyncRetry() throws Exception { URL url = getClass().getClassLoader().getResource("test-async-listeners.xml"); deployTestContrib("org.nuxeo.ecm.core.event", url); EventService service = Framework.getLocalService(EventService.class); // send two events, only one of which is recognized by the listener // (the other is filtered out of the bundle passed to this listener) EventContextImpl context = new EventContextImpl(); context.setProperty("concurrentexception", Boolean.TRUE); Event test1 = new EventImpl("testasync", context); test1.setIsCommitEvent(true); service.fireEvent(test1); service.waitForAsyncCompletion(); assertEquals(2, DummyPostCommitEventListener.handledCount()); } @Test public void testSyncPostCommit() throws Exception { doTestSyncPostCommit(false, false, false, 2, 4); } @Test public void testSyncPostCommitError() throws Exception { // second handler done even though there's an error doTestSyncPostCommit(false, true, false, 2, 4); } @Test public void testSyncPostCommitTimeout() throws Exception { // returned after timeout (300ms), so only one listener done at this time doTestSyncPostCommit(false, false, true, 1, 2); Thread.sleep(3000); // wait other listener assertEquals(2, DummyPostCommitEventListener.handledCount()); assertEquals(4, DummyPostCommitEventListener.eventCount()); } @Test public void testSyncPostCommitBulk() throws Exception { doTestSyncPostCommit(true, false, false, 2, 4); } @Test public void testSyncPostCommitErrorBulk() throws Exception { // second handler not done because error in first one (bulk) doTestSyncPostCommit(true, true, false, 1, 2); } @Test public void testSyncPostCommitTimeoutBulk() throws Exception { // returned after timeout (300ms), so only one listener done doTestSyncPostCommit(true, false, true, 1, 2); Thread.sleep(3000); // wait other listener // other listener was never run due to interrupt assertEquals(1, DummyPostCommitEventListener.handledCount()); assertEquals(2, DummyPostCommitEventListener.eventCount()); } protected void doTestSyncPostCommit(boolean bulk, boolean error, boolean timeout, int expectedHandled, int expectedEvents) throws Exception { EventServiceAdmin eventServiceAdmin = Framework.getLocalService(EventServiceAdmin.class); try { eventServiceAdmin.setBulkModeEnabled(bulk); if (timeout) { Framework.getProperties().setProperty(PostCommitEventExecutor.BULK_TIMEOUT_PROP, "1"); // 1s } doTestSyncPostCommit(error, timeout, expectedHandled, expectedEvents); } finally { eventServiceAdmin.setBulkModeEnabled(false); Framework.getProperties().remove(PostCommitEventExecutor.BULK_TIMEOUT_PROP); } } protected void doTestSyncPostCommit(boolean error, boolean timeout, int expectedHandled, int expectedEvents) throws Exception { URL url = getClass().getClassLoader().getResource("test-sync-postcommit-listeners.xml"); deployTestContrib("org.nuxeo.ecm.core.event", url); EventService service = Framework.getService(EventService.class); // Send 4 events, only 2 of which are recognized by the listeners // (the last one is filtered out of the bundle passed to the listeners // and also servers as a trigger for the event bundle execution). Event event1 = new EventImpl("testnotmached", new EventContextImpl()); service.fireEvent(event1); EventContextImpl context = new EventContextImpl(); if (error) { // Provoke an error in the first listener, to check whether the // second one is done or not (depending on bulk mode) context.setProperty("throw", Boolean.TRUE); } if (timeout) { // Provoke a sleep that makes the work time out context.setProperty("sleep", Boolean.TRUE); } Event event2 = new EventImpl("testsyncpostcommit", context); service.fireEvent(event2); Event event3 = new EventImpl("testsyncpostcommit", new EventContextImpl()); service.fireEvent(event3); Event event4 = new EventImpl("testnotmached", new EventContextImpl()); event4.setIsCommitEvent(true); assertEquals(0, DummyPostCommitEventListener.handledCount()); service.fireEvent(event4); service.waitForAsyncCompletion(); // Thread.sleep(3000); assertEquals(expectedHandled, DummyPostCommitEventListener.handledCount()); assertEquals(expectedEvents, DummyPostCommitEventListener.eventCount()); if (timeout) { // wait for listeners to finish Thread.sleep(2000); } } /** * Test that when the event service component is deactivated, the threads of the async event executor are shut down. */ @Test @Ignore public void testAsyncEventExecutorShutdown() throws Exception { // send an async event to make sure the async event executor spawned // some threads // load contrib URL url = getClass().getClassLoader().getResource("test-PostCommitListeners3.xml"); deployTestContrib("org.nuxeo.ecm.core.event", url); // send event EventService service = Framework.getService(EventService.class); Event test1 = new EventImpl("test1", new EventContextImpl()); test1.setIsCommitEvent(true); service.fireEvent(test1); // wait for async processing to be done service.waitForAsyncCompletion(2 * 1000); assertEquals(1, DummyPostCommitEventListener.handledCount()); // can still fire events Event test2 = new EventImpl("test1", new EventContextImpl()); test2.setIsCommitEvent(true); service.fireEvent(test2); // now stop service // this is called by EventServiceComponent.deactivate() in real life ((EventServiceImpl) service).shutdown(2 * 1000); ((EventServiceImpl) service).init(); assertEquals(2, DummyPostCommitEventListener.handledCount()); Thread.sleep(2 * 1000); assertEquals("Threads not dead", 0, Thread.activeCount() - initialThreadCount); } }