play framework

At Logz.io, we’re using Play 2.0 framework. We wanted to share our experience hardening and securing this part of our architecture by using action composition.

 

Let’s jump to the gory details.

Action composition is a powerful way to enhance or restrict a behavior of a controller.

By composing actions, we practically intercept the incoming request before it arrives to the controller. This allows us perform additional work on the incoming request before the actual process.

Action in Play 2.0 Java version, is an instance of play.mvc.Action with a method named call(Context context) that returns a play.mvc.Result value (or better to say Promise results):

public abstract class Action extends Results {
    public abstract F.Promise call(Context var1) throws Throwable;
}

Play, behind the scenes, adds a root action that will properly use the call method above, and this allows us to further compose Actions by ourselves.

Action composition allows us to add one or more behaviors to other actions, for example, we can compose an Authentication action on another action (service) to limit the access to authenticated users only.

Action composition is done using Java annotations:

Java annotations are a form of Metadata for code, and are used to decorate a class, method, field, parameter, variable, constructor, or package. Annotations can be used at compile time or retrieved at runtime using reflection.

Building our own custom actions will be done using annotations declared for runtime (RetentionPolicy.Runtime):

1.  Define an annotation – this is the annotation we use to mark an action composition over another action:

Annotation declaration is similar to an interface declaration, forwarded by @. Annotations only define some information on the element they decorate, and they do not do any business logic. For this we need a consumer to act upon the presence of annotations.

Defining a custom annotation also uses other annotations: @Target which defines the allowed elements to use with this annotation, @Retention which defines when is this annotation is expected to be used (source, class, or runtime).

In order to define custom action, Play provides the @With annotation to declare which class (must be of type extending Action) to use to perform the actual composite work (a.k.a the consumer).

Inside the annotation declaration, we can only use primitives, string and enums. All members are defined as methods and can be added with default value.

When using the annotation to decorate another Action, we can set values for each member using the name=value format. If only one member is defined, it must be named value and can be set without the attribute name:


In case we defined a default value, we can use the decoration without any parameter.

2.  Define a consumer – a class which extends Action and implements specific logic inside the call method:

public static class AuthenticatedAction extends Action { 
    public AuthenticatedAction() { } 
    public F.Promise call(Http.Context context) throws Throwable { 
        return this.delegate.call(context); 
    }
} 

The Action class retrieves the custom annotation as configuration, and the Action it wraps will be set as its delegate

public abstract class Action extends Results { 
    public T configuration; 
    public Action<?> delegate; 
} 

3. This allows us to implement the call method and inside forward work to the original Action using the delegate. We can also access the annotation members using the configuration member of the abstract Action class. We will now enforce the the restrictions verifying the access to this API service is done by authenticated user only. If it is, we can forward work using the delegate, if not – we can return HTTP Error 401 Unauthorized:

Usage in controller:


In order to pass object/s from the Action to the controller you can use the args member of context, in this example – who is the username currently logged in. This context is then available to the controller to get the data out:

String username = (String) ctx().args.get("username"); 

We use action composition extensively throughout our Play code and find it useful. Hopefully, this helps!

Asaf Yigal is co-founder and VP Product at Logz.io. Prior to Logz.io, Asaf co-founded Currensee, a social-trading platform, which was later acquired by OANDA in 2013. Prior to Currensee, Asaf played executive roles at Akorri in developing an end-to-end performance monitoring platform and at Onaro in developing a storage resource management platform. Both Akorri and Onaro were acquired by NetApp. Prior to Onaro, Asaf headed a research team in the Israeli Navy, taking an artificial intelligence system to military deployment. Asaf holds a B.S. from the Technion and is an Instrument-rated private pilot.