Package com.dotmarketing.osgi

Source Code of com.dotmarketing.osgi.UrlOsgiClassLoader

package com.dotmarketing.osgi;

import com.dotcms.repackage.org.github.jamm.MemoryMeter;
import com.dotmarketing.util.Logger;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* Created by Jonathan Gamba
* Date: 1/31/13
*/
public class UrlOsgiClassLoader extends URLClassLoader {

    Instrumentation instrumentation;

    private ClassLoader mainClassLoader;

    private Collection<URL> urls;
    private Collection<OSGIClassTransformer> transformers;
    private Long bundleId;

    private HashMap<String, byte[]> originalByteCodes;

    public UrlOsgiClassLoader ( URL url, ClassLoader mainClassLoader, Long bundleId ) throws Exception {

        super( new URL[] { url }, null );

        transformers = new ArrayList<>();
        originalByteCodes = new HashMap<>();

        urls = new ArrayList<>();
        urls.add( url );
        this.bundleId = bundleId;

        //Find and set the instrumentation object
        instrumentation = findInstrumentation();

        //Set our main class loader in order to use it in case we don't find a reference to a class we need to load
        this.mainClassLoader = mainClassLoader;
    }

    @Override
    public Class<?> loadClass ( String name ) throws ClassNotFoundException {
        try {
            return super.loadClass( name );
        } catch ( ClassNotFoundException e ) {
            return mainClassLoader.loadClass( name );
        }
    }

    public Class<?> findClass ( String name ) throws ClassNotFoundException {
        return super.findClass( name );
    }

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

    @Override
    protected void addURL ( URL url ) {

        if ( !urls.contains( url ) ) {
            urls.add( url );
            super.addURL( url );
        }
    }

    public Boolean contains ( URL url ) {
        return urls.contains( url );
    }

    /**
     * Reload all the loaded classes of this custom class loader, by reload we mean to redefine those classes.
     *
     * @throws Exception
     */
    public void reload () throws Exception {

        for ( URL url : urls ) {
            reload( url );
        }
    }

    public void reload ( URL url ) throws Exception {
        reload( url, false );
    }

    /**
     * Reload all the loaded classes of this custom class loader of a given url, by reload we mean to redefine those classes.
     * <br>USE THIS METHOD IF YOU DON'T WANT TO RELOAD ALL THE CLASSES FOR ALL THE URLS ADDED TO THIS CLASSLOADER
     *
     * @throws Exception
     */
    public void reload ( URL url, Boolean restoreOriginal ) throws Exception {

        /*
         Remove our custom class loader from the dotCMS class loaders hierarchy in order to avoid problems redefining
         a class that should not even be loaded like the Activator or any class that should live just in the context
         of the OSGI plugin.
          */
        unlinkClassLoaders();

        File jarFile = new File( url.toURI() );
        JarFile jar = new JarFile( jarFile );

        Enumeration resources = jar.entries();
        while ( resources.hasMoreElements() ) {

            //We will try to redefine class by class
            JarEntry entry = (JarEntry) resources.nextElement();
            if ( !entry.isDirectory() && entry.getName().contains( ".class" ) ) {

                String className = entry.getName().replace( "/", "." ).replace( ".class", "" );

                //We just want to redefine loaded classes, we don't want to load something that will not be use it
                Class currentClass = searchClassForReloading( className );
                if ( currentClass != null ) {

                    try {

                        byte[] byteCode = null;

                        if ( restoreOriginal ) {
                            //Get the original implementation for this class in order to restore it to its original state
                            byteCode = originalByteCodes.get( className );
                        } else {

                            //Save the original implementation of this class before to redefine it
                            if ( !originalByteCodes.containsKey( className ) ) {

                                InputStream originalIn = null;
                                ByteArrayOutputStream originalOut = null;
                                try {
                                    originalIn = currentClass.getClassLoader().getResourceAsStream( entry.getName() );
                                    if ( originalIn == null ) {
                                        originalIn = this.getResourceAsStream( entry.getName() );
                                    }
                                    originalOut = new ByteArrayOutputStream();

                                    byte[] buffer = new byte[1024];
                                    int length;
                                    while ( (length = originalIn.read( buffer )) > 0 ) {
                                        originalOut.write( buffer, 0, length );
                                    }
                                    originalByteCodes.put( className, originalOut.toByteArray() );
                                } catch ( Exception e ) {
                                    Logger.error( this, "Error reading resource [ " + entry.getName() + "]", e );
                                } finally {
                                    if ( originalIn != null ) {
                                        originalIn.close();
                                    }
                                    if ( originalOut != null ) {
                                        originalOut.close();
                                    }
                                }
                            }

                            //Read the implementation we want to use to redefine the class
                            InputStream in = null;
                            ByteArrayOutputStream out = null;
                            try {
                                in = jar.getInputStream( entry );
                                out = new ByteArrayOutputStream();

                                byte[] buffer = new byte[1024];
                                int length;
                                while ( (length = in.read( buffer )) > 0 ) {
                                    out.write( buffer, 0, length );
                                }
                                byteCode = out.toByteArray();
                            } finally {
                                if ( in != null ) {
                                    in.close();
                                }
                                if ( out != null ) {
                                    out.close();
                                }
                            }
                        }

                        if ( byteCode != null ) {

                            //Remove any old transformer created to redefined this class
                            Iterator<OSGIClassTransformer> transformerIterator = transformers.iterator();
                            while ( transformerIterator.hasNext() ) {
                                OSGIClassTransformer osgiClassTransformer = transformerIterator.next();

                                String classToTransform = className.replace( '.', '/' );
                                if ( osgiClassTransformer.getClassName().equals( classToTransform ) ) {
                                    instrumentation.removeTransformer( osgiClassTransformer );
                                    transformerIterator.remove();
                                }
                            }

                            //Creates our transformer, a class that will allows to redefine a class content
                            OSGIClassTransformer transformer = new OSGIClassTransformer( className, currentClass.getClassLoader() );

                            //Adding the transformer
                            transformers.add( transformer );
                            instrumentation.addTransformer( transformer, true );

                            //And finally redefine the class
                            ClassDefinition classDefinition = new ClassDefinition( currentClass, byteCode );
                            instrumentation.redefineClasses( classDefinition );
                        }
                    } catch ( ClassNotFoundException e ) {
                        //If the class has not been loaded we don't need to redefine it
                    }
                }

            }
        }

        //Link again the class loaders
        linkClassLoaders();
    }

    /**
     * Search for a given class. This method will be call it when a class needs to be reload it, in order to do that
     * we will search first on the main classLoader (dotCMS ClassLoader) and if the class is not loaded and exist in dotCMS we
     * want to load it to redefine it with our implementation or changes.
     *
     * @param className
     * @return
     */
    private Class searchClassForReloading ( String className ) {

        Class foundClass;
        try {
            //First let search this class in the main dotCMS class loader
            foundClass = mainClassLoader.loadClass( className );
        } catch ( ClassNotFoundException e ) {
            foundClass = findLoadedClass( className );
        }

        return foundClass;
    }

    /**
     * In order to inject an OSGI bundle class code inside dotCMS context it is required to insert our
     * custom class loader with the OSGI bundle classes inside the dotCMS class loaders hierarchy.
     *
     * @throws Exception
     */
    public void linkClassLoaders () throws Exception {

        //Search for the top class loader in the dotCMS class loaders hierarchy
        ClassLoader topClassLoader = findTopLoader( ClassLoader.getSystemClassLoader() );

        //Inject our custom class loader
        Field parentLoaderField = ClassLoader.class.getDeclaredField( "parent" );
        parentLoaderField.setAccessible( true );
        parentLoaderField.set( topClassLoader, this );
        parentLoaderField.setAccessible( false );
    }

    /**
     * Remove our custom class loader from the dotCMS class loaders hierarchy.
     *
     * @throws Exception
     */
    public void unlinkClassLoaders () throws Exception {

        //Search for the top class loader in the dotCMS class loaders hierarchy
        ClassLoader topClassLoader = findTopLoader( ClassLoader.getSystemClassLoader() );

        //Remove our custom class loader
        Field parentLoaderField = ClassLoader.class.getDeclaredField( "parent" );
        parentLoaderField.setAccessible( true );
        parentLoaderField.set( topClassLoader, this.getParent() );//We will remove this class loader from the way
        parentLoaderField.setAccessible( false );
    }

    /**
     * Search for the top class loader in the dotCMS class loaders hierarchy
     *
     * @param loader base class loader
     * @return The class loader at the top of the dotCMS class loaders hierarchy
     */
    private ClassLoader findTopLoader ( ClassLoader loader ) {

        if ( loader.getParent() == null ) {
            return loader;
        } else {
            if ( loader.getParent() instanceof UrlOsgiClassLoader
                    && ((UrlOsgiClassLoader) loader.getParent()).getBundleId().equals( this.getBundleId() ) ) {
                return loader;
            } else {
                return findTopLoader( loader.getParent() );
            }
        }
    }

    public void close () throws IOException {

        //Remove any transformer defined on this class loader
        Iterator<OSGIClassTransformer> transformerIterator = transformers.iterator();
        while ( transformerIterator.hasNext() ) {
            OSGIClassTransformer osgiClassTransformer = transformerIterator.next();

            instrumentation.removeTransformer( osgiClassTransformer );
            transformerIterator.remove();
        }

        originalByteCodes.clear();
        urls.clear();
        super.close();
    }

    public Long getBundleId () {
        return bundleId;
    }

    public void setBundleId ( Long bundleId ) {
        this.bundleId = bundleId;
    }

    /**
     * We need to access to the instrumentation (This class provides services needed to instrument Java programming language code.
     * Instrumentation is the addition of byte-codes to methods for the purpose of gathering data to be
     * utilized by tools, or on this case to modify on run time classes content in order to reload them.)
     *
     * @return an Instrumentation instance
     * @throws Exception
     */
    public Instrumentation findInstrumentation () throws Exception {

        //Find the instrumentation object
        MemoryMeter mm = new MemoryMeter();

        Field instrumentationField = MemoryMeter.class.getDeclaredField( "instrumentation" );//We need to access it using reflection
        instrumentationField.setAccessible( true );

        Instrumentation instrumentation = (Instrumentation) instrumentationField.get( mm );

        instrumentationField.setAccessible( false );

        return instrumentation;
    }

    @Override
    public URL[] getURLs () {
        return new URL[] { };
    }

}
TOP

Related Classes of com.dotmarketing.osgi.UrlOsgiClassLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.