/** Representing a class that might change. */
public class JClassDependency implements PersistentDependency {
  private static final Logger log = Log.open(JClassDependency.class);

  private final String _className;

  private boolean _checkFields = true;
  private boolean _checkStatic = true;
  private boolean _checkProtected = true;
  private boolean _checkPrivate = true;

  private boolean _isDigestModified;

  /** Creates the class dependency. */
  public JClassDependency(JClass cl) {
    _className = cl.getName();
  }

  /**
   * Create a new dependency with a given digest.
   *
   * @param cl the source class
   * @param digest the MD5 digest
   */
  public JClassDependency(JClass cl, String digest) {
    _className = cl.getName();

    String newDigest = getDigest();

    if (!newDigest.equals(digest)) {
      if (log.isLoggable(Level.FINE)) log.fine(_className + " digest is modified.");

      _isDigestModified = true;
    }
  }

  /**
   * Create a new dependency with a given digest.
   *
   * @param cl the source class
   * @param digest the MD5 digest
   */
  public JClassDependency(String className, String digest) {
    _className = className;

    String newDigest = getDigest();

    if (!newDigest.equals(digest)) {
      if (log.isLoggable(Level.FINE)) log.fine(_className + " digest is modified.");

      _isDigestModified = true;
    }
  }

  /** Returns true if the underlying resource has changed. */
  public boolean isModified() {
    return _isDigestModified;
  }

  /** Log the reason for modification */
  public boolean logModified(Logger log) {
    if (isModified()) {
      log.info(_className + " is modified");
      return true;
    } else return false;
  }

  /** Calculates a MD5 digest of the class. */
  public String getDigest() {
    try {
      if (_className == null || "".equals(_className)) return "";

      DynamicClassLoader loader =
          (DynamicClassLoader) Thread.currentThread().getContextClassLoader();

      ClassLoader tmpLoader = loader.getNewTempClassLoader();

      Class cl = Class.forName(_className, false, tmpLoader);

      if (cl == null) return "";

      MessageDigest digest = MessageDigest.getInstance("MD5");

      addDigest(digest, cl.getName());

      addDigest(digest, cl.getModifiers());

      Class superClass = cl.getSuperclass();
      if (superClass != null) addDigest(digest, superClass.getName());

      Class[] interfaces = cl.getInterfaces();
      for (int i = 0; i < interfaces.length; i++) addDigest(digest, interfaces[i].getName());

      Field[] fields = cl.getDeclaredFields();

      Arrays.sort(fields, new FieldComparator());

      if (_checkFields) {
        for (Field field : fields) {
          if (Modifier.isPrivate(field.getModifiers()) && !_checkPrivate) continue;
          if (Modifier.isProtected(field.getModifiers()) && !_checkProtected) continue;

          addDigest(digest, field.getName());
          addDigest(digest, field.getModifiers());
          addDigest(digest, field.getType().getName());

          addDigest(digest, field.getAnnotations());
        }
      }

      Method[] methods = cl.getDeclaredMethods();
      Arrays.sort(methods, new MethodComparator());

      for (int i = 0; i < methods.length; i++) {
        Method method = methods[i];

        if (Modifier.isPrivate(method.getModifiers()) && !_checkPrivate) continue;
        if (Modifier.isProtected(method.getModifiers()) && !_checkProtected) continue;
        if (Modifier.isStatic(method.getModifiers()) && !_checkStatic) continue;

        addDigest(digest, method.getName());
        addDigest(digest, method.getModifiers());
        addDigest(digest, method.getName());

        Class[] param = method.getParameterTypes();
        for (int j = 0; j < param.length; j++) addDigest(digest, param[j].getName());

        addDigest(digest, method.getReturnType().getName());

        Class[] exn = method.getExceptionTypes();
        for (int j = 0; j < exn.length; j++) addDigest(digest, exn[j].getName());

        addDigest(digest, method.getAnnotations());
      }

      byte[] digestBytes = new byte[256];

      int len = digest.digest(digestBytes, 0, digestBytes.length);

      return digestToBase64(digestBytes, len);
    } catch (Exception e) {
      log.log(Level.FINER, e.toString(), e);

      return "";
    }
  }

  /** Returns a string which will recreate the dependency. */
  public String getJavaCreateString() {
    return ("new com.caucho.bytecode.JClassDependency(\""
        + _className
        + "\", \""
        + getDigest()
        + "\")");
  }

  /** Adds the annotations to the digest using a UTF8 encoding. */
  private static void addDigest(MessageDigest digest, Annotation[] annList) {
    if (annList == null) return;

    for (Annotation ann : annList) addDigest(digest, ann);
  }

  /** Adds the annotations to the digest using a UTF8 encoding. */
  private static void addDigest(MessageDigest digest, Annotation ann) {
    addDigest(digest, ann.annotationType().getName());
  }

  /** Adds the int to the digest. */
  private static void addDigest(MessageDigest digest, int v) {
    digest.update((byte) (v >> 24));
    digest.update((byte) (v >> 16));
    digest.update((byte) (v >> 8));
    digest.update((byte) v);
  }

  /** Adds the string to the digest using a UTF8 encoding. */
  private static void addDigest(MessageDigest digest, String string) {
    if (string == null) return;

    int len = string.length();
    for (int i = 0; i < len; i++) {
      int ch = string.charAt(i);
      if (ch < 0x80) digest.update((byte) ch);
      else if (ch < 0x800) {
        digest.update((byte) (0xc0 + (ch >> 6)));
        digest.update((byte) (0x80 + (ch & 0x3f)));
      } else {
        digest.update((byte) (0xe0 + (ch >> 12)));
        digest.update((byte) (0x80 + ((ch >> 6) & 0x3f)));
        digest.update((byte) (0x80 + (ch & 0x3f)));
      }
    }
  }

  private String digestToBase64(byte[] digest, int len) {
    CharBuffer cb = CharBuffer.allocate();

    Base64.encode(cb, digest, 0, len);

    return cb.close();
  }

  public boolean isEqual(Object o) {
    if (o == this) return true;

    if (!(o instanceof JClassDependency)) return false;

    JClassDependency depend = (JClassDependency) o;

    return _className.equals(depend._className);
  }

  static class FieldComparator implements Comparator<Field> {
    public int compare(Field a, Field b) {
      if (a == b) return 0;
      else if (a == null) return -1;
      else if (b == null) return 1;
      else if (a.equals(b)) return 0;

      int cmp = a.getName().compareTo(b.getName());
      if (cmp != 0) return cmp;

      cmp = a.getDeclaringClass().getName().compareTo(b.getDeclaringClass().getName());
      if (cmp != 0) return cmp;

      return a.getType().getName().compareTo(b.getType().getName());
    }
  }

  static class MethodComparator implements Comparator<Method> {
    public int compare(Method a, Method b) {
      if (a == b) return 0;
      else if (a == null) return -1;
      else if (b == null) return 1;
      else if (a.equals(b)) return 0;

      int cmp = a.getName().compareTo(b.getName());
      if (cmp != 0) return cmp;

      Class[] paramA = a.getParameterTypes();
      Class[] paramB = b.getParameterTypes();

      if (paramA.length < paramB.length) return -1;
      else if (paramB.length < paramA.length) return 1;

      for (int i = 0; i < paramA.length; i++) {
        cmp = paramA[i].getName().compareTo(paramB[i].getName());
        if (cmp != 0) return cmp;
      }

      cmp = a.getDeclaringClass().getName().compareTo(b.getDeclaringClass().getName());
      if (cmp != 0) return cmp;

      return a.getReturnType().getName().compareTo(b.getReturnType().getName());
    }
  }
}
Exemple #2
0
/** Manages the enhancement */
@Module
public class EnhancerManager implements ClassFileTransformer {
  private static final L10N L = new L10N(EnhancerManager.class);
  private static final Logger log = Log.open(EnhancerManager.class);

  private static EnvironmentLocal<EnhancerManager> _localEnhancer =
      new EnvironmentLocal<EnhancerManager>();

  private DynamicClassLoader _loader;

  private Path _workPath;

  private JavaClassLoader _jClassLoader = new JavaClassLoader();
  private JavaClassGenerator _javaGen = new JavaClassGenerator();

  private ArrayList<ClassEnhancer> _classEnhancerList = new ArrayList<ClassEnhancer>();

  private EnhancerManager(ClassLoader loader) {
    for (;
        loader != null && !(loader instanceof DynamicClassLoader);
        loader = loader.getParent()) {}

    _loader = (DynamicClassLoader) loader;

    if (loader != null) getLocalEnhancer(loader.getParent());
  }

  public static EnhancerManager create() {
    return create(Thread.currentThread().getContextClassLoader());
  }

  public static EnhancerManager create(ClassLoader loader) {
    EnhancerManager enhancer = _localEnhancer.getLevel(loader);

    if (enhancer == null) {
      enhancer = new EnhancerManager(loader);
      _localEnhancer.set(enhancer, loader);

      for (; loader != null; loader = loader.getParent()) {
        if (loader instanceof DynamicClassLoader) {
          ((DynamicClassLoader) loader).addTransformer(enhancer);
          break;
        }
      }
    }

    return enhancer;
  }

  public static EnhancerManager getLocalEnhancer(ClassLoader loader) {
    return _localEnhancer.get(loader);
  }

  /** Returns the JClassLoader. */
  public JavaClassLoader getJavaClassLoader() {
    return _jClassLoader;
  }

  /** Gets the work path. */
  public Path getWorkPath() {
    if (_workPath != null) return _workPath;
    else return WorkDir.getLocalWorkDir(_loader);
  }

  /** Sets the work path. */
  public void setWorkPath(Path workPath) {
    _workPath = workPath;
  }

  /** Gets the work path. */
  public final Path getPreWorkPath() {
    return getWorkPath().lookup("pre-enhance");
  }

  /** Gets the work path. */
  public final Path getPostWorkPath() {
    return getWorkPath().lookup("post-enhance");
  }

  /** Adds a class enhancer. */
  public void addClassEnhancer(ClassEnhancer classEnhancer) {
    _classEnhancerList.add(classEnhancer);
  }

  /** Returns the enhanced .class or null if no enhancement. */
  public byte[] transform(
      ClassLoader loader,
      String className,
      Class<?> oldClass,
      ProtectionDomain domain,
      byte[] buffer) {
    if (isClassMatch(className)) {
      try {
        ClassLoader tempLoader = ((DynamicClassLoader) loader).getNewTempClassLoader();
        DynamicClassLoader workLoader = SimpleLoader.create(tempLoader, getPostWorkPath());
        workLoader.setServletHack(true);
        boolean isModified = true;

        Thread thread = Thread.currentThread();
        ClassLoader oldLoader = thread.getContextClassLoader();

        try {
          Class<?> cl = Class.forName(className.replace('/', '.'), false, workLoader);

          thread.setContextClassLoader(tempLoader);

          Method init = cl.getMethod("_caucho_init", new Class[] {Path.class});
          Method modified = cl.getMethod("_caucho_is_modified", new Class[0]);

          init.invoke(null, Vfs.lookup());

          isModified = (Boolean) modified.invoke(null);
        } catch (Exception e) {
          log.log(Level.FINEST, e.toString(), e);
        } catch (Throwable e) {
          log.log(Level.FINER, e.toString(), e);
        } finally {
          thread.setContextClassLoader(oldLoader);
        }

        if (!isModified) {
          try {
            return load(className);
          } catch (Exception e) {
            log.log(Level.FINER, e.toString(), e);
          }
        }

        ByteCodeParser parser = new ByteCodeParser();
        parser.setClassLoader(_jClassLoader);

        ByteArrayInputStream is;
        is = new ByteArrayInputStream(buffer, 0, buffer.length);

        JavaClass jClass = parser.parse(is);

        return enhance(jClass);
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw new EnhancerRuntimeException(e);
      }
    }

    return null;
  }

  /** Enhances the given class. */
  public byte[] enhance(JClass jClass) throws ClassNotFoundException {
    String className = jClass.getName().replace('/', '.');
    String extClassName = className + "__ResinExt";

    try {
      EnhancerPrepare prepare = new EnhancerPrepare();
      prepare.setWorkPath(getWorkPath());
      prepare.setClassLoader(_loader);

      for (ClassEnhancer enhancer : _classEnhancerList) {
        if (enhancer.shouldEnhance(className)) {
          prepare.addEnhancer(enhancer);
        }
      }

      // prepare.renameClass(className, extClassName);
      prepare.renameClass(className, className);
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);

      throw new ClassNotFoundException(e.toString());
    }

    boolean hasEnhancer = false;
    GenClass genClass = new GenClass(extClassName);
    genClass.setSuperClassName(className);
    for (ClassEnhancer enhancer : _classEnhancerList) {
      if (enhancer.shouldEnhance(className)) {
        try {
          hasEnhancer = true;
          enhancer.enhance(genClass, jClass, extClassName);
        } catch (RuntimeException e) {
          throw e;
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    }
    // XXX: class-wide enhancements need to go first

    try {
      if (hasEnhancer) {
        _javaGen.setWorkDir(getPreWorkPath());
        _javaGen.generate(genClass);
        _javaGen.compilePendingJava();
      }

      EnhancerFixup fixup = new EnhancerFixup();
      fixup.setJavaClassLoader(_jClassLoader);
      fixup.setClassLoader(_loader);
      fixup.setWorkPath(getWorkPath());

      for (ClassEnhancer enhancer : _classEnhancerList) {
        if (enhancer.shouldEnhance(className)) {
          fixup.addEnhancer(enhancer);
        }
      }

      fixup.fixup(className, extClassName);

      return load(className);
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);

      throw new ClassNotFoundException(e.getMessage());
    }

    // return null;
  }

  private byte[] load(String className) throws IOException {
    Path path = getPostWorkPath().lookup(className.replace('.', '/') + ".class");
    int length = (int) path.getLength();

    if (length < 0)
      throw new FileNotFoundException(L.l("Can't find class file '{0}'", path.getNativePath()));

    byte[] buffer = new byte[length];

    ReadStream is = path.openRead();
    try {
      is.readAll(buffer, 0, buffer.length);
    } finally {
      is.close();
    }

    return buffer;
  }

  /** Returns true for a matching class. */
  public boolean isClassMatch(String className) {
    if (className.lastIndexOf('$') >= 0) {
      int p = className.lastIndexOf('$');
      char ch = 0;

      if (p + 1 < className.length()) ch = className.charAt(p + 1);

      if ('0' <= ch && ch <= '9') return false;
    } else if (className.indexOf('+') > 0 || className.indexOf('-') > 0) return false;

    for (int i = 0; i < _classEnhancerList.size(); i++) {
      if (_classEnhancerList.get(i).shouldEnhance(className)) {
        return true;
      }
    }

    return false;
  }

  public String toString() {
    return getClass().getSimpleName() + "[" + _classEnhancerList + "]";
  }
}
/** Abstract socket to handle both normal sockets and bin/resin sockets. */
public class QJniServerSocket {
  private static final L10N L = new L10N(QJniServerSocket.class);
  private static final Logger log = Log.open(QJniServerSocket.class);

  private QJniServerSocket() {}

  /** Creates the SSL ServerSocket. */
  public static QServerSocket create(int port, int listenBacklog) throws IOException {
    return create(null, port, listenBacklog, true);
  }

  public static QServerSocket create(InetAddress host, int port, int listenBacklog)
      throws IOException {
    return create(host, port, listenBacklog, true);
  }

  /** Creates the SSL ServerSocket. */
  public static QServerSocket create(
      InetAddress host, int port, int listenBacklog, boolean isEnableJni) throws IOException {
    if (isEnableJni) {
      try {
        // JNI doesn't listen immediately
        QServerSocket ss = createJNI(host, port);

        if (ss != null) return ss;
      } catch (IOException e) {
        log.log(Level.FINE, e.toString(), e);
      } catch (Throwable e) {
        log.log(Level.FINE, e.toString(), e);
      }
    }

    for (int i = 0; i < 10; i++) {
      try {
        ServerSocket ss = new ServerSocket(port, listenBacklog, host);

        return new QServerSocketWrapper(ss);
      } catch (BindException e) {
      }

      try {
        Thread.currentThread().sleep(1);
      } catch (Throwable e) {
      }
    }

    try {
      ServerSocket ss = new ServerSocket(port, listenBacklog, host);

      return new QServerSocketWrapper(ss);
    } catch (BindException e) {
      if (host != null)
        throw new BindException(
            L.l(
                "{0}\nCan't bind to {1}:{2}.\nCheck for another server listening to that port.",
                e.getMessage(), host, String.valueOf(port)));
      else
        throw new BindException(
            L.l(
                "{0}\nCan't bind to *:{1}.\nCheck for another server listening to that port.",
                e.getMessage(), String.valueOf(port)));
    }
  }

  /** Creates the SSL ServerSocket. */
  public static QServerSocket createJNI(InetAddress host, int port) throws IOException {
    try {
      Thread thread = Thread.currentThread();
      ClassLoader loader = thread.getContextClassLoader();

      Class cl = Class.forName("com.caucho.vfs.JniServerSocketImpl", false, loader);

      Method method = cl.getMethod("create", new Class[] {String.class, int.class});

      String hostAddress;

      if (host != null) hostAddress = host.getHostAddress();
      else {
        hostAddress = null;
      }

      try {
        return (QServerSocket) method.invoke(null, hostAddress, port);
      } catch (InvocationTargetException e) {
        throw e.getTargetException();
      }
    } catch (IOException e) {
      throw e;
    } catch (ClassNotFoundException e) {
      log.fine(e.toString());

      throw new IOException(L.l("JNI Socket support requires Resin Professional."));
    } catch (Throwable e) {
      log.log(Level.FINE, e.toString(), e);

      throw new IOException(L.l("JNI Socket support requires Resin Professional."));
    }
  }

  /** Creates the SSL ServerSocket. */
  public static QServerSocket openJNI(int fd, int port) throws IOException {
    try {
      Class cl = Class.forName("com.caucho.vfs.JniServerSocketImpl");

      Method method = cl.getMethod("open", new Class[] {int.class, int.class});

      try {
        return (QServerSocket) method.invoke(null, fd, port);
      } catch (InvocationTargetException e) {
        throw e.getTargetException();
      }
    } catch (IOException e) {
      throw e;
    } catch (ClassNotFoundException e) {
      log.fine(e.toString());

      throw new IOException(L.l("JNI Socket support requires Resin Professional."));
    } catch (Throwable e) {
      log.log(Level.FINE, e.toString(), e);

      throw new IOException(L.l("JNI Socket support requires Resin Professional."));
    }
  }
}