Created
June 20, 2014 05:00
-
-
Save Groostav/df57208770a6bd6a1c3d to your computer and use it in GitHub Desktop.
Never Inject request
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class NeverInjectRequest{ | |
/** | |
* Annotation that specifies that a constructor should <i>not</i> be used for injection, even if its paramless | |
* (and thus OK technically to use for injection). It can also serve as documentation for objects that should not be | |
* injected. | |
* | |
* This annotation is enforced by a listener optionally added by the {@link com.EmpowerOperations.Common.AbstractModule} | |
* | |
* @see AbstractModule#installNeverInjectEnforcer() | |
* | |
* Created by Geoff on 2014-06-12. | |
*/ | |
@Retention(RUNTIME) @Target(CONSTRUCTOR) | |
public @interface NeverInject { | |
public static String ErrorMessageFormat = "Attempted to inject '%s' using a constructor tagged with @NeverInject. " + | |
"Consider using a Factory " + | |
"or making that component bootstrapper exempt from the rule " + | |
"(by tagging the constructor with @NeverInject(exceptFor = YourComponentsBootstrapper.class)."; | |
/** | |
* Hack (?) on top of this framework to allow a couple of components to be injected by some modules | |
* | |
* For example, the ExternalToolBootstrapper wants to check the ExternalToolBridgeNode into guice, | |
* a component that's otherwise illegal to check in, we make an exemption. | |
*/ | |
public Class<? extends ComponentBootstrapper>[] exceptFor() default {}; | |
} | |
public class NeverInjectEnforcer implements TypeListener { | |
private final Class<? extends ComponentBootstrapper> requestingClass; | |
public NeverInjectEnforcer(Class<? extends ComponentBootstrapper> requestingClass){ | |
this.requestingClass = requestingClass; | |
} | |
public <TInjected> void hear(TypeLiteral<TInjected> typeLiteral, TypeEncounter<TInjected> encounter) { | |
Queryable<Constructor<?>> neverInjectedCtor = from(typeLiteral.getRawType().getConstructors()) | |
.where(ctor -> ctor.isAnnotationPresent(NeverInject.class)); | |
if(neverInjectedCtor.isEmpty()){ | |
return; | |
} | |
if( ! neverInjectedCtor.isSingle()){ | |
throw new RuntimeException("misuse of NeverInject: only 1 constructor can be tagged as @NeverInject"); | |
} | |
NeverInject annotation = neverInjectedCtor.single().getAnnotation(NeverInject.class); | |
if(from(annotation.exceptFor()).containsElement(requestingClass)){ | |
return; | |
} | |
String message = String.format(NeverInject.ErrorMessageFormat, typeLiteral.getRawType().getSimpleName()); | |
//my first implementation was simply | |
//encounter.addError(new Message(message)); | |
//but that's no good since "encounter" includes encounters where this class will be loaded via a | |
//bind(NeverInjectedTaggedClass).toInstance(myExplicitInstance) or an explicit provider method/object, | |
//both of which I feel should be exempt from that error. | |
//so I'm stuck. I could do something like: | |
//Provider provider = encounter.getProvider(typeLiteral.getRawType()); | |
//if(provider instanceOf ConstructorCallingProvider) context.addError(), but that provider class is guice-internal, | |
//which means either I'd have to go after it via reflection & class names, and that's about as nasty as it gets. | |
//I was hoping maybe I could register an interceptor on the constructor, | |
//but it seems that it only lets you intercept methods. | |
} | |
} | |
public class GuiceFixture { | |
public static class NotInjectable{ | |
public final boolean wasConstructedWithNeverInjectAnnotatedCtor; | |
@NeverInject | |
public NotInjectable(){ | |
wasConstructedWithNeverInjectAnnotatedCtor = true; | |
fail(":called not injectable constructor"); | |
} | |
public NotInjectable(int argToMakeDistinct){ | |
wasConstructedWithNeverInjectAnnotatedCtor = false; | |
} | |
} | |
@Test | |
public void when_guice_is_asked_to_instance_a_class_tagged_with_never_inject_it_will_refuse_to_do_so(){ | |
//setup | |
Injector injector = Guice.createInjector(new AbstractModule(OASISBootstrapper.class) { | |
@Override | |
protected void configure() { | |
installNeverInjectEnforcer(); | |
} | |
}); | |
//act | |
Exception resultingException = captureException(() -> injector.getInstance(NotInjectable.class)); | |
//assert | |
assertThat(resultingException).isInstanceOf(ConfigurationException.class); | |
assertThat(resultingException.getMessage()).contains(String.format(NeverInject.ErrorMessageFormat, NotInjectable.class.getSimpleName())); | |
} | |
@Test | |
public void when_guice_is_asked_to_resolve_an_object_tagged_with_never_inject_but_where_a_provider_is_used(){ | |
//setup | |
Injector injector = Guice.createInjector(new AbstractModule(OASISBootstrapper.class) { | |
@Override | |
protected void configure() { | |
installNeverInjectEnforcer(); | |
} | |
@Provides NotInjectable getNotInjectable(Injector injector){ | |
return new NotInjectable(0); | |
} | |
}); | |
//act | |
NotInjectable notInjectable = injector.getInstance(NotInjectable.class); | |
//assert | |
assertThat(notInjectable).isNotNull(); | |
assertThat(notInjectable.wasConstructedWithNeverInjectAnnotatedCtor).isFalse(); | |
} | |
@Test | |
public void when_guice_is_provided_an_instance_to_bind_to_it_is_accepted(){ | |
//setup | |
Injector injector = Guice.createInjector(new AbstractModule(OASISBootstrapper.class) { | |
@Override | |
protected void configure() { | |
installNeverInjectEnforcer(); | |
bind(NotInjectable.class).toInstance(new NotInjectable(0)); | |
} | |
}); | |
//act | |
NotInjectable notInjectable = injector.getInstance(NotInjectable.class); | |
//assert | |
assertThat(notInjectable).isNotNull(); | |
assertThat(notInjectable.wasConstructedWithNeverInjectAnnotatedCtor).isFalse(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
apologies for the use of
Queryable
andComponentBootstrapper
, hopefully both of those are fairly self-explanitory.