diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppClassLoader.java b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppClassLoader.java index 6c73bf678c4..4df0656aad2 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppClassLoader.java +++ b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppClassLoader.java @@ -18,6 +18,7 @@ import java.io.InputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; @@ -32,12 +33,14 @@ import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.jar.Manifest; import org.eclipse.jetty.util.ClassVisibilityChecker; import org.eclipse.jetty.util.FileID; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceCollators; import org.eclipse.jetty.util.resource.ResourceFactory; @@ -563,6 +566,8 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility bytes = tmp; } + definePackageIfNecessary(name, url); + return defineClass(name, bytes, 0, bytes.length); } catch (IOException | IllegalClassFormatException e) @@ -575,6 +580,41 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility } } + private void definePackageIfNecessary(String className, URL url) throws IOException + { + int lastDotIndex = className.lastIndexOf('.'); + if (lastDotIndex < 0) + return; + String packageName = className.substring(0, lastDotIndex); + if (getDefinedPackage(packageName) == null) + { + try + { + String externalForm = url.toExternalForm(); + if (externalForm.startsWith("jar:file:") && externalForm.contains("!/")) + { + URI jarURI = URIUtil.unwrapContainer(new URI(externalForm)); + Resource manifestResource = getContext().newResource(URIUtil.uriJarPrefix(jarURI, "!/META-INF/MANIFEST.MF").toASCIIString()); + if (manifestResource.exists()) + { + try (InputStream is = manifestResource.newInputStream()) + { + Manifest manifest = new Manifest(is); + definePackage(packageName, manifest, jarURI.toURL()); + return; + } + } + } + } + catch (Throwable t) + { + LOG.trace("could not read manifest of {}", url, t); + } + + definePackage(packageName, null, null, null, null, null, null, null); + } + } + @Override public void close() throws IOException { diff --git a/jetty-ee11/jetty-ee11-webapp/src/test/java/org/eclipse/jetty/ee11/webapp/WebAppClassLoaderTest.java b/jetty-ee11/jetty-ee11-webapp/src/test/java/org/eclipse/jetty/ee11/webapp/WebAppClassLoaderTest.java index 1811843d5db..b19ef998852 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/test/java/org/eclipse/jetty/ee11/webapp/WebAppClassLoaderTest.java +++ b/jetty-ee11/jetty-ee11-webapp/src/test/java/org/eclipse/jetty/ee11/webapp/WebAppClassLoaderTest.java @@ -139,6 +139,7 @@ public class WebAppClassLoaderTest Class clazzA = _loader.loadClass("org.acme.webapp.ClassInJarA"); assertThrows(NoSuchFieldException.class, () -> clazzA.getField("FROM_PARENT")); + assertThat(clazzA.getPackage().getName(), is("org.acme.webapp")); } @Test @@ -190,6 +191,29 @@ public class WebAppClassLoaderTest "org.acme.webapp.ClassInJarB", _loader, "org.acme.other.ClassInClassesC")); + + Class classInJarA = _loader.loadClass("org.acme.webapp.ClassInJarA"); + Class classInJarB = _loader.loadClass("org.acme.webapp.ClassInJarB"); + Class classInClassesC = _loader.loadClass("org.acme.other.ClassInClassesC"); + + assertThat(classInJarA.getPackage().getName(), is("org.acme.webapp")); + assertThat(classInJarB.getPackage().getName(), is("org.acme.webapp")); + assertThat(classInClassesC.getPackage().getName(), is("org.acme.other")); + + assertThat(classInJarA.getPackage().getSpecificationTitle(), is("Test Package")); + assertThat(classInJarA.getPackage().getSpecificationVersion(), is("3.2.1")); + assertThat(classInJarA.getPackage().getImplementationTitle(), is("Testing 123")); + assertThat(classInJarA.getPackage().getImplementationVersion(), is("1.2.3")); + + assertThat(classInJarB.getPackage().getSpecificationTitle(), is("Test Package")); + assertThat(classInJarB.getPackage().getSpecificationVersion(), is("3.2.1")); + assertThat(classInJarB.getPackage().getImplementationTitle(), is("Testing 123")); + assertThat(classInJarB.getPackage().getImplementationVersion(), is("1.2.3")); + + assertThat(classInClassesC.getPackage().getSpecificationTitle(), nullValue()); + assertThat(classInClassesC.getPackage().getSpecificationVersion(), nullValue()); + assertThat(classInClassesC.getPackage().getImplementationTitle(), nullValue()); + assertThat(classInClassesC.getPackage().getImplementationVersion(), nullValue()); } @Test diff --git a/jetty-ee11/jetty-ee11-webapp/src/test/webapp/WEB-INF/lib/acme.jar b/jetty-ee11/jetty-ee11-webapp/src/test/webapp/WEB-INF/lib/acme.jar index 240c90f34d3..0a6fa744a32 100644 Binary files a/jetty-ee11/jetty-ee11-webapp/src/test/webapp/WEB-INF/lib/acme.jar and b/jetty-ee11/jetty-ee11-webapp/src/test/webapp/WEB-INF/lib/acme.jar differ