/** 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()); } } }
/** 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.")); } } }