Ninja can inject a lot of stuff into your methods by default. For instance
variable parts of routes via @PathParam
.
Or query / form parameters via @Param
and @Params
.
Argument extractors allow you to use the very same mechanism (annotations of method parameters) to inject inject arbitrary things into the method of a controller.
This allows you to process a request, extract things (like a user currently logged in), and provide this via an annotation.
NOTES:
Enums do not require anymore a registration in conf.Module. Values are converted automatically, ignoring case. Collections and generics are not supported for parameter injection. Types must be explicitly declared and basic arrays must be used to ensure runtime type-safety.
By default, empty String parameters will be treated as null values. If you
explicitely need empty strings for empty given parameters, register the
ParamParsers.EmptyStringParamParser
in your conf.Module configure()
method:
Multibinder<ParamParser> parsersBinder = Multibinder.newSetBinder(binder(), ParamParser.class); parsersBinder.addBinding().to(ParamParsers.EmptyStringParamParser.class);
You can also add your own supported type to Ninja, or override and existing one,
simply by implementing the ParamParser
interface and binding it
with Guice in your conf.Module configure()
method:
Multibinder<ParamParser> parsersBinder = Multibinder.newSetBinder(binder(), ParamParser.class); parsersBinder.addBinding().to(MyParamParser.class);
Let's write an argument extractor that lets us inject the user currently logged in into our method via an annotation. At the end our controller should look like:
package controllers; @Singleton public class ApplicationController { public Result index( @LoggedInUser String loggedInUser) { //do something with the parameters... } }
The @LoggedInUser
is a so called argument extractor
and allow you to extract arbitrary things out
of the request and re-package them into anything you want.
@LoggedInUser
for instance determines
the user and injects the username into the field annotated with @LoggedInUser
.
Argument extractors consist of two things: A marker interface and an implementation.
First the marker interface:
@WithArgumentExtractor(LoggedInUserExtractor.class) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) public @interface LoggedInUser {}
… and the argument extractor itself:
public class LoggedInUserExtractor implements ArgumentExtractor<String> { @Override public String extract(Context context) { // if we got no session we break: if (context.getSession() != null) { String username = context.getSession().get("username"); return username; } return null; } @Override public Class getExtractedType() { return String.class; } @Override public String getFieldName() { return null; } }
As you can see the interface of the argument extractor references
@WithArgumentExtractor(LoggedInUserExtractor.class)
.
LoggedInUserExtractor
itself has full access to the context (and the incoming request)
and will return the object
annotated initially by
@LoggedInUser
. In our example this was @LoggedInUser String loggedInUser
.
Currently, there is a bug that causes @Inject
fields to not be injected if you use an empty constructor. The workaround is to create a constructor with an injected parameter, such as Context context
.
public class LoggedInUserExtractor implements ArgumentExtractor<String> { @Inject public LoggedInUserExtractor(Context context) {} @Inject UserDao userDao; ... }