package cn.bran.play;
import java.util.HashMap;
import java.util.Map;
import play.cache.Cache;
import play.data.Form;
import play.mvc.Controller;
import play.mvc.Http.Context;
import play.mvc.Result;
import play.mvc.Results;
import cn.bran.japid.compiler.NamedArgRuntime;
import cn.bran.japid.template.JapidRenderer;
import cn.bran.japid.template.JapidTemplateBaseWithoutPlay;
import cn.bran.japid.template.RenderResult;
import cn.bran.japid.util.DirUtil;
import cn.bran.japid.util.RenderInvokerUtils;
import cn.bran.japid.util.StackTraceUtils;
/**
* a helper class. for hiding the template API from user eyes. not really needed
* since the template invocation API is simple enough.
*
* @author Bing Ran<bing_ran@hotmail.com>
*/
public class JapidController extends Controller {
public static ThreadLocal<Map<String, String>> threadData = new ThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<String, String>();
}
};
/**
*
*/
private static final String CONTROLLERS = "controllers.";
public static final char DOT = '.';
/**
* render an array of objects to a template defined by a Template class.
*
* @param <T>
* a sub-class type of JapidTemplateBase
* @param c
* a sub-class of JapidTemplateBase
* @param args
* arguments
*/
public static <T extends JapidTemplateBaseWithoutPlay> JapidResult render(Class<T> c, Object... args) {
try {
RenderResult rr = RenderInvokerUtils.invokeRender(c, args);
JapidResult japidResult = new JapidResult(rr);
postProcess(japidResult);
return japidResult;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static JapidResult postProcess(JapidResult japidResult) {
// XXX play2 does not guarantee the response
if (Context.current.get() != null) {
// apply headers
try {
Map<String, String> currentHeaders = response().getHeaders();
Map<String, String> headers = japidResult.getHeaders();
for (String k : headers.keySet()) {
if (!currentHeaders.containsKey(k))
response().setHeader(k, headers.get(k));
}
} catch (RuntimeException e) {
}
}
// eagerly evaluate. The consequence is that there is no cache support
// in each included part
// no we need cache support
return japidResult/* .eval() */;
}
/**
* just hide the result throwing
*
* @param rr
*/
protected static JapidResult render(RenderResult rr) {
return postProcess(new JapidResult(rr));
}
/**
* pickup the Japid renderer in the conventional location and render it.
* Positional match is used to assign values to parameters
*
* TODO: the signature would be confusing for cases where there is a single
* argument and the type is an array! In that case the user must cast it to
* Object: <code>renderJapid((Object)myArray);</code>
*
* @param objects
*/
public static JapidResult renderJapid(Object... objects) {
String action = template("renderJapid");
return renderJapidWith(action, objects);
}
public static JapidResult renderJapidByName(NamedArgRuntime... namedArgs) {
String action = template("renderJapidByName");
return renderJapidWith(action, namedArgs);
}
/**
*
* @author Bing Ran (bing.ran@hotmail.com)
* @param templateName the script name or a fully qualified class name under which the renderer class
* is registered.
*
* @param args
* @return
*/
public static JapidResult renderJapidWith(String templateName, Object... args) {
templateName = getFullViewName(templateName);
JapidResult japidResult = new JapidResult(JapidRenderer.renderWith(templateName, args));
postProcess(japidResult);
return japidResult;
}
private static JapidResult generateErrorResult(Exception e) {
JapidResult result = new JapidResult(JapidRenderer.handleException(e));
postProcess(result);
return result;
}
/**
* render data to a template string, which is parsed and compiled to Java bytecode before use.
*
* @author Bing Ran (bing.ran@hotmail.com)
* @param template the content of the template
* @param args the arguments
* @return
*/
public static JapidResult renderDynamic(String template, Object... args) {
try {
RenderResult rr = JapidRenderer.renderDynamic(template, args);
return new JapidResult(rr);
} catch (Throwable e) {
return new JapidResult(JapidRenderer.handleException(e));
}
}
public static JapidResult renderDynamicByKey(String key, Object... args) {
try {
RenderResult rr = JapidRenderer.renderDynamicByKey(key, args);
return new JapidResult(rr);
} catch (Throwable e) {
return new JapidResult(JapidRenderer.handleException(e));
}
}
private static String getFullViewName(String template) {
if (template.startsWith("@")) {
template = template.substring(1);
// get parent path
String defaultView = template("renderJapidWith");
String parent = defaultView.substring(0, defaultView.lastIndexOf('.'));
template = parent + "." + template;
}
return template;
}
public static JapidResult renderJapidWith(String template, NamedArgRuntime[] namedArgs) {
template = getFullViewName(template);
JapidResult japidResult = new JapidResult(JapidRenderer.getRenderResultWith(template, namedArgs));
return postProcess(japidResult);
}
public static String template(String method) {
// first check if there is a method hint in the session
String japidControllerInvoker = threadData.get().remove(GlobalSettingsWithJapid.ACTION_METHOD);
// use the thread local is not reliable nor correct if the action forwards to another action.
// disable it
if (true || japidControllerInvoker == null) {
// return StackTraceUtils.getJapidRenderInvoker();
japidControllerInvoker = StackTraceUtils.getJapidControllerInvoker(method);
}
if (japidControllerInvoker.startsWith(CONTROLLERS))
japidControllerInvoker = japidControllerInvoker.substring(CONTROLLERS.length());
String expr = japidControllerInvoker;
// some content negotiation
// TODO: shall we set the response content type accordingly?
String format = resolveFormat(Context.current.get());
if ("html".equals(format)) {
return expr;
} else {
String expr_format = expr + "_" + format;
try {
Class<?> appClass = JapidRenderer.getClass(JapidRenderer.getTemplateClassName(expr_format));
if (appClass != null)
return expr_format;
else {
return expr;
}
} catch (RuntimeException e) {
return expr;
}
}
}
public static String resolveFormat(Context context) {
if (context == null)
return "html";
Map<String, String[]> headers = context.request().headers();
String format = "html";
if (headers.get("accept") == null && headers.get("Accept") == null && headers.get("ACCEPT") == null) {
return format;
}
String accept = "";
if (headers.get("accept") != null)
accept = headers.get("accept")[0];
else if (headers.get("Accept") != null)
accept = headers.get("Accept")[0];
else if (headers.get("ACCEPT") != null)
accept = headers.get("ACCEPT")[0];
if (accept.indexOf("application/xhtml") != -1 || accept.indexOf("text/html") != -1 || accept.startsWith("*/*")) {
format = "html";
} else if (accept.indexOf("application/xml") != -1 || accept.indexOf("text/xml") != -1) {
format = "xml";
} else if (accept.indexOf("text/plain") != -1) {
format = "txt";
} else if (accept.indexOf("application/json") != -1 || accept.indexOf("text/javascript") != -1) {
format = "json";
} else if (accept.endsWith("*/*")) {
format = "html";
}
return format;
}
/**
* mind the cost associated with this and the key building issues, as stated
* in the cache() method
*
* @param objs
* @return
*/
protected static RenderResult getFromCache(Object... objs) {
// the key building with caller info and the arguments
if (RenderResultCache.shouldIgnoreCache())
return null;
String caller = buildKey(null, objs);
Object object = Cache.get(caller);
if (object instanceof RenderResult) {
return (RenderResult) object;
} else {
return null;
}
}
/**
*
* @param keyBase
* usually the fully qualified method name of the controller
* action
* @param objs
* @return
*/
protected static RenderResult getFromCache(String keyBase, Object... objs) {
// the key building with caller info and the arguments
if (RenderResultCache.shouldIgnoreCache())
return null;
String caller = buildKey(keyBase, objs);
Object object = Cache.get(caller);
if (object instanceof RenderResult) {
return (RenderResult) object;
} else {
return null;
}
}
/**
* @param objs
* @return
*/
private static String buildKey(String base, Object... objs) {
// the getCaller thing is relatively expensive, as it might take
// hundreds of us to complete.
String caller = base;
if (base == null)
caller = StackTraceUtils.getCaller2(); // tricky and expensive
for (Object o : objs) {
caller += "-" + String.valueOf(o);
}
return caller;
}
/**
* render a text in a RenderResult so it can work with invoke tag in
* templates.
*
* @param s
*/
protected static JapidResult renderText(String s) {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "text/plain; charset=utf-8");
return render(new RenderResult(headers, new StringBuilder(s), -1L));
}
protected static Result renderText(Object o) {
String str = o == null ? "" : o.toString();
return renderText(str);
}
protected static Result renderText(int o) {
return renderText(new Integer(o));
}
protected static Result renderText(long o) {
return renderText(new Long(o));
}
protected static Result renderText(float o) {
return renderText(new Float(o));
}
protected static Result renderText(double o) {
return renderText(new Double(o));
}
protected static Result renderText(boolean o) {
return renderText(new Boolean(o));
}
protected static Result renderText(char o) {
return renderText(new String(new char[] { o }));
}
protected static NamedArgRuntime named(String name, Object val) {
return new NamedArgRuntime(name, val);
}
static String runnerName = CacheablePlayActionRunner.class.getName();
/**
* determine if the current stack frame is a descendant of
* CacheablePlayActionRunner which is used when invoking actions from Japid
* views
*
* @return
*/
public static boolean isInvokedfromJapidView() {
Throwable t = new Throwable();
t.printStackTrace();
final StackTraceElement[] ste = t.getStackTrace();
for (int i = 0; i < ste.length; i++) {
StackTraceElement st = ste[i];
String className = st.getClassName();
if (className.equals(runnerName)) {
return true;
}
}
return false;
}
/**
* Instantiates a new authenticity checking form that wraps the specified class.
*/
public static <T> Form<T> form(Class<T> clazz) {
return new AuthenticForm<T>(clazz);
}
/**
* Instantiates a new form that wraps the specified class.
*/
public static <T> Form<T> form(String name, Class<T> clazz) {
return new AuthenticForm<T>(name, clazz);
}
/**
* wrap a RenderResult in an OK result.
*
* @author Bing Ran (bing.ran@gmail.com)
* @param rr
* @return
*/
public static Result ok(RenderResult rr) {
try {
return new JapidResult(rr);
} catch (Exception e) {
return generateErrorResult(e);
}
}
/**
* find out if a Japid class denoted by the template name is available
*
* @author Bing Ran (bing.ran@gmail.com)
* @param templateName
* @return
*/
public static boolean hasTemplate(String templateName) {
return JapidRenderer.hasTemplate(templateName);
}
public static Class<? extends JapidTemplateBaseWithoutPlay> getTemplateClass(String templateName) {
return JapidRenderer.getTemplateClass(templateName);
}
}