Monday, May 9, 2016

Reflection loading of reverse routings

In Twirl reverse routing is as easy as the inline Scala operator - @controllers.routes.HomeController.loginSubmit. The same for the Java or Scala code. Just the routes package need to be added before the controller class name.

I implement the reves routing helper in my handlebars templates with the help of reflection.

It's no need even to add the rotes package

...
<form action="{{route "controllers.HomeController.loginSubmit"}}" method="POST>
...
{{route "controllers.HomeController.myAction()"}}
{{route "controllers.HomeController.myActionName(\"name\")"}}
{{route "controllers.HomeController.myActionAge(33)"}}
The core method of realization is the reflection of the correspond routes class:
  1. Get the class loader.
  2. Load the auto generated class routes.
  3. Get the reverse router object of the controller (it's in a static field of the routes class).
  4. Get the action of the reverse controller (do not forget about method parameters).
  5. Get the URL of the action.

Realization

private static String reverseUrl(
    final String controllerPackage,
    final String controllerClass,
    final String methodName,
    final RouteMethodArguments methodArguments) throws Exception {

  // Get the play class loader.
  final ClassLoader classLoader = Play.classloader(Play.current());

  // Load the auto generated class "routes".
  final Class routerClass = classLoader.loadClass(controllerPackage + ".routes");

  // Get the reverse router object of the controller.
  final Field declaredField = routerClass.getDeclaredField(controllerClass);
  // It's static field.
  final Object object = declaredField.get(null);
  final Class type = declaredField.getType();

  // Get the action of the reverse controller.
  final Method routerMethod = type.getMethod(methodName, methodArguments.types);
  final Call invoke = (Call) routerMethod.invoke(object, methodArguments.values);

  // Get the URL of the action.
  final String actionUrl = invoke.url();

  return actionUrl;
}
There are also some trivial code for the parsing helper parameter and for the caching. For now, I support only the String and Integer parameters for the actions. The cashe system is the guava cache.

One more thing - the RouteMethodArguments class that represents the arguments of the action

private static class RouteMethodArguments {
  final Class<?>[] types;
  final Object[] values;

  RouteMethodArguments(Class<?>[] types, Object[] values) {
    this.types = types;
    this.values = values;
  }
}