404 lines
14 KiB
Diff
404 lines
14 KiB
Diff
|
From 0fd7b0d19bd96456292585e883e3ba2ebbaf579b Mon Sep 17 00:00:00 2001
|
||
|
From: Mikolaj Izdebski <mizdebsk@redhat.com>
|
||
|
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<String, ArtifactInstaller> cachedPluginsByType = new LinkedHashMap<>();
|
||
|
+
|
||
|
+ private final Map<String, ArtifactInstaller> 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<String> 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<String> 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.
|
||
|
+ * <p>
|
||
|
+ * 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<String> imports = new HashSet<>();
|
||
|
+
|
||
|
+ private final Set<String> 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<Path> 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<URL> getResources( String name )
|
||
|
+ throws IOException
|
||
|
+ {
|
||
|
+ Collection<URL> 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
|
||
|
|