Skip to content

Instantly share code, notes, and snippets.

@Groostav
Created June 20, 2014 05:00
Show Gist options
  • Save Groostav/df57208770a6bd6a1c3d to your computer and use it in GitHub Desktop.
Save Groostav/df57208770a6bd6a1c3d to your computer and use it in GitHub Desktop.
Never Inject request
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();
}
}
}
@Groostav
Copy link
Author

apologies for the use of Queryable and ComponentBootstrapper, hopefully both of those are fairly self-explanitory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment