Package cn.bran.play

Source Code of cn.bran.play.JapidPlugin

package cn.bran.play;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

import play.Logger;
import play.Play;
import play.Play.Mode;
import play.PlayPlugin;
import play.classloading.ApplicationClasses.ApplicationClass;
import play.classloading.ApplicationClassloaderState;
import play.exceptions.CompilationException;
import play.exceptions.UnexpectedException;
import play.mvc.Http.Header;
import play.mvc.Http.Request;
import play.mvc.Http.Response;
import play.mvc.Router;
import play.mvc.Router.Route;
import play.mvc.Scope.Flash;
import play.mvc.results.Result;
import play.templates.Template;
import play.vfs.VirtualFile;
import cn.bran.japid.compiler.JapidCompilationException;
import cn.bran.japid.template.JapidRenderer;
import cn.bran.japid.template.JapidTemplateBaseWithoutPlay;
import cn.bran.japid.util.JapidFlags;
import cn.bran.japid.util.StringUtils;
import cn.bran.play.routing.AutoPath;
import cn.bran.play.routing.RouterClass;
import cn.bran.play.routing.RouterMethod;

/**
*
*
* @author Bing Ran<bing_ran@hotmail.com>
*
*/
public class JapidPlugin extends PlayPlugin {
  /**
   *
   */
  private static final String WHEN_REQ_IN = "_whenRaw";
  public static final String ACTION_METHOD = "__actionMethod";
  private static final String RENDER_JAPID_WITH = "/renderJapidWith";
  private static final String NO_CACHE = "no-cache";
  private static AtomicLong lastTimeChecked = new AtomicLong(0);
  // can be used to cache a plugin scoped values
  private static Map<String, Object> japidCache = new ConcurrentHashMap<String, Object>();
  private String appPath;


  /**
   * pre-compile the templates so PROD mode would work
   */
  @Override
  public void onLoad() {
    Logger.info("JapidPlugin.onload().");
    if (Play.mode == Mode.DEV) {
      Logger.info("[Japid] version " + JapidRenderer.VERSION + " in DEV mode. Detecting japid scripts on classpath...");
      beforeDetectingChanges();
    } else {
      String isPrecompiling = System.getProperty("precompile");
      if (isPrecompiling != null && (isPrecompiling.equals("yes") || isPrecompiling.equals("true"))) {
        Logger.info("[Japid] version " + JapidRenderer.VERSION + " in PROD mode with precompiling: detect japid template changes.");
        beforeDetectingChanges();
      } else { // PROD mode and not pre-compile
        Logger.info("[Japid] version " + JapidRenderer.VERSION + " in PROD mode without pre-compiling. If the Japid templates have not been compiled to Java code, please run 'play japid:gen' before starting the application.");
      }
    }
    getDumpRequest();
    setupInjectTemplateBorder();
  }

  /**
   * @author Bing Ran (bing.ran@hotmail.com)
   */
  private void setupInjectTemplateBorder() {
    String property = Play.configuration.getProperty("japid.trace.file", "false");
    if ("on".equalsIgnoreCase(property) || "yes".equalsIgnoreCase(property))
      property = "true";
    JapidTemplateBaseWithoutPlay.globalTraceFile = new Boolean(property);

    property = Play.configuration.getProperty("japid.trace.file.html", "false");
    if ("on".equalsIgnoreCase(property) || "yes".equalsIgnoreCase(property))
      property = "true";
    JapidTemplateBaseWithoutPlay.globalTraceFileHtml = new Boolean(property);

    property = Play.configuration.getProperty("japid.trace.file.json", "false");
    if ("on".equalsIgnoreCase(property) || "yes".equalsIgnoreCase(property))
      property = "true";
    JapidTemplateBaseWithoutPlay.globalTraceFileJson = new Boolean(property);
  }

  @Override
  public void onConfigurationRead() {
    // appPath = Play.configuration.getProperty("context", "/");
  }

  public static Map<String, Object> getCache() {
    return japidCache;
  }

  /**
   *
   */
  private void getDumpRequest() {
    String property = Play.configuration.getProperty("japid.dump.request");
    this.dumpRequest = property;
  }

  @Override
  public void beforeDetectingChanges() {
//    logDuration("beforeDetectingChanges");
    // have a delay in change detection.
    if (System.currentTimeMillis() - lastTimeChecked.get() < 1000)
      return;
    try {
      List<File> changed = JapidCommands.reloadChanged();
      if (changed.size() > 0) {
        for (File f : changed) {
          // System.out.println("pre-detect changed: " + f.getName());
        }
      }
    } catch (JapidCompilationException e) {
      // turn japid compilation error to Play's template error to get
      // better error reporting
      JapidPlayTemplate jpt = new JapidPlayTemplate();
      jpt.name = e.getTemplateName();
      jpt.source = e.getTemplateSrc();
      // throw new TemplateExecutionException(jpt, e.getLineNumber(),
      // e.getMessage(), e);
      VirtualFile vf = VirtualFile.fromRelativePath("/app/" + e.getTemplateName());
      throw new CompilationException(vf, "\"" + e.getMessage() + "\"", e.getLineNumber(), 0, 0);
    } catch (RuntimeException e) {
      // TODO Auto-generated catch block
      // e.printStackTrace();
      throw e;
    }

    boolean hasRealOrphan = JapidCommands.rmOrphanJava();
    lastTimeChecked.set(System.currentTimeMillis());

    if (hasRealOrphan) {
      // a little messy here. clean the cache in case bad files are delete
      // remove all the existing ApplicationClass will reload everything.
      // ideally we just need to remove the orphan. But the internal cache
      // is not visible. Need API change to do that.
      Play.classes.clear();
      throw new RuntimeException("found orphan template Java artifacts. reload to be safe.");
    }
  }

  // // VirtualFile appRoot = VirtualFile.open(Play.applicationPath);
  // TranslateTemplateTask t = new TranslateTemplateTask();
  // {
  // Project proj = new Project();
  // t.setProject(proj);
  // proj.init();
  //
  // t.setSrcdir(new File("app"));
  // t.setIncludes(JAPIDVIEWS_ROOT + "/**/*.html");
  // t.importStatic(JapidPlayAdapter.class);
  // t.importStatic(Validation.class);
  // t.importStatic(JavaExtensions.class);
  // t.addAnnotation(NoEnhance.class);
  // t.addImport(JAPIDVIEWS_ROOT + "._layouts.*");
  // t.addImport(JAPIDVIEWS_ROOT + "._tags.*");
  // t.addImport("models.*");
  //
  // // t.add(new ModifiedSelector());
  // t.setTaskType("foo");
  // t.setTaskName("foo");
  // t.setOwningTarget(new Target());
  // }

  @Override
  public void onApplicationReady() {
    // appPath = Play.ctxPath; // won't work due to a bug in the servlet
    // wrapper. see onRoutesLoaded()
    if (Play.mode == Mode.PROD) {
      JapidPlayRenderer.init();
    }
  }

  @Override
  public void onApplicationStop() {
    try {

    } catch (Exception e) {
      throw new UnexpectedException(e);
    }
  }

  /**
   * just before an action is invoked. See {@code ActionInvoker}
   */
  @Override
  public void beforeActionInvocation(Method actionMethod) {
//    logDuration("beforeActionInvocation");
   
    final String actionName = actionMethod.getDeclaringClass().getName() + "." + actionMethod.getName();
    JapidController.threadData.get().put(ACTION_METHOD, actionName);

    String property = Play.configuration.getProperty("japid.dump.request");
    if (property != null && property.length() > 0) {
      if (!"false".equals(property) && !"no".equals(property)) {
        if ("yes".equals(property) || "true".equals(property)) {
          System.out.println("--- action ->: " + Request.current().action);
        } else if (Request.current().url.matches(property)) {
          System.out.println("--- action ->: " + Request.current().action);
        }
      }
    }

    String string = Flash.current().get(RenderResultCache.READ_THRU_FLASH);
    if (string != null) {
      RenderResultCache.setIgnoreCache(true);
    } else {
      // cache-control in lower case, lowercase for some reason
      Header header = Request.current().headers.get("cache-control");
      if (header != null) {
        List<String> list = header.values;
        if (list.contains(NO_CACHE)) {
          RenderResultCache.setIgnoreCache(true);
        }
      } else {
        header = Request.current().headers.get("pragma");
        if (header != null) {
          List<String> list = header.values;
          if (list.contains(NO_CACHE)) {
            RenderResultCache.setIgnoreCache(true);
          }
        } else {
          // just in case
          RenderResultCache.setIgnoreCacheInCurrentAndNextReq(false);
        }
      }
    }

    // // shortcut
    // String path = Request.current().path;
    // // System.out.println("request path:" + path);
    // Matcher matcher = renderJapidWithPattern.matcher(path);
    // if (matcher.matches()) {
    // String template = matcher.group(1);
    // JapidController.renderJapidWith(template);
    // }
  }

//  private void logDuration(String where) {
//    Request current = Request.current();
//    if (current == null)
//      return;
//    Map<String, Object> args = current.args;
//    Object whenRaw = args.get(WHEN_REQ_IN);
//    if (whenRaw != null && whenRaw instanceof Long) {
//      long start = (Long)whenRaw;
//      long now = System.nanoTime();
//      String timeBeforeAction = StringUtils.durationInMsFromNanos(start, now);
//      JapidFlags.debug("Time required for the request to reach " + where + ": " + timeBeforeAction + "ms.");
//    }
//  }

  String dumpRequest = null;
  private ArrayList<Route> recentAddedRoutes;
  private String ctxPath;

  @Override
  public boolean rawInvocation(Request req, Response response) throws Exception {
    req.args.put(WHEN_REQ_IN, System.nanoTime());

    Mode mode = Play.mode;
    if (mode == Mode.DEV) {
      String path = req.path;
      if (path.endsWith("_japidgen")) {
        try {
          beforeDetectingChanges();
          JapidPlayRenderer.gen();
        } catch (Exception e) {
        }
        response.out.write("OK".getBytes());
        return true;
      } else if (path.endsWith("_japidregen")) {
        try {
          JapidCommands.regen();
          JapidPlayRenderer.regen();
        } catch (Exception e) {
        }
        response.out.write("OK".getBytes());
        return true;
      }
    }

    if (dumpRequest != null && dumpRequest.length() > 0) {
      if ("yes".equals(dumpRequest) || "true".equals(dumpRequest)) {
        System.out.println("---->>" + req.method + " : " + req.url + " [" + req.action + "]");
        // System.out.println("request.controller:" +
        // current.controller);
      } else if ("false".equals(dumpRequest) || "no".equals(dumpRequest)) {
        // do nothing
      } else if (req.url.matches(dumpRequest)) {
        String querystring = req.querystring;
        if (querystring != null && querystring.length() > 0)
          querystring = "?" + querystring;
        else
          querystring = "";
        System.out.println("---->>" + req.method + " : " + req.url + querystring);
        String contentType = req.contentType;
        if (contentType == null || contentType.length() == 0) {
          contentType = "";
        }

        for (String k : req.headers.keySet()) {
          Header h = req.headers.get(k);
          System.out.println("... " + h.name + ":" + URLDecoder.decode(h.value(), "utf-8"));
        }
        // cookie is already in the headers
        // for (String ck : req.cookies.keySet()) {
        // Cookie c = req.cookies.get(ck);
        // System.out.println("... cookie --> " + c.name + ":" +
        // c.value);
        // }
        if ("POST".equals(req.method)) {
          if (contentType.contains("application/x-www-form-urlencoded")) {
            dumpReqBody(req, true);
          } else if (contentType.startsWith("text")) {
            dumpReqBody(req, false);
          } else if (contentType.contains("multipart/form-data")) {
            // cannot dump it, since it may contain binary
          } else if (contentType.contains("xml")) {
            dumpReqBody(req, false);
          } else if (contentType.contains("javascript")) {
            dumpReqBody(req, false);
          }
        }
      }

    }

    return false;

  }

  /**
   * @param req
   * @throws IOException
   * @throws UnsupportedEncodingException
   */
  private void dumpReqBody(Request req, boolean urlDecode) throws IOException, UnsupportedEncodingException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    InputStream bodystream = req.body;
    int r = bodystream.read();
    while (r > -1) {
      bos.write(r);
      r = bodystream.read();
    }

    bodystream.close();

    String body = bos.toString("UTF-8");
    if (urlDecode)
      body = URLDecoder.decode(body, "UTF-8");
    System.out.println("... body -> " + body);
    req.body = new ByteArrayInputStream(bos.toByteArray());
  }

  /**
   * this takes place before the flash and session save in the ActionInvoker
   */
  @Override
  public void onActionInvocationResult(Result result) {
//    logDuration("onActionInvocationResult");
    Flash fl = Flash.current();
    if (RenderResultCache.shouldIgnoreCacheInCurrentAndNextReq()) {
      fl.put(RenderResultCache.READ_THRU_FLASH, "yes");
    } else {
      fl.remove(RenderResultCache.READ_THRU_FLASH);
      fl.discard(RenderResultCache.READ_THRU_FLASH);
    }

    // always reset the flag since the thread may be reused for another
    // request processing
    RenderResultCache.setIgnoreCacheInCurrentAndNextReq(false);
  }

  /**
   * right after an action is invoked. See {@code ActionInvoker} Currently
   * this code has no effect to the flash due to a bug in ActionInvoker where
   * Flash save its state to cookie.
   */
  @Override
  public void afterActionInvocation() {
//    logDuration("afterActionInvocation");
    JapidController.threadData.get().remove(ACTION_METHOD);
  }

  @Override
  public void detectChange() {
//    logDuration("detectChange");
  }

  /**
   * this thing happens very early in the cycle. Neither Session nore Flash is
   * restored yet.
   */
  @Override
  public void beforeInvocation() {
//    logDuration("beforeInvocation");
  }

  @Override
  public void afterApplicationStart() {
//    logDuration("afterApplicationStart");
    getDumpRequest();
    setupInjectTemplateBorder();
  }

  @Override
  public void onApplicationStart() {
    JapidFlags.debug("JapidPlugin: clean japidCache");
    japidCache.clear();
    buildRoutesFromAnnotations();
  }

  @Override
  public void onEvent(String message, Object context) {

  }

  /**
   * intercept a special url that renders a Japid template without going thru
   * a controller.
   *
   * The url format: (anything)/renderJapidWith/(template path from japidview,
   * not included) e.g.
   *
   * http://localhost:9000/renderJapidWith/templates/callPicka
   *
   * will render the template "templates/callPicka.html" in the japidview
   * package in the app dir.
   *
   * TODO: add parameter support.
   */
  @Override
  public void routeRequest(Request request) {
//    logDuration("routeRequest");
    if (Play.mode == Mode.DEV) {
      buildRoutesFromAnnotations();
    }

    if (request.path.endsWith("_listroutes")) {
      List<Route> routes = Router.routes;
      for (Route r : routes) {
        JapidFlags.info(r.toString());
      }
    }
  }

  /**
   * auto routes gave higher priority in the route table.
   *
   * @author Bing Ran (bing.ran@gmail.com)
   */
  private void buildRoutesFromAnnotations() {
    if (!Play.classloader.currentState.equals(lastApplicationClassloaderState)) {
      JapidFlags.debug("reload auto route due to classloader state change");
      buildRoutes();
    } else if (Router.lastLoading != routesLoadingTime) {
      JapidFlags.debug("reload auto route due to router timestamp");
      buildRoutes();
    }
    this.ctxPath = Play.ctxPath;
    lastApplicationClassloaderState = Play.classloader.currentState;
    routesLoadingTime = Router.lastLoading;
  }

  private void buildRoutes() {

    List<Route> oldRoutes = Router.routes;
    List<Route> newRoutes = new ArrayList<Route>(oldRoutes.size());

    if (this.lastApplicationClassloaderState == Play.classloader.currentState && recentAddedRoutes != null
        && this.ctxPath == Play.ctxPath) {
      JapidFlags.debug("classloader state not changed. Use cached auto-routes.");
      newRoutes = new ArrayList<Route>(recentAddedRoutes);
    } else {
      // rebuild the dynamic route table
      long start = System.nanoTime();
      boolean ctxChanged = this.ctxPath != Play.ctxPath;

      for (ApplicationClass ac : Play.classes.all()) {
        // check cache
        RouterClass r = routerCache.get(ac.name);
        if (r != null && !ctxChanged && r.matchHash(ac.sigChecksum)) {
          // no change
          // JapidFlags.debug(ac.name + " has not changed. ");
        } else {
          Class<?> javaClass = ac.javaClass;
          if (javaClass != null 
              && javaClass.getName().startsWith("controllers.")
              && javaClass.getAnnotation(AutoPath.class) != null) {
            JapidFlags.debug("generate route for: " + ac.name);
            r = new RouterClass(javaClass, appPath, ac.sigChecksum);
          } else
            continue;
        }

        this.routerCache.put(ac.name, r);
        newRoutes.addAll(r.buildRoutes());
      }
      //
      JapidFlags.debug("rebuilding auto paths took(/ms): " + StringUtils.durationInMsFromNanos(start, System.nanoTime()));

      this.recentAddedRoutes = new ArrayList<Route>(newRoutes);
      this.lastApplicationClassloaderState = Play.classloader.currentState;
    }

    // copy fixed routes from the old route
    for (Iterator<Route> iterator = oldRoutes.iterator(); iterator.hasNext();) {
      Route r = iterator.next();
      if (r.routesFileLine != RouterMethod.AUTO_ROUTE_LINE) {
        newRoutes.add(r);
      }
    }
    Router.routes = newRoutes;
  }

  private ApplicationClassloaderState lastApplicationClassloaderState = null;
  private long routesLoadingTime = 0;
  private Map<String, RouterClass> routerCache = new HashMap<String, RouterClass>();

  private static Pattern renderJapidWithPattern = Pattern.compile(".*" + RENDER_JAPID_WITH + "/(.+)");

  /**
   * on tomcat this is the sure way to get the ctxPath from Play. The servlet
   * wrapper is the culprit.
   */
  @Override
  public void onRoutesLoaded() {
    // if (!Play.standalonePlayServer) {
    appPath = Play.ctxPath;
    // System.out.println("reload auto route due to system route loaded.");
    buildRoutes();
    lastApplicationClassloaderState = Play.classloader.currentState;
    routesLoadingTime = Router.lastLoading;
    // }
  }

  /*
   * (non-Javadoc)
   *
   * @see play.PlayPlugin#onInvocationException(java.lang.Throwable)
   */
  @Override
  public void onInvocationException(Throwable e) {

  }

//  /* (non-Javadoc)
//   * @see play.PlayPlugin#loadTemplate(play.vfs.VirtualFile)
//   */
//  @Override
//  public Template loadTemplate(VirtualFile file) {
//    Template t = new JapidPlayTemplate() {
//      @Override
//      protected String internalRender(Map<String, Object> args) {
//        // TODO Auto-generated method stub
//        return super.internalRender(args);
//      }
//    };
//  }
}
TOP

Related Classes of cn.bran.play.JapidPlugin

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.