Update: Play Framework now supports Java filters as of 2.5, so this post is no longer as relevant as it once was if you are on the latest version. However, if you aren't able to upgrade, continue reading.
Working with the more advanced features of the Play Framework can be rather challenging if all you are used to is Java. Play only exposes Scala APIs for Filters. However, because of Java and Scala interoperability, it is possible to access those Scala APIs through Java. Possible doesn't necessarily mean pretty. Below is a walkthough on how to implement a Filter only using Java.
The hard part about this whole process is understanding how to extend the Filter class in Java. Without further ado, here's the magic:
import play.api.mvc.*;
import scala.Function1;
import scala.concurrent.Future;
import scala.runtime.AbstractFunction1;
public abstract class JavaFilter implements Filter {
@Override
public Future<Result> apply(Function1<RequestHeader, Future<Result>> nextFilter, RequestHeader requestHeader) {
return nextFilter
.apply(requestHeader)
.map(new AbstractFunction1<Result, Result>() {
@Override
public Result apply(Result currentResult) {
return Apply(currentResult, requestHeader);
}
},
play.api.libs.concurrent.Execution.defaultContext());
}
@Override
public EssentialAction apply(EssentialAction next) {
return Filter$class.apply(this, next);
}
public abstract Result Apply(Result currentResult, RequestHeader requestHeader);
}
The code above maps Scala only features into the Java features we know and love (read: are familiar with and tolerate).
Let's say you want to add an HTTP response header "X-Hello" with value "World!" every time the incoming request has a "X-Filter" header. All you have to do is implement our JavaFilter from above!
import play.api.mvc.RequestHeader;
import play.api.mvc.Result;
public class HelloFilter extends JavaFilter {
@Override
public Result Apply(Result currentResult, RequestHeader requestHeader) {
if (requestHeader.headers().get("X-Filter").isDefined()) {
ResultAdapter resultAdapter = new ResultAdapter(currentResult);
return resultAdapter.WithHeader("X-Hello", "World!");
}
return currentResult;
}
}
JavaFilter provides a ready-to-go abstract definition that is eventually called by the Play pipeline. However, interacting with the Result class directly is a bit cumbersome since the method parameters take in classes that are generated by syntactic sugar in Scala. I've added a quick Adapter below that should allow you to add a response header as a traditional key-value pair.
import play.api.libs.iteratee.Enumerator;
import play.api.mvc.ResponseHeader;
import play.api.mvc.Result;
import scala.Enumeration;
import scala.Tuple2;
import scala.collection.JavaConversions;
import scala.collection.mutable.Buffer;
import java.util.ArrayList;
import java.util.List;
public class ResultAdapter extends Result{
private Result result;
public ResultAdapter(Result result) {
super(result.header(), result.body(), result.connection());
this.result = result;
if (result instanceof ResultAdapter) {
throw new RuntimeException("Could not create ResultAdapter from ResultAdapter");
}
}
public ResultAdapter WithHeader(String name, String value) {
List<Tuple2<String, String>> headerList = new ArrayList<>();
Tuple2<String, String> header = new Tuple2<>(name, value);
headerList.add(header);
Buffer<Tuple2<String, String>> headerBuffer = JavaConversions.asScalaBuffer(headerList);
result = result.withHeaders(headerBuffer);
return this;
}
@Override
public ResponseHeader header() {
return result.header();
}
@Override
public Enumerator<byte[]> body() {
return result.body();
}
@Override
public Enumeration.Value connection() {
return result.connection();
}
@Override
public Object productElement(int n) {
return result.productElement(n);
}
@Override
public int productArity() {
return result.productArity();
}
@Override
public boolean canEqual(Object that) {
return result.canEqual(that);
}
@Override
public boolean equals(Object that) {
return result.equals(that);
}
}
So, you're good to go! Actually, not really. You need to let Play know about your new Filter. Below is an example Global.java class that will add the Filter.
import play.GlobalSettings;
import play.api.mvc.EssentialFilter;
public class Global extends GlobalSettings {
@Override
public <T extends EssentialFilter> Class<T>[] filters() {
return new Class[] {HelloFilter.class};
}
}
Now you're done!