Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Further Spring Boot and Class Loading fixes #282

Closed
wants to merge 17 commits into from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ bin/
.gitignore.swp
.faces-config.xml.jsfdia
docs/reference/src/main/docbook/en-US/version_info.xml
/config-prettyfaces/dependency-reduced-pom.xml
/config-tuckey/dependency-reduced-pom.xml
/transform-minify/dependency-reduced-pom.xml
6 changes: 6 additions & 0 deletions annotations-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,11 @@
<artifactId>rewrite-config-servlet</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ protected static String getClassName(String filename)
String relativePath = filename.substring(0, endIndex);

// replace / by . to create FQCN
return relativePath.replace('/', '.');
return relativePath.replace('/', '.').replace('\\', '.');
}

/**
Expand Down Expand Up @@ -196,7 +196,7 @@ protected void processClass(String className, InputStream classFileStream, Class
try
{
// request this class from the ClassLoader
Class<?> clazz = Class.forName(className, false, classLoader);
Class<?> clazz = classLoader.loadClass(className);

// call handler
visitor.visit(clazz);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,27 @@
*/
package org.ocpsoft.rewrite.annotation.scan;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;

import javax.servlet.ServletContext;

import org.ocpsoft.rewrite.annotation.ClassVisitorImpl;
import org.ocpsoft.rewrite.annotation.api.ClassVisitor;
import org.ocpsoft.rewrite.annotation.spi.ClassFinder;

/**
* Implementation of {@link ClassFinder} that searches for classes in the <code>/WEB-INF/classes</code> directory of a
* web application. Please note that this class is stateful. It should be used only for one call to
* {@link #findClasses(ClassVisitor)}.
* {@link #findClasses(ClassVisitorImpl)}.
*
* @author Christian Kaltepoth
*/
Expand All @@ -46,6 +51,10 @@ public class WebClassesFinder extends AbstractClassFinder
* Manage a set of classes already processed
*/
private final Set<String> processedClasses = new LinkedHashSet<String>();

public static final String CLASS_EXTENSION = ".class";
public static final int CLASS_EXTENSION_LENGTH = CLASS_EXTENSION.length();
public static final String META_INF = "META-INF";

/**
* Initialization
Expand All @@ -55,35 +64,102 @@ public WebClassesFinder(ServletContext servletContext, ClassLoader classLoader,
{
super(servletContext, classLoader, packageFilter, byteCodeFilter);
}

@Override
public void findClasses(ClassVisitor visitor)
{
try
{
// get the absolute URL of the classes folder
URL classesFolderUrl = servletContext.getResource(CLASSES_FOLDER);

// abort if classes folder is missing
if (classesFolderUrl == null)
{
log.warn("Cannot find classes folder: " + CLASSES_FOLDER);
return;
if (classesFolderUrl != null) {
// call recursive directory processing method
processDirectory(classesFolderUrl, CLASSES_FOLDER, visitor);
return;
}

// call recursive directory processing method
processDirectory(classesFolderUrl, CLASSES_FOLDER, visitor);

String main = System.getProperty("sun.java.command");
if (main.endsWith(".jar")) {
classesFolderUrl = Thread.currentThread().getContextClassLoader().getResource("");
} else {
classesFolderUrl = Class.forName(main).getProtectionDomain().getCodeSource().getLocation();
if ("file".equals(classesFolderUrl.getProtocol()) && classesFolderUrl.getPath().endsWith(".jar"))
classesFolderUrl = new URL("jar:" + classesFolderUrl.toExternalForm() + "!/");
}
if (classesFolderUrl != null) {
processUrl(visitor, classesFolderUrl);
return;
}

// abort if classes folder is missing
log.warn("Cannot find classes folder");
}
catch (MalformedURLException e)
catch (Exception e)
{
throw new IllegalStateException("Invalid URL: " + e.getMessage(), e);
}
}

public void processUrl(ClassVisitor visitor, URL url) {
try {
if ("file".equals(url.getProtocol())) {
File file = new File(url.getFile());
scanDir(visitor, file, file.getAbsolutePath().length() + 1);
} else if ("jar".equals(url.getProtocol()) || url.getPath().endsWith(".jar")) {
JarURLConnection connection = (JarURLConnection)url.openConnection();
scanJar(visitor, connection);
}
} catch (Exception exception) {
throw new IllegalArgumentException("Error scanning url '" + url.toExternalForm() + "'", exception);
}
}
private void scanDir(ClassVisitor visitor, File file, int prefix) throws Exception {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
scanDir(visitor, child, prefix);
}
} else if (file.getName().endsWith(CLASS_EXTENSION)) {
String className = getClassName(file.getAbsolutePath().substring(prefix));
handleClassUrl(visitor, file.toURI().toURL(), className);
}
}
private void scanJar(ClassVisitor visitor, JarURLConnection connection) throws Exception {
for (Enumeration<JarEntry> enumeration = connection.getJarFile().entries(); enumeration.hasMoreElements();) {
JarEntry entry = enumeration.nextElement();
if (!entry.isDirectory() && !entry.getName().startsWith(META_INF) &&
entry.getName().endsWith(CLASS_EXTENSION)) {
String className = getClassName(entry.getName());
URL url = new URL(connection.getURL(), entry.getName());
handleClassUrl(visitor, url, className);
}
}
}
private void handleClassUrl(ClassVisitor visitor, URL url, String className) {
if (mustProcessClass(className) && !processedClasses.contains(className)) {
processedClasses.add(className);
InputStream classFileStream = null;
try {
try {
classFileStream = url.openStream();
} catch (Exception e) {
if (log.isDebugEnabled())
log.debug("Cound not obtain InputStream for class: " + className, e);
}
processClass(className, classFileStream, visitor);
} finally {
try {
if (classFileStream != null)
classFileStream.close();
} catch (IOException e) {
if (log.isDebugEnabled())
log.debug("Failed to close input stream: " + e.getMessage());
}
}
}
}

/**
/**
* Scan for classes in a single directory. This method will call itself recursively if it finds other directories and
* call {@link #processClass(String, InputStream, ClassVisitor)} when it finds a file ending with ".class" and
* call {@link #processClass(String, InputStream, ClassVisitorImpl) when it finds a file ending with ".class" and
* that is accepted by the {@link PackageFilter}
*
* @param absoluteUrl The absolute URL of the WEB-INF node to scan
Expand Down Expand Up @@ -140,15 +216,15 @@ protected void processDirectory(URL absoluteUrl, String relativePath, ClassVisit
}
if (childNodeRelative.endsWith(".class"))
{
handleClassEntry(childNodeName, visitor);
handleClassEntry(childNodeUrl, childNodeName, visitor);
}
}
}

/**
* Handles class entry in a WEB-INF.
*/
private void handleClassEntry(String entryName, ClassVisitor visitor)
private void handleClassEntry(URL entryUrl, String entryName, ClassVisitor visitor)
{

// build class name from relative name
Expand All @@ -169,15 +245,17 @@ private void handleClassEntry(String entryName, ClassVisitor visitor)
{

/*
* Try to open the .class file. if this isn't possible, we will scan it anyway.
* Try to open the .class file. If an IOException is thrown, we will scan it anyway.
*/
classFileStream = servletContext.getResourceAsStream(entryName);

if (classFileStream == null)
try
{
classFileStream = servletContext.getResourceAsStream(entryName);
}
catch (Exception e)
{
if (log.isDebugEnabled())
{
log.debug("Could not obtain InputStream for class file: " + entryName);
log.debug("Could not obtain InputStream for class file: " + entryName, e);
}
}

Expand Down Expand Up @@ -207,7 +285,7 @@ private void handleClassEntry(String entryName, ClassVisitor visitor)
}

/**
* @param path The path
* @param path
* @return last node in a a string representation of URL path. For example for "/a/b/c/d/" returns "d/", for
* "/a/b/c/d.class" returns "d.class"
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@

import javax.servlet.ServletContext;

import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.ocpsoft.rewrite.annotation.api.ClassVisitor;

@SuppressWarnings({ "unchecked", "rawtypes" })
@Ignore // ignored since we now user Class.forName(name, false, cl) which we cannot mock
public class WebClassesFinderTest
{

Expand Down
15 changes: 12 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@
<module>integration-spring</module>
<module>security-integration-shiro</module>
<module>rewrite-servlet</module>
<module>showcase</module>
<!-- <module>showcase</module> -->
<module>distribution</module>
<module>examples</module>
<module>documentation</module>
<!-- <module>examples</module> -->
<!-- <module>documentation</module> -->
</modules>

<build>
Expand All @@ -105,6 +105,15 @@
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
Expand Down
Loading