You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by db...@apache.org on 2008/09/03 04:19:40 UTC

svn commit: r691473 - in /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb: config/DeploymentLoader.java util/AnnotationFinder.java

Author: dblevins
Date: Tue Sep  2 19:19:39 2008
New Revision: 691473

URL: http://svn.apache.org/viewvc?rev=691473&view=rev
Log:
Highly optimized scanner for determining if a jar is an ejb jar and exiting as quickly as possible

Added:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/AnnotationFinder.java
Modified:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/DeploymentLoader.java

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/DeploymentLoader.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/DeploymentLoader.java?rev=691473&r1=691472&r2=691473&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/DeploymentLoader.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/config/DeploymentLoader.java Tue Sep  2 19:19:39 2008
@@ -68,7 +68,7 @@
 import org.apache.openejb.util.URLs;
 import org.apache.openejb.util.UrlCache;
 import static org.apache.openejb.util.URLs.toFile;
-import org.apache.openejb.finder.ClassFinder;
+import org.apache.openejb.util.AnnotationFinder;
 import org.apache.openejb.finder.ResourceFinder;
 import org.apache.openejb.finder.UrlSet;
 import org.xml.sax.SAXException;
@@ -1073,11 +1073,21 @@
         }
 
         if (searchForDescriptorlessApplications) {
-            ClassFinder classFinder = new ClassFinder(classLoader, baseUrl);
+            AnnotationFinder classFinder = new AnnotationFinder(classLoader, baseUrl);
 
-            if (classFinder.isAnnotationPresent(Stateless.class) ||
-                    classFinder.isAnnotationPresent(Stateful.class) ||
-                    classFinder.isAnnotationPresent(MessageDriven.class)) {
+            AnnotationFinder.Filter filter = new AnnotationFinder.Filter() {
+                public boolean accept(String annotationName) {
+                    if (annotationName.startsWith("javax.ejb.")) {
+                        if ("javax.ejb.Stateful".equals(annotationName)) return true;
+                        if ("javax.ejb.Stateless".equals(annotationName)) return true;
+                        if ("javax.ejb.Singleton".equals(annotationName)) return true;
+                        if ("javax.ejb.MessageDriven".equals(annotationName)) return true;
+                    }
+                    return false;
+                }
+            };
+
+            if (classFinder.find(filter)) {
                 return EjbModule.class;
             }
         }

Added: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/AnnotationFinder.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/AnnotationFinder.java?rev=691473&view=auto
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/AnnotationFinder.java (added)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/util/AnnotationFinder.java Tue Sep  2 19:19:39 2008
@@ -0,0 +1,349 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.openejb.util;
+
+import org.apache.openejb.asm.ClassReader;
+import org.apache.openejb.asm.ClassVisitor;
+import org.apache.openejb.asm.Attribute;
+import org.apache.openejb.asm.FieldVisitor;
+import org.apache.openejb.asm.MethodVisitor;
+import org.apache.openejb.asm.AnnotationVisitor;
+import org.apache.openejb.finder.UrlSet;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarEntry;
+import java.net.URL;
+import java.net.JarURLConnection;
+import java.net.URLDecoder;
+import java.io.IOException;
+import java.io.File;
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+
+/**
+ * ClassFinder searches the classpath of the specified classloader for
+ * packages, classes, constructors, methods, or fields with specific annotations.
+ *
+ * For security reasons ASM is used to find the annotations.  Classes are not
+ * loaded unless they match the requirements of a called findAnnotated* method.
+ * Once loaded, these classes are cached.
+ *
+ * The getClassesNotLoaded() method can be used immediately after any find*
+ * method to get a list of classes which matched the find requirements (i.e.
+ * contained the annotation), but were unable to be loaded.
+ *
+ * @author David Blevins
+ * @version $Rev$ $Date$
+ */
+public class AnnotationFinder {
+
+    private final ClassLoader classLoader;
+    private final List<String> classesNotLoaded = new ArrayList<String>();
+    private final int ASM_FLAGS = ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES;
+    private final Collection<URL> urls;
+    private List<String> classNames;
+
+    /**
+     * Creates a ClassFinder that will search the urls in the specified classloader
+     * excluding the urls in the classloader's parent.
+     *
+     * To include the parent classloader, use:
+     *
+     *    new ClassFinder(classLoader, false);
+     *
+     * To exclude the parent's parent, use:
+     *
+     *    new ClassFinder(classLoader, classLoader.getParent().getParent());
+     *
+     * @param classLoader source of classes to scan
+     * @throws Exception if something goes wrong
+     */
+    public AnnotationFinder(ClassLoader classLoader) throws Exception {
+        this(classLoader, true);
+    }
+
+    /**
+     * Creates a ClassFinder that will search the urls in the specified classloader.
+     *
+     * @param classLoader source of classes to scan
+     * @param excludeParent Allegedly excludes classes from parent classloader, whatever that might mean
+     * @throws Exception if something goes wrong.
+     */
+    public AnnotationFinder(ClassLoader classLoader, boolean excludeParent) throws Exception {
+        this(classLoader, AnnotationFinder.getUrls(classLoader, excludeParent));
+    }
+
+    /**
+     * Creates a ClassFinder that will search the urls in the specified classloader excluding
+     * the urls in the 'exclude' classloader.
+     *
+     * @param classLoader source of classes to scan
+     * @param exclude source of classes to exclude from scanning
+     * @throws Exception if something goes wrong
+     */
+    public AnnotationFinder(ClassLoader classLoader, ClassLoader exclude) throws Exception {
+        this(classLoader, AnnotationFinder.getUrls(classLoader, exclude));
+    }
+
+    public AnnotationFinder(ClassLoader classLoader, URL url) {
+        this(classLoader, Arrays.asList(url));
+    }
+
+    public AnnotationFinder(ClassLoader classLoader, Collection<URL> urls) {
+        this.classLoader = classLoader;
+        this.urls = urls;
+        classNames = new ArrayList<String>();
+        for (URL location : urls) {
+            try {
+                if (location.getProtocol().equals("jar")) {
+                    classNames.addAll(jar(location));
+                } else if (location.getProtocol().equals("file")) {
+                    try {
+                        // See if it's actually a jar
+                        URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/");
+                        JarURLConnection juc = (JarURLConnection) jarUrl.openConnection();
+                        juc.getJarFile();
+                        classNames.addAll(jar(jarUrl));
+                    } catch (IOException e) {
+                        classNames.addAll(file(location));
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Returns a list of classes that could not be loaded in last invoked findAnnotated* method.
+     * <p/>
+     * The list will only contain entries of classes whose byte code matched the requirements
+     * of last invoked find* method, but were unable to be loaded and included in the results.
+     * <p/>
+     * The list returned is unmodifiable.  Once obtained, the returned list will be a live view of the
+     * results from the last findAnnotated* method call.
+     * <p/>
+     * This method is not thread safe.
+     * @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
+     */
+    public List<String> getClassesNotLoaded() {
+        return Collections.unmodifiableList(classesNotLoaded);
+    }
+
+    public boolean find(Filter filter){
+        Visitor annotationVisitor = new Visitor(filter);
+
+        for (String className : classNames) {
+            try {
+                readClassDef(className, annotationVisitor);
+            } catch (NotFoundException e) {
+            } catch (FoundException e) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public interface Filter {
+        boolean accept(String annotationName);
+    }
+
+    private static Collection<URL> getUrls(ClassLoader classLoader, boolean excludeParent) throws IOException {
+        return AnnotationFinder.getUrls(classLoader, excludeParent? classLoader.getParent() : null);
+    }
+
+    private static Collection<URL> getUrls(ClassLoader classLoader, ClassLoader excludeParent) throws IOException {
+        UrlSet urlSet = new UrlSet(classLoader);
+        if (excludeParent != null){
+            urlSet = urlSet.exclude(excludeParent);
+        }
+        return urlSet.getUrls();
+    }
+
+    private List<String> file(URL location) {
+        List<String> classNames = new ArrayList<String>();
+        File dir = new File(URLDecoder.decode(location.getPath()));
+        if (dir.getName().equals("META-INF")) {
+            dir = dir.getParentFile(); // Scrape "META-INF" off
+        }
+        if (dir.isDirectory()) {
+            scanDir(dir, classNames, "");
+        }
+        return classNames;
+    }
+
+    private void scanDir(File dir, List<String> classNames, String packageName) {
+        File[] files = dir.listFiles();
+        for (File file : files) {
+            if (file.isDirectory()) {
+                scanDir(file, classNames, packageName + file.getName() + ".");
+            } else if (file.getName().endsWith(".class")) {
+                String name = file.getName();
+                name = name.replaceFirst(".class$", "");
+                classNames.add(packageName + name);
+            }
+        }
+    }
+
+    private List<String> jar(URL location) throws IOException {
+        String jarPath = location.getFile();
+        if (jarPath.indexOf("!") > -1){
+            jarPath = jarPath.substring(0, jarPath.indexOf("!"));
+        }
+        URL url = new URL(jarPath);
+        InputStream in = url.openStream();
+        try {
+            JarInputStream jarStream = new JarInputStream(in);
+            return jar(jarStream);
+        } finally {
+            in.close();
+        }
+    }
+
+    private List<String> jar(JarInputStream jarStream) throws IOException {
+        List<String> classNames = new ArrayList<String>();
+
+        JarEntry entry;
+        while ((entry = jarStream.getNextJarEntry()) != null) {
+            if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
+                continue;
+            }
+            String className = entry.getName();
+            className = className.replaceFirst(".class$", "");
+            className = className.replace('/', '.');
+            classNames.add(className);
+        }
+
+        return classNames;
+    }
+
+    private void readClassDef(String className, ClassVisitor visitor) {
+        classes++;
+        if (!className.endsWith(".class")) {
+            className = className.replace('.', '/') + ".class";
+        }
+        try {
+            URL resource = classLoader.getResource(className);
+            if (resource != null) {
+                InputStream in = resource.openStream();
+//                in = new BufferedInputStream(in, 8192 / 4);
+                try {
+                    ClassReader classReader = new ClassReader(in);
+                    classReader.accept(visitor, ASM_FLAGS);
+                } finally {
+                    in.close();
+                }
+            } else {
+                new Exception("Could not load " + className).printStackTrace();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    public static int classes;
+
+    public static class NotFoundException extends RuntimeException {
+    }
+
+    public static class FoundException extends RuntimeException {
+    }
+
+
+    public class Visitor implements ClassVisitor {
+        private NotFoundException notFoundException;
+        private FoundException foundException;
+        private final Filter filter;
+
+        public Visitor(Filter filter) {
+            this.filter = filter;
+
+            try {
+                throw new NotFoundException();
+            } catch (NotFoundException e) {
+                notFoundException = e;
+            }
+
+            try {
+                throw new FoundException();
+            } catch (FoundException e) {
+                foundException = e;
+            }
+        }
+
+        public AnnotationVisitor visitAnnotation(String name, boolean visible) {
+            // annotation names show up as
+            // Ljavax.ejb.Stateless;
+            // so we hack of the first and last chars and replace the slashes
+            StringBuilder sb = new StringBuilder(name);
+            sb.deleteCharAt(0);
+            sb.deleteCharAt(sb.length()-1);
+            for (int i = 0; i < sb.length(); i++) {
+                if (sb.charAt(i) == '/'){
+                    sb.setCharAt(i, '.');
+                }
+            }
+
+            name = sb.toString();
+
+            if (filter.accept(name)){
+                throw foundException;
+            }
+            return null;
+        }
+
+
+        public void visit(int i, int i1, String string, String string1, String string2, String[] strings) {
+        }
+
+        public void visitSource(String string, String string1) {
+        }
+
+        public void visitOuterClass(String string, String string1, String string2) {
+        }
+
+        public void visitAttribute(Attribute attribute) {
+            throw notFoundException;
+        }
+
+        public void visitInnerClass(String string, String string1, String string2, int i) {
+            throw notFoundException;
+        }
+
+        public FieldVisitor visitField(int i, String string, String string1, String string2, Object object) {
+            throw notFoundException;
+        }
+
+        public MethodVisitor visitMethod(int i, String string, String string1, String string2, String[] strings) {
+            throw notFoundException;
+        }
+
+        public void visitEnd() {
+            throw notFoundException;
+        }
+
+    }
+
+
+
+}