org.pentaho.osgi.legacy.LegacyPluginExtenderFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.osgi.legacy.LegacyPluginExtenderFactory.java

Source

/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2017 by Hitachi Vantara : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * 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.pentaho.osgi.legacy;

import org.apache.commons.io.IOUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.wiring.BundleWiring;
import org.pentaho.di.core.exception.KettlePluginException;
import org.pentaho.di.core.plugins.PluginInterface;
import org.pentaho.di.core.plugins.PluginRegistry;
import org.pentaho.di.core.plugins.PluginTypeInterface;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.List;

/**
 * Factory class to instantiate services implementing interfaces known to OSGi using classes from a legacy plugin
 */
public class LegacyPluginExtenderFactory {
    private final ClassLoader legacyBridgingClassloader;

    /**
     * Constructs the LegacyPluginExtenderFactory
     *
     * @param bundleContext the bundleContext
     * @param pluginType    the pluginType's canonical name
     * @param pluginId      the plugin id
     * @throws KettlePluginException
     */
    public LegacyPluginExtenderFactory(BundleContext bundleContext, String pluginType, String pluginId)
            throws KettlePluginException {
        ClassLoader parentClassloader = getPluginClassloader(pluginType, pluginId);
        if (parentClassloader == null) {
            throw new KettlePluginException(
                    "Unable to get parent classloader for pluginType: " + pluginType + " and pluginId " + pluginId);
        } else {
            System.out.println("Parent classloader: " + parentClassloader);
        }
        legacyBridgingClassloader = new LegacyPluginExtenderClassLoader(parentClassloader, bundleContext);
    }

    /**
     * Gets the classloader for the specified plugin, blocking until the plugin becomes available the feature watcher will
     * kill us after a while anyway
     *
     * @param pluginType the plugin type (Specified as a string so that we can get the classloader for plugin types OSGi
     *                   doesn't know about)
     * @param pluginId   the plugin id
     * @return
     * @throws KettlePluginException
     * @throws InterruptedException
     */
    private static ClassLoader getPluginClassloader(String pluginType, String pluginId)
            throws KettlePluginException {
        Class<? extends PluginTypeInterface> pluginTypeInterface = null;
        PluginRegistry pluginRegistry = PluginRegistry.getInstance();
        while (true) {
            synchronized (pluginRegistry) {
                if (pluginTypeInterface == null) {
                    for (Class<? extends PluginTypeInterface> potentialPluginTypeInterface : pluginRegistry
                            .getPluginTypes()) {
                        if (pluginType.equals(potentialPluginTypeInterface.getCanonicalName())) {
                            pluginTypeInterface = potentialPluginTypeInterface;
                        }
                    }
                }
                PluginInterface plugin = pluginRegistry.getPlugin(pluginTypeInterface, pluginId);
                if (plugin != null) {
                    return pluginRegistry.getClassLoader(plugin);
                }
                try {
                    pluginRegistry.wait();
                } catch (InterruptedException e) {
                    throw new KettlePluginException(e);
                }
            }
        }
    }

    /**
     * Creates the object using a child-first classloader that uses local bundle bytecode first (so that it will be the
     * classloader for bundle classes created using this method which is necessary for correct loading of imports), the
     * bundleContext's classloader second, and the Kettle plugin's classloader third.
     * <p/>
     * This allows bundles to implement interfaces known only to OSGi using classes only known to Ketle plugins (or vice
     * versa).
     *
     * @param className the classname to instantiate
     * @return the instantiated class
     * @throws ClassNotFoundException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws KettlePluginException
     * @throws IllegalAccessException
     * @throws InterruptedException
     */
    public Object create(String className) throws ClassNotFoundException, InvocationTargetException,
            IllegalAccessException, InstantiationException {
        return create(className, null);
    }

    /**
     * Creates the object using a child-first classloader that uses local bundle bytecode first (so that it will be the
     * classloader for bundle classes created using this method which is necessary for correct loading of imports), the
     * bundleContext's classloader second, and the Kettle plugin's classloader third.
     * <p/>
     * This allows bundles to implement interfaces known only to OSGi using classes only known to Ketle plugins (or vice
     * versa).
     *
     * @param className the classname to instantiate
     * @param arguments the arguments to use to instantiate the class
     * @return the instantiated class
     * @throws ClassNotFoundException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws KettlePluginException
     * @throws IllegalAccessException
     * @throws InterruptedException
     */
    public Object create(String className, List<Object> arguments) throws ClassNotFoundException,
            IllegalAccessException, InstantiationException, InvocationTargetException {
        Class<?> clazz = Class.forName(className, true, legacyBridgingClassloader);
        if (arguments == null || arguments.size() == 0) {
            return clazz.newInstance();
        }
        for (Constructor<?> constructor : clazz.getConstructors()) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            if (parameterTypes.length == arguments.size()) {
                boolean match = true;
                for (int i = 0; i < parameterTypes.length; i++) {
                    Object o = arguments.get(i);
                    if (o != null && !parameterTypes[i].isInstance(o)) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    return constructor.newInstance(arguments.toArray());
                }
            }
        }
        throw new InstantiationException(
                "Unable to find constructor for class " + className + " with arguments " + arguments);
    }

    /**
     * A classloader that will use load bytecode local to the bundle, the bundle's classloader as a second try, and the
     * parent classloader last
     */
    private static class LegacyPluginExtenderClassLoader extends ClassLoader {
        private final BundleWiring bundleWiring;
        private final PublicLoadResolveClassLoader bundleWiringClassloader;

        public LegacyPluginExtenderClassLoader(ClassLoader parentClassLoader, BundleContext bundleContext) {
            super(parentClassLoader);
            this.bundleWiring = (BundleWiring) bundleContext.getBundle().adapt(BundleWiring.class);
            this.bundleWiringClassloader = new PublicLoadResolveClassLoader(bundleWiring.getClassLoader());
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            int lastIndexOfDot = name.lastIndexOf('.');
            String translatedPath = "/" + name.substring(0, lastIndexOfDot).replace('.', '/');
            String translatedName = name.substring(lastIndexOfDot + 1) + ".class";
            List<URL> entries = bundleWiring.findEntries(translatedPath, translatedName, 0);
            if (entries.size() == 1) {
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                try {
                    IOUtils.copy(entries.get(0).openStream(), byteArrayOutputStream);
                } catch (IOException e) {
                    throw new ClassNotFoundException("Unable to define class", e);
                }
                byte[] bytes = byteArrayOutputStream.toByteArray();
                return defineClass(name, bytes, 0, bytes.length);
            }
            throw new ClassNotFoundException();
        }

        @Override
        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class<?> result = null;
            synchronized (this) {
                result = findLoadedClass(name);
            }
            if (result == null) {
                try {
                    result = findClass(name);
                } catch (Exception e) {

                }
            }
            if (result == null) {
                try {
                    return bundleWiringClassloader.loadClass(name, resolve);
                } catch (Exception e) {

                }
            }
            if (result == null) {
                return super.loadClass(name, resolve);
            }
            if (resolve) {
                resolveClass(result);
            }
            return result;
        }
    }

    /**
     * Trivial classloader subclass that lets us call loadClass with a resolve parameter
     */
    private static class PublicLoadResolveClassLoader extends ClassLoader {
        public PublicLoadResolveClassLoader(ClassLoader parent) {
            super(parent);
        }

        @Override
        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            return super.loadClass(name, resolve);
        }
    }
}