Injecting Objects Into a Node Instance
A node instance is constructed every time that node is reached in a tree, and is discarded as soon as it has been used to process the state once.
This model is different to authentication modules, which are instantiated once for each end-user authentication process, and then all authentication interactions for the life of the authentication process address the same instance in the same JVM.
Modules can store state in the module instance. However, state stored in a node will be lost when the node's process method is complete. To make state available for other nodes in the tree, nodes must either return the state to the user or store it in the shared state.
AM uses Google's Guice dependency injection framework for authentication nodes. AM uses Guice to manage most of its object life-cycles. You can use just-in-time bindings from the constructor to inject an object from Guice.
The following node-specific instances are available from Guice:
- @Assisted Realm
The realm that the node is in.
- @Assisted UUID
The unique ID of the node instance.
- @Assisted AuthTree
The tree that this node is being processed as part of.
- <T> @Assisted T
The configuration object that is an instance of the interface specified in the
configClass
metadata parameter.
Tip
Any other objects in AM that are managed by Guice can also be obtained from within the constructor.
The following example is the injection used by the Account lockout Node:
@Inject public AccountLockoutNode(CoreWrapper coreWrapper, @Assisted Config config) throws NodeProcessException { this.coreWrapper = coreWrapper; this.config = config; }
For more information, see the Inject annotation type and the AssistedInject annotation type in the Google Guice Javadoc.
Using a Cache
You can use Guice injection to cache information in a node by annotating the object that contains the cache with the @Singleton
annotation, for example:
@Node.Metadata( outcomeProvider = SingleOutcomeNode.OutcomeProvider.class, configClass = MyCustomNode.Config.class) public class MyCustomNode extends SingleOutcomeNode { public interface Config { String url(); } private final Config config; private final MyCustomNodeCache cache; @Inject public MyCustomNode(@Assisted Config config, MyCustomNodeCache cache) { this.config = config; this.cache = cache; } @Override public Action process(TreeContext context) { CachedThing thing = cache.getThing(config.url()); // implement node logic here } } @Singleton class MyCustomNodeCache { private final LoadingCache<String, CachedThing> cache = CacheBuilder.newBuilder() .build(CacheLoader.from(url -> read(url))); public CachedThing get(String url) { return cache.get(url); } private CachedThing read(String url) { // Access resource and construct } }
Custom Guice Bindings
If just-in-time bindings are not sufficient for your use case, you can add your own Guice module into the injector configuration by implementing your own com.google.inject.Module
and registering it using the service loader mechanism. For example:
com/example/MyCustomModule.java
public class MyCustomModule extends AbstractModule { @Override protected void configure() { bind(Thing.class).to(MyThing.class); // and so on } }
META-INF/services/com.google.inject.Module
# See https://docs.oracle.com/javase/tutorial/ext/basics/spi.html com.example.MyCustomModule
The MyCustomModule
object will then be automatically configured as part of the injector creation.