6b9f22
From 0fd7b0d19bd96456292585e883e3ba2ebbaf579b Mon Sep 17 00:00:00 2001
6b9f22
From: Mikolaj Izdebski <mizdebsk@redhat.com>
6b9f22
Date: Wed, 21 Jun 2017 10:21:10 +0200
6b9f22
Subject: [PATCH] Fix installer plugin loading
6b9f22
6b9f22
---
6b9f22
 .../install/impl/ArtifactInstallerFactory.java     |  97 ++++++--
6b9f22
 .../tools/install/impl/IsolatedClassRealm.java     | 245 +++++++++++++++++++++
6b9f22
 2 files changed, 320 insertions(+), 22 deletions(-)
6b9f22
 create mode 100644 xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/IsolatedClassRealm.java
6b9f22
6b9f22
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
6b9f22
index 7a80571..e6a9a2d 100644
6b9f22
--- a/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/ArtifactInstallerFactory.java
6b9f22
+++ b/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/ArtifactInstallerFactory.java
6b9f22
@@ -15,8 +15,17 @@
6b9f22
  */
6b9f22
 package org.fedoraproject.xmvn.tools.install.impl;
6b9f22
 
6b9f22
+import java.io.BufferedReader;
6b9f22
+import java.io.IOException;
6b9f22
+import java.io.InputStream;
6b9f22
+import java.io.InputStreamReader;
6b9f22
+import java.nio.file.Files;
6b9f22
+import java.nio.file.Path;
6b9f22
+import java.nio.file.Paths;
6b9f22
 import java.util.Arrays;
6b9f22
-import java.util.Collection;
6b9f22
+import java.util.LinkedHashMap;
6b9f22
+import java.util.List;
6b9f22
+import java.util.Map;
6b9f22
 import java.util.Properties;
6b9f22
 
6b9f22
 import org.slf4j.Logger;
6b9f22
@@ -35,49 +44,93 @@ class ArtifactInstallerFactory
6b9f22
 
6b9f22
     private final ArtifactInstaller defaultArtifactInstaller;
6b9f22
 
6b9f22
-    private final ArtifactInstaller eclipseArtifactInstaller;
6b9f22
+    private final IsolatedClassRealm pluginRealm;
6b9f22
 
6b9f22
-    private static ArtifactInstaller loadPlugin( String className )
6b9f22
+    private final Map<String, ArtifactInstaller> cachedPluginsByType = new LinkedHashMap<>();
6b9f22
+
6b9f22
+    private final Map<String, ArtifactInstaller> cachedPluginsByImplClass = new LinkedHashMap<>();
6b9f22
+
6b9f22
+    private ArtifactInstaller tryLoadPlugin( String type )
6b9f22
     {
6b9f22
+        if ( cachedPluginsByType.containsKey( type ) )
6b9f22
+            return cachedPluginsByType.get( type );
6b9f22
+
6b9f22
         try
6b9f22
         {
6b9f22
-            return (ArtifactInstaller) ArtifactInstallerFactory.class.getClassLoader().loadClass( className ).newInstance();
6b9f22
+            String resourceName = ArtifactInstaller.class.getCanonicalName() + "/" + type;
6b9f22
+            InputStream resourceStream = pluginRealm != null ? pluginRealm.getResourceAsStream( resourceName ) : null;
6b9f22
+            if ( resourceStream == null )
6b9f22
+            {
6b9f22
+                logger.debug( "No XMvn Installer plugin found for packaging type {}", type );
6b9f22
+                cachedPluginsByType.put( type, null );
6b9f22
+                return null;
6b9f22
+            }
6b9f22
+
6b9f22
+            String pluginImplClass;
6b9f22
+            try ( BufferedReader resourceReader = new BufferedReader( new InputStreamReader( resourceStream ) ) )
6b9f22
+            {
6b9f22
+                pluginImplClass = resourceReader.readLine();
6b9f22
+            }
6b9f22
+
6b9f22
+            ArtifactInstaller pluggedInInstaller = cachedPluginsByImplClass.get( pluginImplClass );
6b9f22
+            if ( pluggedInInstaller == null )
6b9f22
+            {
6b9f22
+                pluggedInInstaller = (ArtifactInstaller) pluginRealm.loadClass( pluginImplClass ).newInstance();
6b9f22
+                cachedPluginsByImplClass.put( pluginImplClass, pluggedInInstaller );
6b9f22
+            }
6b9f22
+
6b9f22
+            cachedPluginsByType.put( type, pluggedInInstaller );
6b9f22
+            return pluggedInInstaller;
6b9f22
         }
6b9f22
-        catch ( ReflectiveOperationException e )
6b9f22
+        catch ( IOException | ReflectiveOperationException e )
6b9f22
         {
6b9f22
-            return null;
6b9f22
+            throw new RuntimeException( "Unable to load XMvn Installer plugin for packaging type " + type, e );
6b9f22
         }
6b9f22
     }
6b9f22
 
6b9f22
     public ArtifactInstallerFactory( Configurator configurator )
6b9f22
     {
6b9f22
         defaultArtifactInstaller = new DefaultArtifactInstaller( configurator );
6b9f22
-        // FIXME Don't hardcode plugin class name
6b9f22
-        eclipseArtifactInstaller = loadPlugin( "org.fedoraproject.p2.xmvn.EclipseArtifactInstaller" );
6b9f22
+
6b9f22
+        Path pluginDir = Paths.get( "/usr/share/xmvn/lib/installer" );
6b9f22
+        if ( Files.isDirectory( pluginDir ) )
6b9f22
+        {
6b9f22
+            ClassLoader parentClassLoader = ArtifactInstallerFactory.class.getClassLoader();
6b9f22
+            pluginRealm = new IsolatedClassRealm( parentClassLoader );
6b9f22
+            pluginRealm.addJarDirectory( pluginDir );
6b9f22
+            PLUGIN_IMPORTS.forEach( pluginRealm::importPackage );
6b9f22
+        }
6b9f22
+        else
6b9f22
+        {
6b9f22
+            pluginRealm = null;
6b9f22
+        }
6b9f22
     }
6b9f22
 
6b9f22
     /**
6b9f22
-     * List of Tycho pacgkaging types.
6b9f22
+     * List of packages imported from XMvn Installer class loader to plug-in realms.
6b9f22
      */
6b9f22
-    private static final Collection<String> ECLIPSE_PACKAGING_TYPES = Arrays.asList( "eclipse-plugin", //
6b9f22
-                                                                                     "eclipse-test-plugin", //
6b9f22
-                                                                                     "eclipse-feature", //
6b9f22
-                                                                                     "eclipse-repository", //
6b9f22
-                                                                                     "eclipse-application", //
6b9f22
-                                                                                     "eclipse-update-site", //
6b9f22
-                                                                                     "eclipse-target-definition" );
6b9f22
+    private static final List<String> PLUGIN_IMPORTS = Arrays.asList( // XMvn API
6b9f22
+                                                                      "org.fedoraproject.xmvn.artifact", //
6b9f22
+                                                                      "org.fedoraproject.xmvn.config", //
6b9f22
+                                                                      "org.fedoraproject.xmvn.deployer", //
6b9f22
+                                                                      "org.fedoraproject.xmvn.locator", //
6b9f22
+                                                                      "org.fedoraproject.xmvn.metadata", //
6b9f22
+                                                                      "org.fedoraproject.xmvn.resolver", //
6b9f22
+                                                                      // XMvn Installer SPI
6b9f22
+                                                                      "org.fedoraproject.xmvn.tools.install", //
6b9f22
+                                                                      // SLF4J API
6b9f22
+                                                                      "org.slf4j" //
6b9f22
+    );
6b9f22
 
6b9f22
     @SuppressWarnings( "unused" )
6b9f22
     public ArtifactInstaller getInstallerFor( Artifact artifact, Properties properties )
6b9f22
     {
6b9f22
         String type = properties.getProperty( "type" );
6b9f22
-        if ( type != null && ECLIPSE_PACKAGING_TYPES.contains( type ) )
6b9f22
+        if ( type != null )
6b9f22
         {
6b9f22
-            if ( eclipseArtifactInstaller != null )
6b9f22
-                return eclipseArtifactInstaller;
6b9f22
-
6b9f22
-            logger.error( "Unable to load XMvn P2 plugin, Eclipse artifact installation will be impossible" );
6b9f22
-            throw new RuntimeException( "Unable to load XMvn P2 plugin" );
6b9f22
+            ArtifactInstaller pluggedInInstaller = tryLoadPlugin( type );
6b9f22
+            if ( pluggedInInstaller != null )
6b9f22
+                return pluggedInInstaller;
6b9f22
         }
6b9f22
 
6b9f22
         return defaultArtifactInstaller;
6b9f22
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
6b9f22
new file mode 100644
6b9f22
index 0000000..3324604
6b9f22
--- /dev/null
6b9f22
+++ b/xmvn-tools/xmvn-install/src/main/java/org/fedoraproject/xmvn/tools/install/impl/IsolatedClassRealm.java
6b9f22
@@ -0,0 +1,245 @@
6b9f22
+/*-
6b9f22
+ * Copyright (c) 2014-2017 Red Hat, Inc.
6b9f22
+ *
6b9f22
+ * Licensed under the Apache License, Version 2.0 (the "License");
6b9f22
+ * you may not use this file except in compliance with the License.
6b9f22
+ * You may obtain a copy of the License at
6b9f22
+ *
6b9f22
+ *     http://www.apache.org/licenses/LICENSE-2.0
6b9f22
+ *
6b9f22
+ * Unless required by applicable law or agreed to in writing, software
6b9f22
+ * distributed under the License is distributed on an "AS IS" BASIS,
6b9f22
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6b9f22
+ * See the License for the specific language governing permissions and
6b9f22
+ * limitations under the License.
6b9f22
+ */
6b9f22
+package org.fedoraproject.xmvn.tools.install.impl;
6b9f22
+
6b9f22
+import java.io.IOException;
6b9f22
+import java.net.MalformedURLException;
6b9f22
+import java.net.URL;
6b9f22
+import java.net.URLClassLoader;
6b9f22
+import java.nio.file.DirectoryStream;
6b9f22
+import java.nio.file.Files;
6b9f22
+import java.nio.file.Path;
6b9f22
+import java.util.Collection;
6b9f22
+import java.util.Collections;
6b9f22
+import java.util.Enumeration;
6b9f22
+import java.util.HashSet;
6b9f22
+import java.util.LinkedHashSet;
6b9f22
+import java.util.Set;
6b9f22
+
6b9f22
+/**
6b9f22
+ * A generic, isolated class loader.
6b9f22
+ * 

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