/** * */ package cn.bran.play; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import play.Application; import play.GlobalSettings; import play.Play; import play.api.mvc.Handler; import play.cache.Cache; import play.cache.Cached; import play.core.Router.Routes; import play.mvc.Action; import play.mvc.Results; import play.mvc.Http.Context; import play.mvc.Http.Request; import play.mvc.Http.RequestHeader; import play.mvc.SimpleResult; import cn.bran.japid.template.JapidRenderer; import cn.bran.japid.util.JapidFlags; import cn.bran.japid.util.StringUtils; import cn.bran.play.routing.JaxrsRouter; import play.libs.F.Promise; import scala.Option; import scala.Tuple3; import scala.collection.Seq; /** * @author bran * */ public class GlobalSettingsWithJapid extends GlobalSettings { /** * */ public static final String ACTION_METHOD = "__actionMethod"; private static final String NO_CACHE = "no-cache"; private static String dumpRequest; public static Application _app; private boolean cacheResponse = true; // support @Cache annotation private boolean useJaxrs = true; // // public GlobalSettingsWithJapid() { // System.out.println("GlobalSettingsWithJapid.<init>()"); // } // // static { // System.out.println("GlobalSettingsWithJapid.<cinit>()"); // } // /** * @author Bing Ran (bing.ran@hotmail.com) * @param app */ @Override public void onStop(Application app) { if (app.isDev()) JapidRenderer.persistJapidClasses(); JapidRenderer.shutdown(); JapidFlags.debug("Cache persister shut down and recycled."); } /* * (non-Javadoc) * * @see play.GlobalSettings#onStart(play.Application) */ @Override public void onStart(Application app) { _app = app; JapidRenderer.usePlay = true; JapidRenderer.setAppPath(Play.application().path().getPath()); super.onStart(app); onStartJapid(); JapidRenderer.init(app.isDev(), app.configuration().asMap(), app.classloader()); JaxrsRouter.setClassLoader(app.classloader()); // if (JapidFlags.verbose) { // JapidFlags.log("You can turn off Japid logging in the console by calling JapidRenderer.setLogVerbose(false) in the Global's onStartJapid() method."); // } JaxrsRouter.init(app, this); JapidFlags.printLogLevel(); // printRouteTable(); } protected List<Tuple3<String, String, String>> getPlayRoutes() { play.api.Application realApp = _app.getWrappedApplication(); Option<Routes> routes = realApp.routes(); if (routes.isDefined()) { Routes r = routes.get(); Seq<Tuple3<String, String, String>> docs = r.documentation(); return scala.collection.JavaConversions.seqAsJavaList(docs); } return null; } private void printRouteTable() { JapidFlags.out("<==== Route table derived from jaxRS annotations =====>"); String tab = JaxrsRouter.getRouteTableString(); String[] tabs = tab.split("\n"); for (String t : tabs) { JapidFlags.out("\t" + t); } JapidFlags.out("<=====================================================>"); } /** * sub classes do more customization of Japid here * * @author Bing Ran (bing.ran@hotmail.com) */ public void onStartJapid() { }; @Override public Action<?> onRequest(Request request, final Method actionMethod) { final String actionName = actionMethod.getDeclaringClass().getName() + "." + actionMethod.getName(); final Map<String, String> threadData = JapidController.threadData.get(); if (!cacheResponse) { return new Action.Simple() { public Promise<SimpleResult> call(Context ctx) throws Throwable { // pass the FQN to the japid controller to determine the // template to use // will be cleared right when the value is retrieved in the // japid controller // assuming the delegate call will take place in the same // thread threadData.put(ACTION_METHOD, actionName); Promise<SimpleResult> call = delegate.call(ctx); threadData.remove(ACTION_METHOD); return call; } }; } return new Action<Cached>() { public Promise<SimpleResult> call(Context ctx) { try { beforeActionInvocation(ctx, actionMethod); SimpleResult result = null; Request req = ctx.request(); String method = req.method(); int duration = 0; String key = null; Cached cachAnno = actionMethod.getAnnotation(Cached.class); // Check the cache (only for GET or HEAD) if ((method.equals("GET") || method.equals("HEAD")) && cachAnno != null) { key = cachAnno.key(); if ("".equals(key) || key == null) { key = "urlcache:" + req.uri() + ":" + req.queryString(); } duration = cachAnno.duration(); result = (SimpleResult) Cache.get(key); } if (result == null) { // pass the action name hint to japid controller threadData.put(ACTION_METHOD, actionName); Promise<SimpleResult> ps = delegate.call(ctx); threadData.remove(ACTION_METHOD); if (!StringUtils.isEmpty(key) && duration > 0) { result = ps.get(1, TimeUnit.MILLISECONDS); Cache.set(key, result, duration); } onActionInvocationResult(ctx); return ps; } else { onActionInvocationResult(ctx); return Promise.pure(result); } } catch (RuntimeException e) { throw e; } catch (Throwable t) { throw new RuntimeException(t); } } }; // return new Action.Simple() { // public Result call(Context ctx) throws Throwable { // beforeActionInvocation(ctx, actionMethod); // dumpIt(ctx.request(), actionMethod); // Result call = delegate.call(ctx); // onActionInvocationResult(ctx); // return call; // } // }; } @Override public Handler onRouteRequest(RequestHeader request) { if (useJaxrs) { // if (_app.isDev()) // JapidFlags.debug("route with Japid router"); Handler handlerFor = JaxrsRouter.handlerFor(request); if (handlerFor == null) { handlerFor = super.onRouteRequest(request); // if (handlerFor == null && _app.isDev()) { // JapidFlags.warn("Japid router could not route the request: " // + request.toString()); // } } return handlerFor; } else return super.onRouteRequest(request); } public void onActionInvocationResult(Context ctx) { play.mvc.Http.Flash fl = ctx.flash(); if (RenderResultCache.shouldIgnoreCacheInCurrentAndNextReq()) { fl.put(RenderResultCache.READ_THRU_FLASH, "yes"); } else { fl.remove(RenderResultCache.READ_THRU_FLASH); } // always reset the flag since the thread may be reused for another // request processing RenderResultCache.setIgnoreCacheInCurrentAndNextReq(false); } public static void beforeActionInvocation(Context ctx, Method actionMethod) { Request request = ctx.request(); play.mvc.Http.Flash flash = ctx.flash(); Map<String, String[]> headers = request.headers(); String property = dumpRequest; if (property != null && property.length() > 0) { if (!"false".equals(property) && !"no".equals(property)) { if ("yes".equals(property) || "true".equals(property)) { JapidFlags.log("action ->: " + actionMethod.toString()); } else { if (request.uri().matches(property)) { JapidFlags.log("action ->: " + actionMethod.toString()); } } } } String string = flash.get(RenderResultCache.READ_THRU_FLASH); if (string != null) { RenderResultCache.setIgnoreCache(true); } else { // cache-control in lower case, lower-case for some reason String[] header = headers.get("cache-control"); if (header != null) { List<String> list = Arrays.asList(header); if (list.contains(NO_CACHE)) { RenderResultCache.setIgnoreCache(true); } } else { header = headers.get("pragma"); if (header != null) { List<String> list = Arrays.asList(header); if (list.contains(NO_CACHE)) { RenderResultCache.setIgnoreCache(true); } } else { // just in case RenderResultCache.setIgnoreCacheInCurrentAndNextReq(false); } } } } // static private void getDumpRequest() { // String property = _app.configuration().getString("japid.dump.request"); // dumpRequest = property; // } public void addImport(String imp) { JapidRenderer.addImport(imp); } public void addImport(Class<?> cls) { JapidRenderer.addImport(cls); } public void addImportStatic(String imp) { JapidRenderer.addImportStatic(imp); } public void addImportStatic(Class<?> cls) { JapidRenderer.addImportStatic(cls); } public void setKeepJavaFiles(boolean keepJava) { JapidRenderer.setKeepJavaFiles(keepJava); } public void setLogVerbose(boolean verb) { JapidRenderer.setLogVerbose(verb); } public static void setTemplateRoot(String... root) { JapidRenderer.setTemplateRoot(root); } public static void setRefreshInterval(int i) { JapidRenderer.setRefreshInterval(i); } public void setCacheResponse(boolean c) { this.cacheResponse = c; } /** * set the switch to present a Japid error in pretty HTML page, or it throws * an exception. The default is true; * * @author Bing Ran (bing.ran@gmail.com) * @param presentErrorInHtml */ public void setPresentErrorInHtml(boolean presentErrorInHtml) { JapidRenderer.setPresentErrorInHtml(presentErrorInHtml); } /** * controls if Just-In-Time persisting of the Japid classes is enabled. * * @param enableJITCachePersistence * the enableJITCachePersistence to set */ public static void setEnableJITCachePersistence(boolean enableJITCachePersistence) { JapidRenderer.setEnableJITCachePersistence(enableJITCachePersistence); } /** * set the folder to save the japid classes cache file * * @param classCacheRoot * the classCacheRoot to set */ public static void setClassCacheRoot(String classCacheRoot) { JapidRenderer.setClassCacheRoot(classCacheRoot); } /** * @return the useJaxrs */ public boolean isUseJaxrs() { return useJaxrs; } /** * set to use Jax-RS protocol based routing mechanism * * @param useJaxrs * the useJaxrs to set */ public void setUseJapidRouting(boolean useJaxrs) { this.useJaxrs = useJaxrs; } /** * tell Japid engine to search for Japid scripts from classpath only * * @author Bing Ran (bing.ran@gmail.com) */ public void setScanClasspathOnly() { setTemplateRoot((String[]) null); } /* * (non-Javadoc) * * @see play.GlobalSettings#onHandlerNotFound(play.mvc.Http.RequestHeader) */ @Override public Promise<SimpleResult> onHandlerNotFound(RequestHeader arg0) { if (_app.isDev()) { List<Tuple3<String, String, String>> playRoutes = getPlayRoutes(); JapidResult r = new JapidResult(JapidRenderer.renderWith(japidviews.dev404.class, arg0, playRoutes, JaxrsRouter.getRouteTable())); Promise<SimpleResult> pure = play.libs.F.Promise.pure((SimpleResult) Results.notFound(r)); return pure; } else return super.onHandlerNotFound(arg0); } }