Inject objects into a node instance
A node instance is constructed every time the node is reached in a tree and is discarded as soon as it’s been used to process the state once.
State stored in a node is lost when the node’s process method completes. To make state available for other nodes in the tree, nodes must return the state to the user or store it in the shared state.
AM uses Google’s Guice dependency injection framework for authentication nodes and uses Guice to manage most of its object lifecycles. 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 TreeMetadata
-
The metadata for the tree that the node belongs to.
- <T> @Assisted T
-
The configuration object that is an instance of the interface specified in the
configClass
metadata parameter.
Any other objects in AM that are managed by Guice can also be obtained from within the constructor. |
The following example is the configuration injection used by the Debug node:
@Inject
public DebugNode(@Assisted DebugNode.Config config) {
this.config = config;
...
}
Use 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.
The Guice injected singleton will be used by multiple threads, so it must be thread safe. The following example uses
the Google LoadingCache , which is thread safe.
Alternatively, use java.util.concurrent.ConcurrentMap if you prefer to use a built-in Java class.
|
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
}
}
Send an HTTP request
You can use Guice injection to send an HTTP request by injecting the CloseableHttpClientHandler
into the
node instance. This means the node uses the standard AM HTTP client handler, and all the httpClient
settings and tuning apply.
The following example demonstrates sending an HTTP POST request to the https://www.example.com/api
endpoint:
@Inject
public MyCustomNode(@Named("CloseableHttpClientHandler") Handler httpClientHandler) {
this.httpClientHandler = httpClientHandler;
}
@Override
public Action process(TreeContext context) {
URI uri = URI.create("https://www.example.com/api");
Request request = new Request()
.setUri(uri)
.setMethod(HttpConstants.Methods.POST);
JsonValue body = json(object(field("sampleKey", "sampleValue")));
request.getEntity().setJson(body);
Response response = httpClientHandler.handle(new RootContext(), request).getOrThrow();
}
Custom Guice bindings
If just-in-time bindings aren’t 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
// Learn more in 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.