Skip to content

Commit d1117b4

Browse files
authored
Merge pull request #459 from scijava/module-exception-event
Add ModuleErroredEvent for callbacks on exceptions thrown by Modules
2 parents 74518c5 + d8b0318 commit d1117b4

File tree

4 files changed

+216
-29
lines changed

4 files changed

+216
-29
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</parent>
1111

1212
<artifactId>scijava-common</artifactId>
13-
<version>2.92.1-SNAPSHOT</version>
13+
<version>2.93.0-SNAPSHOT</version>
1414

1515
<name>SciJava Common</name>
1616
<description>SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO.</description>

src/main/java/org/scijava/module/ModuleRunner.java

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.scijava.event.EventService;
4040
import org.scijava.log.LogService;
4141
import org.scijava.module.event.ModuleCanceledEvent;
42+
import org.scijava.module.event.ModuleErroredEvent;
4243
import org.scijava.module.event.ModuleExecutedEvent;
4344
import org.scijava.module.event.ModuleExecutingEvent;
4445
import org.scijava.module.event.ModuleFinishedEvent;
@@ -144,36 +145,42 @@ public void run() {
144145

145146
final String title = module.getInfo().getTitle();
146147

147-
// announce start of execution process
148-
if (ss != null) ss.showStatus("Running command: " + title);
149-
if (es != null) es.publish(new ModuleStartedEvent(module));
150-
151-
// execute preprocessors
152-
final ModulePreprocessor canceler = preProcess();
153-
if (canceler != null) {
154-
// module execution was canceled by preprocessor
155-
final String reason = canceler.getCancelReason();
156-
cancel(reason);
157-
cleanupAndBroadcastCancelation(title, reason);
158-
return;
148+
try {
149+
// announce start of execution process
150+
if (ss != null) ss.showStatus("Running command: " + title);
151+
if (es != null) es.publish(new ModuleStartedEvent(module));
152+
153+
// execute preprocessors
154+
final ModulePreprocessor canceler = preProcess();
155+
if (canceler != null) {
156+
// module execution was canceled by preprocessor
157+
final String reason = canceler.getCancelReason();
158+
cancel(reason);
159+
cleanupAndBroadcastCancelation(title, reason);
160+
return;
161+
}
162+
163+
// execute module
164+
if (es != null) es.publish(new ModuleExecutingEvent(module));
165+
module.run();
166+
if (isCanceled()) {
167+
// module execution was canceled by the module itself
168+
cleanupAndBroadcastCancelation(title, getCancelReason());
169+
return;
170+
}
171+
if (es != null) es.publish(new ModuleExecutedEvent(module));
172+
173+
// execute postprocessors
174+
postProcess();
175+
176+
// announce completion of execution process
177+
if (es != null) es.publish(new ModuleFinishedEvent(module));
178+
if (ss != null) ss.showStatus("Command finished: " + title);
159179
}
160-
161-
// execute module
162-
if (es != null) es.publish(new ModuleExecutingEvent(module));
163-
module.run();
164-
if (isCanceled()) {
165-
// module execution was canceled by the module itself
166-
cleanupAndBroadcastCancelation(title, getCancelReason());
167-
return;
180+
catch (final Throwable t) {
181+
cleanupAndBroadcastException(title, t);
182+
throw t;
168183
}
169-
if (es != null) es.publish(new ModuleExecutedEvent(module));
170-
171-
// execute postprocessors
172-
postProcess();
173-
174-
// announce completion of execution process
175-
if (es != null) es.publish(new ModuleFinishedEvent(module));
176-
if (ss != null) ss.showStatus("Command finished: " + title);
177184
}
178185

179186
// -- Helper methods --
@@ -190,6 +197,16 @@ private void cleanupAndBroadcastCancelation(final String title,
190197
}
191198
}
192199

200+
private void cleanupAndBroadcastException(final String title,
201+
final Throwable t)
202+
{
203+
if (es != null) es.publish(new ModuleErroredEvent(module, t));
204+
if (ss != null) {
205+
ss.showStatus("Module errored: " + title);
206+
if (t != null) ss.warn(t.getMessage());
207+
}
208+
}
209+
193210
private boolean isCanceled() {
194211
return module instanceof Cancelable && ((Cancelable) module).isCanceled();
195212
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2023 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.module.event;
31+
32+
import org.scijava.module.Module;
33+
34+
/**
35+
* An event indicating a module execution has thrown an exception.
36+
*
37+
* @author Gabriel Selzer
38+
*/
39+
public class ModuleErroredEvent extends ModuleExecutionEvent {
40+
41+
private final Throwable exc;
42+
43+
public ModuleErroredEvent(final Module module, final Throwable exc) {
44+
super(module);
45+
this.exc = exc;
46+
}
47+
48+
public Throwable getException() {
49+
return exc;
50+
}
51+
52+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*-
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2023 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.module.event;
31+
32+
import static org.junit.Assert.assertEquals;
33+
import static org.junit.Assert.assertThrows;
34+
import static org.junit.Assert.assertNotNull;
35+
36+
import org.junit.Before;
37+
import org.junit.Test;
38+
import org.scijava.Context;
39+
import org.scijava.event.EventHandler;
40+
import org.scijava.event.EventService;
41+
import org.scijava.module.AbstractModule;
42+
import org.scijava.module.AbstractModuleInfo;
43+
import org.scijava.module.Module;
44+
import org.scijava.module.ModuleException;
45+
import org.scijava.module.ModuleInfo;
46+
import org.scijava.module.ModuleService;
47+
48+
/**
49+
* Tests {@link ModuleErroredEvent} behavior.
50+
*
51+
* @author Gabriel Selzer
52+
* @author Curtis Rueden
53+
*/
54+
public class ModuleErroredEventTest {
55+
56+
private EventService es;
57+
private ModuleService module;
58+
59+
@Before
60+
public void setUp() {
61+
Context ctx = new Context();
62+
es = ctx.getService(EventService.class);
63+
module = ctx.getService(ModuleService.class);
64+
}
65+
66+
@Test
67+
public void testModuleErroredEvent() {
68+
69+
// Must be a final boolean array to be included in the below closure
70+
final Throwable[] caughtException = { null };
71+
72+
// Add a new EventHandler to change our state
73+
final Object interestedParty = new Object() {
74+
75+
@EventHandler
76+
void onEvent(final ModuleErroredEvent e) {
77+
caughtException[0] = e.getException();
78+
}
79+
};
80+
es.subscribe(interestedParty);
81+
82+
// Run the module, ensure we get the exception
83+
assertThrows(Exception.class, //
84+
() -> module.run(new TestModuleInfo(), false).get());
85+
assertNotNull(caughtException[0]);
86+
assertEquals("Yay!", caughtException[0].getMessage());
87+
}
88+
89+
static class TestModuleInfo extends AbstractModuleInfo {
90+
91+
@Override
92+
public String getDelegateClassName() {
93+
return this.getClass().getName();
94+
}
95+
96+
@Override
97+
public Class<?> loadDelegateClass() throws ClassNotFoundException {
98+
return this.getClass();
99+
}
100+
101+
@Override
102+
public Module createModule() throws ModuleException {
103+
ModuleInfo thisInfo = this;
104+
return new AbstractModule() {
105+
106+
@Override
107+
public ModuleInfo getInfo() {
108+
return thisInfo;
109+
}
110+
111+
@Override
112+
public void run() {
113+
throw new RuntimeException("Yay!");
114+
}
115+
};
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)