diff --git a/pom.xml b/pom.xml index 0a51af7..7e95592 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,16 @@ + + org.slf4j + slf4j-api + 1.7.36 + + + org.eclipse.transformer + org.eclipse.transformer + 1.1.0-SNAPSHOT + org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java index 55e77fa..070ca70 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java +++ b/src/main/java/org/codehaus/plexus/classworlds/ClassWorld.java @@ -25,11 +25,13 @@ import java.util.List; import java.util.Map; import java.util.function.Predicate; +import java.util.function.Supplier; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.classworlds.realm.DuplicateRealmException; import org.codehaus.plexus.classworlds.realm.FilteredClassRealm; import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; +import org.codehaus.plexus.classworlds.realm.TransformerClassRealm; /** * A collection of ClassRealms, indexed by id. @@ -75,25 +77,46 @@ public ClassRealm newRealm(String id, ClassLoader classLoader) throws DuplicateR * @since 2.7.0 * @see FilteredClassRealm */ - public synchronized ClassRealm newRealm(String id, ClassLoader classLoader, Predicate filter) + public ClassRealm newRealm(String id, ClassLoader classLoader, Predicate filter) throws DuplicateRealmException { - if (realms.containsKey(id)) { - throw new DuplicateRealmException(this, id); - } - - ClassRealm realm; + return newRealm(() -> { + if (filter == null) { + // return new ClassRealm(this, id, classLoader); + return TransformerClassRealm.newJakartaTransformerClassRealm(this, id, classLoader); + } else { + return new FilteredClassRealm(filter, this, id, classLoader); + } + }); + } - if (filter == null) { - realm = new ClassRealm(this, id, classLoader); - } else { - realm = new FilteredClassRealm(filter, this, id, classLoader); + /** + * Adds a class realm using caller supplied factory. + * + * @param factory The factory to create realm instance. + * @return the created class realm + * @throws DuplicateRealmException in case a realm with the given id does already exist + * @since TBD + * @see FilteredClassRealm + */ + public synchronized ClassRealm newRealm(Supplier factory) throws DuplicateRealmException { + ClassRealm realm = factory.get(); + if (realms.containsKey(realm.getId())) { + Exception closeEx = null; + try { + realm.close(); + } catch (Exception e) { + closeEx = e; + } + DuplicateRealmException ex = new DuplicateRealmException(this, realm.getId()); + if (closeEx != null) { + ex.addSuppressed(closeEx); + } + throw ex; } - realms.put(id, realm); - + realms.put(realm.getId(), realm); for (ClassWorldListener listener : listeners) { listener.realmCreated(realm); } - return realm; } diff --git a/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java b/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java index bbeacee..3595aa7 100644 --- a/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java +++ b/src/main/java/org/codehaus/plexus/classworlds/realm/ClassRealm.java @@ -45,7 +45,7 @@ * @author bob mcwhirter * @author Jason van Zyl */ -public class ClassRealm extends URLClassLoader { +public abstract class ClassRealm extends URLClassLoader { private final ClassWorld world; diff --git a/src/main/java/org/codehaus/plexus/classworlds/realm/TransformerClassRealm.java b/src/main/java/org/codehaus/plexus/classworlds/realm/TransformerClassRealm.java new file mode 100644 index 0000000..b9092e1 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/classworlds/realm/TransformerClassRealm.java @@ -0,0 +1,350 @@ +/* + * 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.codehaus.plexus.classworlds.realm; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import org.codehaus.plexus.classworlds.ClassWorld; +import org.eclipse.transformer.TransformException; +import org.eclipse.transformer.action.ActionContext; +import org.eclipse.transformer.action.ActionType; +import org.eclipse.transformer.action.ByteData; +import org.eclipse.transformer.action.impl.ClassActionImpl; +import org.eclipse.transformer.action.impl.SelectionRuleImpl; +import org.eclipse.transformer.action.impl.ServiceLoaderConfigActionImpl; +import org.eclipse.transformer.action.impl.SignatureRuleImpl; +import org.eclipse.transformer.action.impl.ZipActionImpl; +import org.eclipse.transformer.util.FileUtils; +import org.eclipse.transformer.util.SignatureUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A {@link ClassRealm} that integrates {@link org.eclipse.transformer.Transformer}. + * + * @since TBD + */ +public class TransformerClassRealm extends ClassRealm { + private final ZipActionImpl jarAction; + private final ClassActionImpl classAction; + private final ServiceLoaderConfigActionImpl serviceConfigAction; + + public static TransformerClassRealm newJakartaTransformerClassRealm( + ClassWorld world, String id, ClassLoader baseClassLoader) { + Logger logger = LoggerFactory.getLogger(TransformerClassRealm.class); + ActionContext context = new ActionContext( + logger, + new SelectionRuleImpl(logger, getIncludes(), getExcludes()), + new SignatureRuleImpl( + logger, getToJakartaRenames(), null, null, null, null, null, Collections.emptyMap())); + + ZipActionImpl jarAction = new ZipActionImpl(context, ActionType.JAR, false); + ClassActionImpl classAction = jarAction.addUsing(ClassActionImpl::new); + ServiceLoaderConfigActionImpl configAction = jarAction.addUsing(ServiceLoaderConfigActionImpl::new); + return new TransformerClassRealm(world, id, baseClassLoader, jarAction, classAction, configAction); + } + + private static Map getIncludes() { + Map includes = new HashMap<>(); + includes.put("*", ""); + // includes.put("org/apache/*", ""); + // includes.put("org/codehaus/*", ""); + // includes.put("org/mojohaus/*", ""); + // includes.put("org/eclipse/*", ""); + return includes; + } + + private static Map getExcludes() { + Map excludes = new HashMap<>(); + excludes.put("org/google/inject/*", ""); + excludes.put("org/codehaus/plexus/classworlds/*", ""); + return excludes; + } + + private static Map getToJakartaRenames() { + Map renames = new HashMap<>(); + renames.put("javax.inject.*", "jakarta.inject"); + return renames; + } + + /** + * Creates a new class realm. + * + * @param world The class world this realm belongs to, must not be null. + * @param id The identifier for this realm, must not be null. + * @param baseClassLoader The base class loader for this realm, may be null to use the bootstrap class + * @param jarAction the action to apply to each jar file loaded through this class loader. + * @param classAction the action to apply to each class loaded through this class loader. + * @param serviceConfigAction the action to apply to each service configuration file loaded through this class loader. + */ + public TransformerClassRealm( + ClassWorld world, + String id, + ClassLoader baseClassLoader, + ZipActionImpl jarAction, + ClassActionImpl classAction, + ServiceLoaderConfigActionImpl serviceConfigAction) { + super(world, id, baseClassLoader); + this.jarAction = jarAction; + this.classAction = classAction; + this.serviceConfigAction = serviceConfigAction; + } + + protected ZipActionImpl getJarAction() { + return jarAction; + } + + protected ClassActionImpl getClassAction() { + return classAction; + } + + protected ServiceLoaderConfigActionImpl getServiceConfigAction() { + return serviceConfigAction; + } + + protected boolean selectResource(String resourceName) { + return getJarAction().selectResource(resourceName); + } + + public String getResourceName(String className) { + return SignatureUtils.classNameToResourceName(className); + } + + protected boolean acceptClass(String resourceName) { + return getClassAction().acceptResource(resourceName); + } + + protected InputStream applyClass(String resourceName, InputStream inputStream) throws TransformException { + ClassActionImpl classAction = getClassAction(); + ByteData inputData = classAction.collect(resourceName, inputStream); + ByteData outputData = classAction.apply(inputData); + return outputData.stream(); + } + + protected boolean acceptServiceConfig(String resourceName) { + ServiceLoaderConfigActionImpl configAction = getServiceConfigAction(); + return ((configAction != null) && configAction.acceptResource(resourceName)); + } + + protected InputStream applyServiceConfig(String resourceName, InputStream inputStream) throws TransformException { + ServiceLoaderConfigActionImpl configAction = getServiceConfigAction(); + if (configAction == null) { + return inputStream; + } else { + ByteData inputData = configAction.collect(resourceName, inputStream); + ByteData outputData = configAction.apply(inputData); + return outputData.stream(); + } + } + + protected class TransformClassURLStreamHandler extends URLStreamHandler { + private final URL baseURL; + + protected TransformClassURLStreamHandler(URL baseURL) { + this.baseURL = baseURL; + } + + @Override + protected URLConnection openConnection(URL u) throws IOException { + return new TransformClassURLConnection(baseURL); + } + } + + protected class TransformClassURLConnection extends URLConnection { + private final URLConnection baseConnection; + + protected TransformClassURLConnection(URL baseURL) throws IOException { + super(baseURL); + this.baseConnection = this.getURL().openConnection(); + } + + public URLConnection getBaseConnection() { + return baseConnection; + } + + @Override + public void connect() throws IOException { + getBaseConnection().connect(); + } + + @Override + public InputStream getInputStream() throws IOException { + URLConnection useBaseConnection = getBaseConnection(); + String baseName = useBaseConnection.getURL().toString(); + InputStream baseStream = useBaseConnection.getInputStream(); + return applyClass(baseName, baseStream); + } + } + + protected class TransformConfigURLStreamHandler extends URLStreamHandler { + private final URL baseURL; + + protected TransformConfigURLStreamHandler(URL baseURL) { + this.baseURL = baseURL; + } + + @Override + protected URLConnection openConnection(URL u) throws IOException { + return new TransformConfigURLConnection(baseURL); + } + } + + protected class TransformConfigURLConnection extends URLConnection { + private final URLConnection baseConnection; + + protected TransformConfigURLConnection(URL baseURL) throws IOException { + super(baseURL); + this.baseConnection = this.getURL().openConnection(); + } + + public URLConnection getBaseConnection() { + return baseConnection; + } + + @Override + public void connect() throws IOException { + getBaseConnection().connect(); + } + + @Override + public InputStream getInputStream() throws IOException { + URLConnection useBaseConnection = getBaseConnection(); + String baseName = useBaseConnection.getURL().toString(); + InputStream baseStream = useBaseConnection.getInputStream(); + return applyServiceConfig(baseName, baseStream); + } + } + + protected URL transformAsClass(URL baseURL) { + String baseText = baseURL.toString(); + try { + return new URL(null, baseText, new TransformClassURLStreamHandler(baseURL)); + } catch (MalformedURLException e) { + return baseURL; + } + } + + protected URL transformAsServiceConfig(URL baseURL) { + String baseText = baseURL.toString(); + try { + return new URL(null, baseText, new TransformConfigURLStreamHandler(baseURL)); + } catch (MalformedURLException e) { + return baseURL; + } + } + + @Override + public URL findResource(String name) { + URL baseURL = super.findResource(name); + if (baseURL == null) { + return null; + } else { + return transform(name, baseURL); + } + } + + protected URL transform(String name, URL baseURL) { + if (!selectResource(name)) { + return baseURL; + } else if (acceptClass(name)) { + return transformAsClass(baseURL); + } else if (acceptServiceConfig(name)) { + return transformAsServiceConfig(baseURL); + } else { + return baseURL; + } + } + + private final String SISU_JAVAX_RESOURCE = "META-INF/sisu/javax.inject.Named"; + private final String SISU_JAKARTA_RESOURCE = "META-INF/sisu/jakarta.inject.Named"; + + @Override + public Enumeration findResources(String name) throws IOException { + if (SISU_JAKARTA_RESOURCE.equals(name)) { + return doFindSisuIndexes(); + } else { + return doFindResources(name); + } + } + + protected Enumeration doFindSisuIndexes() throws IOException { + Enumeration javax = super.findResources(SISU_JAKARTA_RESOURCE); + Enumeration jakarta = super.findResources(SISU_JAVAX_RESOURCE); + Vector indexes = new Vector<>(); + while (javax.hasMoreElements()) { + indexes.add(javax.nextElement()); + } + while (jakarta.hasMoreElements()) { + indexes.add(jakarta.nextElement()); + } + return indexes.elements(); + } + + protected Enumeration doFindResources(String name) throws IOException { + Enumeration baseURLs = super.findResources(name); + if (!baseURLs.hasMoreElements()) { + return baseURLs; + } else if (!selectResource(name)) { + return baseURLs; + } else { + Vector transformedURLs = new Vector<>(); + while (baseURLs.hasMoreElements()) { + transformedURLs.add(transform(name, baseURLs.nextElement())); + } + return transformedURLs.elements(); + } + } + + @Override + protected Class findClassInternal(String className) throws ClassNotFoundException { + String resourceName = getResourceName(className); + if (!selectResource(resourceName)) { + return super.findClassInternal(className); + } + URL res = loadResourceFromSelf(resourceName); + if (res == null) { + throw new ClassNotFoundException(resourceName); + } + try (InputStream classStream = res.openStream()) { + if (classStream == null) { + throw new ClassNotFoundException(className); + } + ByteBuffer classData; + try { + classData = FileUtils.read(className, classStream); + } catch (IOException e) { + throw new ClassNotFoundException(className, e); + } + return super.defineClass(className, classData.array(), classData.arrayOffset(), classData.limit()); + } catch (IOException e) { + throw new ClassNotFoundException(className, e); + } + } +} diff --git a/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java b/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java index 6fb6184..bbb3175 100644 --- a/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java +++ b/src/test/java/org/codehaus/plexus/classworlds/realm/ClassRealmImplTest.java @@ -54,8 +54,8 @@ void newRealm() throws Exception { } @Test - void locateSourceRealmNoImports() { - ClassRealm realm = new ClassRealm(this.world, "foo", null); + void locateSourceRealmNoImports() throws DuplicateRealmException { + ClassRealm realm = world.newRealm("foo", null); assertSame(null, realm.getImportClassLoader("com.werken.Stuff")); } diff --git a/src/test/java/org/codehaus/plexus/classworlds/realm/DefaultClassRealmTest.java b/src/test/java/org/codehaus/plexus/classworlds/realm/DefaultClassRealmTest.java index 127fe9e..ffb2926 100644 --- a/src/test/java/org/codehaus/plexus/classworlds/realm/DefaultClassRealmTest.java +++ b/src/test/java/org/codehaus/plexus/classworlds/realm/DefaultClassRealmTest.java @@ -40,7 +40,7 @@ class DefaultClassRealmTest extends AbstractClassWorldsTestCase { @Test void loadClassFromRealm() throws Exception { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("component0-1.0.jar")); @@ -49,7 +49,7 @@ void loadClassFromRealm() throws Exception { @Test void loadClassFromChildRealmWhereClassIsLocatedInParentRealm() throws Exception { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("component0-1.0.jar")); @@ -60,7 +60,7 @@ void loadClassFromChildRealmWhereClassIsLocatedInParentRealm() throws Exception @Test void loadClassFromChildRealmWhereClassIsLocatedInGrantParentRealm() throws Exception { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("component0-1.0.jar")); @@ -73,7 +73,7 @@ void loadClassFromChildRealmWhereClassIsLocatedInGrantParentRealm() throws Excep @Test void loadClassFromChildRealmWhereClassIsLocatedInBothChildRealmAndParentRealm() throws Exception { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "parent", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("component5-1.0.jar")); @@ -89,8 +89,8 @@ void loadClassFromChildRealmWhereClassIsLocatedInBothChildRealmAndParentRealm() } @Test - void loadNonExistentClass() { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + void loadNonExistentClass() throws DuplicateRealmException { + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("component0-1.0.jar")); @@ -161,7 +161,7 @@ void loadClassFromBaseClassLoaderBeforeSelf() throws Exception { @Test void loadClassFromRealmWithCircularClassReferences() throws Exception { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("circular-0.1.jar")); @@ -178,7 +178,7 @@ void loadClassFromRealmWithCircularClassReferences() throws Exception { @Test void resource() throws Exception { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("component0-1.0.jar")); @@ -189,7 +189,7 @@ void resource() throws Exception { void malformedResource() throws Exception { URL jarUrl = getJarUrl("component0-1.0.jar"); - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(jarUrl); @@ -212,7 +212,7 @@ void malformedResource() throws Exception { @Test void findResourceOnlyScansSelf() throws Exception { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("a.jar")); @@ -230,7 +230,7 @@ void findResourceOnlyScansSelf() throws Exception { @Test void findResourcesOnlyScansSelf() throws Exception { - ClassRealm mainRealm = new ClassRealm(new ClassWorld(), "main", null); + ClassRealm mainRealm = new ClassWorld().newRealm("main", null); mainRealm.addURL(getJarUrl("a.jar")); @@ -254,10 +254,10 @@ void parallelDeadlockClassRealm() throws Exception { } } - private void doOneDeadlockAttempt() throws InterruptedException { + private void doOneDeadlockAttempt() throws DuplicateRealmException, InterruptedException { // Deadlock sample graciously ripped from http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html - final ClassRealm cl1 = new ClassRealm(new ClassWorld(), "cl1", null); - final ClassRealm cl2 = new ClassRealm(new ClassWorld(), "cl2", cl1); + final ClassRealm cl1 = new ClassWorld().newRealm("cl1", null); + final ClassRealm cl2 = new ClassWorld().newRealm("cl2", cl1); cl1.setParentRealm(cl2); cl1.addURL(getJarUrl("deadlock.jar")); cl2.addURL(getJarUrl("deadlock.jar"));