@With annotation in Play Framework

If you're following well the article about authentication and authorization in Play Framework, you could see that @Authorize annotation is accompanied by another annotation, @With. Thanks to it, the @Authorized is evaluated with the class specified inside @With annotation.

This article will present deeper the subject of @With annotation. At the begin we'll see how it's constructed. At the second part we'll adopt @With to our sample store.

What is @With annotation in Play Framework ?

This annotation is a kind of hook invoked before the call of controller method responsible for generating the Result object. You can think about @With as about a servlet's filter chain applied before generating a response for HTTP request. @With annotation accepts only one parameter: an array of classes extending play.mvc.Action. If more than one class is specified, they're executed in the order of definition, from the left to the right.

Very often a class called in @With annotation extends of Action's inner Simple class. Because Action extends play.mvc.Result, any action invoked thanks to the annotation can produce an result shown to the user instead of the result produced by the controller's method. Actions must define call method which returns Promise object. If we're looking on source code of F class, we'll see that it is a public static typed class, which methods return always an instance of object created by Scala's FPromiseHelper. And this helper wrapps Scala's Future object to expected F.Promise instance.

If we want to execute next Action in the list, we should return delegate.call(context) method. Other responses will interrupt request processing. If they're other than null (instance of Result), they'll produce a physical result, as for example redirection to another route.

Implement @With annotation in Play Framework

To illustrate use of actions in @With annotations, we'll make a simple checking feature. We want that customers could buy only the products available in the stock. If checked product is out of stock, we want to redirect user to out-of-stock informational page. To do that, we need to start by updating the database with in_stock column storing the information if the product is available to buy (1) or not (0). The content of this evolution scripts is following:

# --- !Ups
ALTER TABLE products ADD COLUMN in_stock INT(1) NOT NULL;
UPDATE products SET in_stock = 1 WHERE id < 3;

# --- !Downs

After executing, we should have following state in our products table:

mysql> select * from products;
+----+---------------+--------+-------------------------------------------------------+-------+----------+
| id | categories_id | name   | description                                           | price | in_stock |
+----+---------------+--------+-------------------------------------------------------+-------+----------+
|  1 |             1 | bread  | You must eat it every morning !                       |  1.99 |        1 |
|  2 |             1 | butter | You can''t eat our bread without this famous butter ! |  5.99 |        1 |
|  3 |             2 | water  | This mineral water will add you a lot of energy       |  0.99 |        0 |
+----+---------------+--------+-------------------------------------------------------+-------+----------+
3 rows in set (0.00 sec)

Now, it's the turn of action used to check product availability, OutOfStockAction:

public class OutOfStockAction extends Action  {

  @Override
  public Promise call(Context ctx) throws Throwable {
    Logger.debug("Invoking OutOfStock before adding a product to the shopping cart");
    String[] parts = Context.current().request().path().split("/");
    Product product = null;
    final int productId = FromStringConverter.toInt(parts[parts.length-1]);
    try {
      // We can't use service even with @Transactional annotation; an exception "no EntityManager is bound"
      // is thrown every time. JPA.withTransaction is a possible workaround.
      Boolean inStock = JPA.withTransaction(new F.Function0() {
        @Override
        public Boolean apply() throws Throwable {
          Query inStockQuery = JPA.em().createQuery("SELECT p FROM Product p WHERE p.id = :productId AND p.inStock = :inStock");
          inStockQuery.setParameter("productId", productId);
          inStockQuery.setParameter("inStock", Product.IN_STOCK);
          Product product = null;
          try {
            product = (Product) inStockQuery.getSingleResult();
          } catch (NoResultException nre) {
          } catch (Exception e) {
            Logger.error("An error occurred on checking product ("+productId+") availability", e);
          }
          return product != null;
        }
      });
      if (inStock.booleanValue() == true) {
        return delegate.call(ctx);
      }
    } catch (Exception e) {
      Logger.error("An error occurred on checking if product is in stock for path "+Context.current().request().path(), e);
    }
    return F.Promise.pure(redirect(routes.ProductController.productUnavailable(productId)));
  }
}

The code is quite simple. First, we split the path of request to get the last element being the id of the product. After we make JPA request to see if this product is in stock. If it's the case, we continue the action chain execution by returning delegate.call(ctx). If it's not the case, we redirect immediately to the page with "product out-of-stock message". We also create a second action. Thanks to it we'll able to test if the execution chain is well interrupted in the case of product out-of-stock:

public class TestAction extends Action  {

  @Override
  public Promise call(Context ctx) throws Throwable {
    Logger.debug("Calling TestAction");
    return delegate.call(ctx);
  }
}

The last thing to do is to annotate controller's method which adds given product to user's shopping cart:

public class ShoppingCartController extends Controller {
  @With({OutOfStockAction.class, TestAction.class})
  @Transactional
  public static Result addProduct(int productId) {
    // ...
  }
}

And that's all. The actions are executed in the order of definition. It means that OutOfStockAction will be called before TestAction. If one redirection is made (= product out-of-stock), TestAction won't be called. ShoppingCartController's addProduct method will be called only if both actions return delegate.call method. All chain is executed in the same thread. Be aware about it and avoid to put time consuming operations influencing controllers (as for example our availability test) in actions. Otherwise customer can think that the shop is bugged and spends his money somewhere else.

Another interesting point discovered at the occasion of working with @With annotation and actions, is the use of transactions in actions. The first strange thing is the order of @Transactional and @With appearance in addProduct method. If we put @Transactional annotation before @With and the product is in the stock (addProduct method is called), following exception is thrown:

[debug] application - Invoking OutOfStock before adding a product to the shopping cart
[debug] application - Calling TestAction
[debug] application - Adding product to the cart called
[error] application - An error occurred on adding product (1) to cart
java.lang.RuntimeException: No EntityManager bound to this thread. Try to annotate your action method with @play.db.jpa.Transactional
  at play.db.jpa.JPA.em(JPA.java:51) ~[play-java-jpa_2.11-2.3.1.jar:2.3.1]
  at services.ProductService.getById(ProductService.java:27) ~[classes/:na]
  at controllers.ShoppingCartController.addProduct(ShoppingCartController.java:28) ~[classes/:2.3.1]
  at Routes$$anonfun$routes$1$$anonfun$applyOrElse$11$$anonfun$apply$11.apply(routes_routing.scala:219) ~[classes/:na]
  at Routes$$anonfun$routes$1$$anonfun$applyOrElse$11$$anonfun$apply$11.apply(routes_routing.scala:219) ~[classes/:na]

As the message indicates, this message means that they're no EntityManager available to current thread (the same thread which was used in actions). The error looks like Play would associate @Transactional annotation to @with actions and no to addProduct method. But it also strange because when we annotate @With actions with @Transactional, it doesn't work. Here too the "No EntityManager bound to this thread" is thrown. This observation comes from Play 2.3 may be not present in other versions.

This article shows us how to implement a kind of servlet's filter for Play application. This filtering method can be achieved thanks to @With annotation which contains a list of objects extending Action class and returning F.Promise result. An action can return call() method which will be continue the execution chain or another result, as for example physical one (view, redirection). The last one'll break the execution chain.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!