Skip to content

Commit 33f934a

Browse files
committed
Merge branch 'non-strict-injection'
This branch makes the SciJava application framework try harder to keep going when something goes wrong during context injection while in non-strict mode. The idea is that it might help applications succeed in starting up when they otherwise would have failed with an exception. In practice, it is still a crapshoot, since the normal assumptions about which fields have been successfully injected no longer hold while in non-strict mode; consequently, subsequent NPEs are much more likely.
2 parents a9070f0 + c9830fb commit 33f934a

File tree

1 file changed

+103
-37
lines changed

1 file changed

+103
-37
lines changed

src/main/java/org/scijava/Context.java

Lines changed: 103 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.scijava.event.ContextDisposingEvent;
4343
import org.scijava.event.EventHandler;
4444
import org.scijava.event.EventService;
45+
import org.scijava.log.LogService;
4546
import org.scijava.plugin.Parameter;
4647
import org.scijava.plugin.PluginIndex;
4748
import org.scijava.service.Service;
@@ -62,10 +63,11 @@ public class Context implements Disposable {
6263

6364
/**
6465
* System property indicating whether the context should fail fast when
65-
* is attempts to instantiate a required service which is invalid or missing.
66+
* attempting to instantiate a required service which is invalid or missing.
6667
* If this property is set to "false" then the context creation will attempt
6768
* to continue even when a required service cannot be instantiated. Otherwise,
68-
* the constructor will throw an {@link IllegalArgumentException} in that situation.
69+
* the constructor will throw an {@link IllegalArgumentException} in that
70+
* situation.
6971
*/
7072
public static final String STRICT_PROPERTY = "scijava.context.strict";
7173

@@ -77,6 +79,23 @@ public class Context implements Disposable {
7779
/** Master index of all plugins known to the application context. */
7880
private final PluginIndex pluginIndex;
7981

82+
/**
83+
* Whether context creation and injection should behave strictly, failing fast
84+
* when attempting to instantiate a required service which is invalid or
85+
* missing.
86+
* <ul>
87+
* <li>If the flag is false, then the context creation will attempt to
88+
* continue even when a required service cannot be instantiated. Otherwise,
89+
* the constructor will throw an {@link IllegalArgumentException} in that
90+
* situation.</li>
91+
* <li>If this flag is false, then a call to {@link Context#inject(Object)}
92+
* will attempt to catch any errors that occur during context injection
93+
* (notably: {@link NoClassDefFoundError} when scanning for event handler
94+
* methods), logging them as errors.</li>
95+
* </ul>
96+
*/
97+
private boolean strict;
98+
8099
/**
81100
* Creates a new SciJava application context with all available services.
82101
*
@@ -239,6 +258,8 @@ public Context(final Collection<Class<? extends Service>> serviceClasses,
239258
this.pluginIndex = pluginIndex == null ? new PluginIndex() : pluginIndex;
240259
this.pluginIndex.discover();
241260

261+
setStrict(strict);
262+
242263
final ServiceHelper serviceHelper =
243264
new ServiceHelper(this, serviceClasses, strict);
244265
serviceHelper.loadServices();
@@ -254,6 +275,14 @@ public PluginIndex getPluginIndex() {
254275
return pluginIndex;
255276
}
256277

278+
public boolean isStrict() {
279+
return strict;
280+
}
281+
282+
public void setStrict(final boolean strict) {
283+
this.strict = strict;
284+
}
285+
257286
/**
258287
* Gets the service of the given class.
259288
*
@@ -329,46 +358,14 @@ public Service getService(final String className) {
329358
*/
330359
public void inject(final Object o) {
331360
// iterate over all @Parameter annotated fields
332-
final List<Field> fields =
333-
ClassUtils.getAnnotatedFields(o.getClass(), Parameter.class);
361+
final List<Field> fields = getParameterFields(o);
334362
for (final Field f : fields) {
335-
f.setAccessible(true); // expose private fields
336-
337-
final Class<?> type = f.getType();
338-
if (Service.class.isAssignableFrom(type)) {
339-
final Service existingService = (Service) ClassUtils.getValue(f, o);
340-
if (existingService != null) {
341-
throw new IllegalStateException("Context already injected: " +
342-
f.getDeclaringClass().getName() + "#" + f.getName());
343-
}
344-
345-
// populate Service parameter
346-
@SuppressWarnings("unchecked")
347-
final Class<? extends Service> serviceType =
348-
(Class<? extends Service>) type;
349-
final Service service = getService(serviceType);
350-
if (service == null && f.getAnnotation(Parameter.class).required()) {
351-
throw new IllegalArgumentException(
352-
createMissingServiceMessage(serviceType));
353-
}
354-
ClassUtils.setValue(f, o, service);
355-
}
356-
else if (Context.class.isAssignableFrom(type) && type.isInstance(this)) {
357-
final Context existingContext = (Context) ClassUtils.getValue(f, o);
358-
if (existingContext != null) {
359-
throw new IllegalStateException("Context already injected: " +
360-
f.getDeclaringClass().getName() + "#" + f.getName());
361-
}
362-
363-
// populate Context parameter
364-
ClassUtils.setValue(f, o, this);
365-
}
363+
inject(f, o);
366364
}
367365

368366
// NB: Subscribe to all events handled by this object.
369367
// This greatly simplifies event handling.
370-
final EventService eventService = getService(EventService.class);
371-
if (eventService != null) eventService.subscribe(o);
368+
subscribeToEvents(o);
372369
}
373370

374371
// -- Disposable methods --
@@ -403,6 +400,75 @@ public static List<Class<? extends Service>> serviceClassList(
403400

404401
// -- Helper methods --
405402

403+
private List<Field> getParameterFields(Object o) {
404+
try {
405+
return ClassUtils.getAnnotatedFields(o.getClass(), Parameter.class);
406+
}
407+
catch (final Throwable t) {
408+
handleSafely(t);
409+
}
410+
return Collections.emptyList();
411+
}
412+
413+
private void inject(final Field f, final Object o) {
414+
try {
415+
f.setAccessible(true); // expose private fields
416+
417+
final Class<?> type = f.getType();
418+
if (Service.class.isAssignableFrom(type)) {
419+
final Service existingService = (Service) ClassUtils.getValue(f, o);
420+
if (existingService != null) {
421+
throw new IllegalStateException("Context already injected: " +
422+
f.getDeclaringClass().getName() + "#" + f.getName());
423+
}
424+
425+
// populate Service parameter
426+
@SuppressWarnings("unchecked")
427+
final Class<? extends Service> serviceType =
428+
(Class<? extends Service>) type;
429+
final Service service = getService(serviceType);
430+
if (service == null && f.getAnnotation(Parameter.class).required()) {
431+
throw new IllegalArgumentException(
432+
createMissingServiceMessage(serviceType));
433+
}
434+
ClassUtils.setValue(f, o, service);
435+
}
436+
else if (Context.class.isAssignableFrom(type) && type.isInstance(this)) {
437+
final Context existingContext = (Context) ClassUtils.getValue(f, o);
438+
if (existingContext != null) {
439+
throw new IllegalStateException("Context already injected: " +
440+
f.getDeclaringClass().getName() + "#" + f.getName());
441+
}
442+
443+
// populate Context parameter
444+
ClassUtils.setValue(f, o, this);
445+
}
446+
}
447+
catch (final Throwable t) {
448+
handleSafely(t);
449+
}
450+
}
451+
452+
private void subscribeToEvents(final Object o) {
453+
try {
454+
final EventService eventService = getService(EventService.class);
455+
if (eventService != null) eventService.subscribe(o);
456+
}
457+
catch (final Throwable t) {
458+
handleSafely(t);
459+
}
460+
}
461+
462+
private void handleSafely(final Throwable t) {
463+
if (isStrict()) {
464+
// NB: Only rethrow unchecked exceptions.
465+
if (t instanceof RuntimeException) throw (RuntimeException) t;
466+
if (t instanceof Error) throw (Error) t;
467+
}
468+
final LogService log = getService(LogService.class);
469+
if (log != null) log.error(t);
470+
}
471+
406472
private String createMissingServiceMessage(
407473
final Class<? extends Service> serviceType)
408474
{

0 commit comments

Comments
 (0)