com.github.andriykuba.play.handlebars.helpers.PlayHelpers Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of play-handlebars Show documentation
Show all versions of play-handlebars Show documentation
Handlebars templates based on Java port of handlebars with special handlers for Play Framework
package com.github.andriykuba.play.handlebars.helpers;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.github.andriykuba.play.handlebars.HandlebarsApi;
import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Options;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import play.api.Play;
import play.i18n.Lang;
import play.i18n.MessagesApi;
import play.mvc.Call;
/**
* Helpers specific for the Play.
*
*/
public final class PlayHelpers {
// Guava cache is a thread-safe so we can use it here with no doubt.
final LoadingCache reverseRoutingCache;
final LoadingCache assetsRoutingCache;
final MessagesApi messagesApi;
/**
* MessagesApi is a singleton so we can use it in helpers
*
* @param messagesApi
* MessagesApi, used in the message helpers
*/
public PlayHelpers(final MessagesApi messagesApi) {
this.messagesApi = messagesApi;
// Initialize the reverse router cache.
reverseRoutingCache = CacheBuilder.newBuilder().build(
new CacheLoader() {
public CharSequence load(String key) throws Exception {
return PlayHelpers.loadRoute(key);
}
});
// Initialize the assets router cache.
assetsRoutingCache = CacheBuilder.newBuilder().build(
new CacheLoader() {
public CharSequence load(String key) throws Exception {
return PlayHelpers.loadAsset(key);
}
});
}
/**
* Replacement of the Twirl's "@routes.Assets.versioned".
*
* @param url
* relative path to the asset.
* @return
* actual path to the asset.
* @throws Exception
* Any exception in the case of resolving assets URL
*/
public CharSequence asset(final String url) throws Exception {
return assetsRoutingCache.get(url);
}
/**
* Called by the cache loader. Do the same as {@link #asset(String) asset}
*
* @param url
* Inner URL of the assets
* @return
* Real URL of the assets
* @throws Exception
* Any exception in the case of resolving assets URL
*/
public static CharSequence loadAsset(final String url) throws Exception {
/*-
* This code is good only if "aggregateReverseRoutes" is configured
*
* Assets assets = new controllers.Assets.Asset(url));
* return controllers.routes.Assets.versioned(assets).toString();
*
* So it was done the same but with reflection, it works in both cases
* as with "aggregateReverseRoutes" as without.
*/
return reverseUrl(
"controllers",
"Assets",
"versioned",
new RouteMethodArguments(
new Class[] { controllers.Assets.Asset.class },
new Object[] { new controllers.Assets.Asset(url) }));
}
/**
* The same as the reverse routing {@code
* .routes..
* } but does not need the ".routes." part in the path.
*
* Reflection used for get the reverse routing. Cache used for the
* optimization.
*
* @param action
* Action, like {@code ..}.
* Only {@link String} and {@link Integer} action arguments type are
* supported. String must not contain a comma symbol, like ",".
* @param options
* Object for getting context to resolve handlebar variables in method signature.
* @return
* URL that correspond to the action
* @throws Exception
* any exception in the cache
*/
public CharSequence route(final String action, final Options options) throws Exception {
String actionReolved = resolveContextVariables(action.trim(), options.context);
return reverseRoutingCache.get(actionReolved);
}
/**
* Called by the cache loader. Do the same as {@link #route(String) route}
*
* @param action
* @return
* @throws Exception
*/
private static CharSequence loadRoute(final String action) throws Exception {
// Trim the string to avoid nasty space mistakes.
final int signatureStart = action.indexOf("(");
final String actionWithoutArguments =
(signatureStart > 0 ? action.substring(0, signatureStart) : action).trim();
// Divide the method call from the class path.
final String[] methodSplitment = splitStringByLastDot(actionWithoutArguments);
final String methodName = methodSplitment[1];
// Divide the class from the path.
final String[] classSplitment = splitStringByLastDot(methodSplitment[0]);
// Get the method and its arguments
RouteMethodArguments methodArguments;
if (signatureStart > 0) {
// Possible arguments are present.
final String parametersString = action.substring(signatureStart + 1, action.lastIndexOf(")"));
methodArguments = parseMethodArguments(parametersString);
} else {
methodArguments = new RouteMethodArguments(null, null);
}
// Return the action URL
return reverseUrl(classSplitment[0], classSplitment[1], methodName, methodArguments);
}
/**
* Get the URL by the controllers package, class, method and parameters. It
* use reflection for get the reverse route.
*
* @param controllerPackage
* The name of the package of the controller
* @param controllerClass
* The name of the controller class
* @param methodName
* The method name in the controller, i.e. action
* @param methodArguments
* The arguments of the method
* @return
* Reversed URL
* @throws Exception
* Any exception in the case of reversion
*/
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;
}
/**
* Parse the method arguments. Only String and Integers are legal parameters.
* No parameters are allowed, but an empty parameter is not allowed
*
* @param argumentsString
* The method arguments as string
* @param context
* @param context
* @return
* Parsed route arguments
*/
private static RouteMethodArguments parseMethodArguments(String argumentsString) {
if (argumentsString.trim().length() == 0) {
// Method with empty braces - no arguments
return new RouteMethodArguments(null, null);
}
final String[] arguments = argumentsString.split(",");
final List> types = new ArrayList<>();
final List