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;
+ }
+
+ }
+
+
+
+}