From 0fd7b0d19bd96456292585e883e3ba2ebbaf579b Mon Sep 17 00:00:00 2001 From: Mikolaj Izdebski Date: Wed, 21 Jun 2017 10:21:10 +0200 Subject: [PATCH] Fix installer plugin loading --- .../install/impl/ArtifactInstallerFactory.java | 97 ++++++-- .../tools/install/impl/IsolatedClassRealm.java | 245 +++++++++++++++++++++ 2 files changed, 320 insertions(+), 22 deletions(-) create mode 100644 xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/IsolatedClassRealm.java diff --git a/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/ArtifactInstallerFactory.java b/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/ArtifactInstallerFactory.java index 7a80571..e6a9a2d 100644 --- a/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/ArtifactInstallerFactory.java +++ b/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/ArtifactInstallerFactory.java @@ -15,8 +15,17 @@ */ package org.fedoraproject.xmvn.tools.install.impl; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; -import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; import org.slf4j.Logger; @@ -35,49 +44,93 @@ class ArtifactInstallerFactory private final ArtifactInstaller defaultArtifactInstaller; - private final ArtifactInstaller eclipseArtifactInstaller; + private final IsolatedClassRealm pluginRealm; - private static ArtifactInstaller loadPlugin( String className ) + private final Map cachedPluginsByType = new LinkedHashMap<>(); + + private final Map cachedPluginsByImplClass = new LinkedHashMap<>(); + + private ArtifactInstaller tryLoadPlugin( String type ) { + if ( cachedPluginsByType.containsKey( type ) ) + return cachedPluginsByType.get( type ); + try { - return (ArtifactInstaller) ArtifactInstallerFactory.class.getClassLoader().loadClass( className ).newInstance(); + String resourceName = ArtifactInstaller.class.getCanonicalName() + "/" + type; + InputStream resourceStream = pluginRealm != null ? pluginRealm.getResourceAsStream( resourceName ) : null; + if ( resourceStream == null ) + { + logger.debug( "No XMvn Installer plugin found for packaging type {}", type ); + cachedPluginsByType.put( type, null ); + return null; + } + + String pluginImplClass; + try ( BufferedReader resourceReader = new BufferedReader( new InputStreamReader( resourceStream ) ) ) + { + pluginImplClass = resourceReader.readLine(); + } + + ArtifactInstaller pluggedInInstaller = cachedPluginsByImplClass.get( pluginImplClass ); + if ( pluggedInInstaller == null ) + { + pluggedInInstaller = (ArtifactInstaller) pluginRealm.loadClass( pluginImplClass ).newInstance(); + cachedPluginsByImplClass.put( pluginImplClass, pluggedInInstaller ); + } + + cachedPluginsByType.put( type, pluggedInInstaller ); + return pluggedInInstaller; } - catch ( ReflectiveOperationException e ) + catch ( IOException | ReflectiveOperationException e ) { - return null; + throw new RuntimeException( "Unable to load XMvn Installer plugin for packaging type " + type, e ); } } public ArtifactInstallerFactory( Configurator configurator ) { defaultArtifactInstaller = new DefaultArtifactInstaller( configurator ); - // FIXME Don't hardcode plugin class name - eclipseArtifactInstaller = loadPlugin( "org.fedoraproject.p2.xmvn.EclipseArtifactInstaller" ); + + Path pluginDir = Paths.get( "/usr/share/xmvn/lib/installer" ); + if ( Files.isDirectory( pluginDir ) ) + { + ClassLoader parentClassLoader = ArtifactInstallerFactory.class.getClassLoader(); + pluginRealm = new IsolatedClassRealm( parentClassLoader ); + pluginRealm.addJarDirectory( pluginDir ); + PLUGIN_IMPORTS.forEach( pluginRealm::importPackage ); + } + else + { + pluginRealm = null; + } } /** - * List of Tycho pacgkaging types. + * List of packages imported from XMvn Installer class loader to plug-in realms. */ - private static final Collection ECLIPSE_PACKAGING_TYPES = Arrays.asList( "eclipse-plugin", // - "eclipse-test-plugin", // - "eclipse-feature", // - "eclipse-repository", // - "eclipse-application", // - "eclipse-update-site", // - "eclipse-target-definition" ); + private static final List PLUGIN_IMPORTS = Arrays.asList( // XMvn API + "org.fedoraproject.xmvn.artifact", // + "org.fedoraproject.xmvn.config", // + "org.fedoraproject.xmvn.deployer", // + "org.fedoraproject.xmvn.locator", // + "org.fedoraproject.xmvn.metadata", // + "org.fedoraproject.xmvn.resolver", // + // XMvn Installer SPI + "org.fedoraproject.xmvn.tools.install", // + // SLF4J API + "org.slf4j" // + ); @SuppressWarnings( "unused" ) public ArtifactInstaller getInstallerFor( Artifact artifact, Properties properties ) { String type = properties.getProperty( "type" ); - if ( type != null && ECLIPSE_PACKAGING_TYPES.contains( type ) ) + if ( type != null ) { - if ( eclipseArtifactInstaller != null ) - return eclipseArtifactInstaller; - - logger.error( "Unable to load XMvn P2 plugin, Eclipse artifact installation will be impossible" ); - throw new RuntimeException( "Unable to load XMvn P2 plugin" ); + ArtifactInstaller pluggedInInstaller = tryLoadPlugin( type ); + if ( pluggedInInstaller != null ) + return pluggedInInstaller; } return defaultArtifactInstaller; diff --git a/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/IsolatedClassRealm.java b/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/IsolatedClassRealm.java new file mode 100644 index 0000000..3324604 --- /dev/null +++ b/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/IsolatedClassRealm.java @@ -0,0 +1,245 @@ +/*- + * Copyright (c) 2014-2017 Red Hat, Inc. + * + * Licensed 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.fedoraproject.xmvn.tools.install.impl; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * A generic, isolated class loader. + *

+ * This class loader has its own classpath, separate from the primary Java classpath. It has a parent class loader, to + * which it delegates loading a set of imported classes. All other classes are loaded from its own classpath. + * + * @author Mikolaj Izdebski + */ +class IsolatedClassRealm + extends URLClassLoader +{ + static + { + registerAsParallelCapable(); + } + + private final ClassLoader parent; + + private final Set imports = new HashSet<>(); + + private final Set importsAll = new HashSet<>(); + + public IsolatedClassRealm( ClassLoader parent ) + { + super( new URL[0], null ); + this.parent = parent; + } + + public void addJar( Path jar ) + { + try + { + addURL( jar.toUri().toURL() ); + } + catch ( MalformedURLException e ) + { + throw new RuntimeException( e ); + } + } + + public void addJarDirectory( Path dir ) + { + try ( DirectoryStream stream = Files.newDirectoryStream( dir, "*.jar" ) ) + { + for ( Path path : stream ) + { + addJar( path ); + } + } + catch ( IOException e ) + { + throw new RuntimeException( e ); + } + } + + public void importPackage( String packageName ) + { + imports.add( packageName ); + } + + public void importAllPackages( String packageName ) + { + importsAll.add( packageName ); + } + + boolean isImported( String name ) + { + int index = name.lastIndexOf( '/' ); + + if ( index >= 0 ) + { + name = name.replace( '/', '.' ); + } + else + { + index = Math.max( name.lastIndexOf( '.' ), 0 ); + } + + String namespace = name.substring( 0, index ); + + if ( imports.contains( namespace ) ) + return true; + + while ( !namespace.isEmpty() ) + { + if ( importsAll.contains( namespace ) ) + return true; + + namespace = namespace.substring( 0, Math.max( namespace.lastIndexOf( '.' ), 0 ) ); + } + + return false; + } + + @Override + public Class loadClass( String name ) + throws ClassNotFoundException + { + return loadClass( name, false ); + } + + @Override + protected Class loadClass( String name, boolean resolve ) + throws ClassNotFoundException + { + if ( isImported( name ) ) + { + try + { + return parent.loadClass( name ); + } + catch ( ClassNotFoundException e ) + { + } + } + + try + { + return super.loadClass( name, resolve ); + } + catch ( ClassNotFoundException e ) + { + } + + synchronized ( getClassLoadingLock( name ) ) + { + Class clazz = findLoadedClass( name ); + if ( clazz != null ) + { + return clazz; + } + + try + { + return super.findClass( name ); + } + catch ( ClassNotFoundException e ) + { + } + } + + throw new ClassNotFoundException( name ); + } + + @Override + protected Class findClass( String name ) + throws ClassNotFoundException + { + throw new ClassNotFoundException( name ); + } + + @Override + public URL getResource( String name ) + { + if ( isImported( name ) ) + { + URL resource = parent.getResource( name ); + if ( resource != null ) + { + return resource; + } + } + + URL resource = super.getResource( name ); + if ( resource != null ) + { + return resource; + } + + resource = super.findResource( name ); + if ( resource != null ) + { + return resource; + } + + return null; + } + + @Override + public Enumeration getResources( String name ) + throws IOException + { + Collection resources = new LinkedHashSet<>(); + + if ( isImported( name ) ) + { + try + { + resources.addAll( Collections.list( parent.getResources( name ) ) ); + } + catch ( IOException e ) + { + } + } + + try + { + resources.addAll( Collections.list( super.getResources( name ) ) ); + } + catch ( IOException e ) + { + } + + try + { + resources.addAll( Collections.list( super.findResources( name ) ) ); + } + catch ( IOException e ) + { + } + + return Collections.enumeration( resources ); + } +} -- 2.9.3