All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.impl.proc.ProcedureJarLoader Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2020 "Graph Foundation,"
 * Graph Foundation, Inc. [https://graphfoundation.org]
 *
 * This file is part of ONgDB.
 *
 * ONgDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
/*
 * Copyright (c) 2002-2020 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.kernel.impl.proc;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import org.neo4j.collection.PrefetchingRawIterator;
import org.neo4j.collection.RawIterator;
import org.neo4j.internal.kernel.api.exceptions.KernelException;
import org.neo4j.kernel.api.proc.CallableProcedure;
import org.neo4j.kernel.api.proc.CallableUserAggregationFunction;
import org.neo4j.kernel.api.proc.CallableUserFunction;
import org.neo4j.logging.Log;

/**
 * Given the location of a jarfile, reads the contents of the jar and returns compiled {@link CallableProcedure}
 * instances.
 */
public class ProcedureJarLoader
{
    private final ReflectiveProcedureCompiler compiler;
    private final Log log;

    ProcedureJarLoader( ReflectiveProcedureCompiler compiler, Log log )
    {
        this.compiler = compiler;
        this.log = log;
    }

    public Callables loadProceduresFromDir( File root ) throws IOException, KernelException
    {
        if ( root == null || !root.exists() )
        {
            return Callables.empty();
        }

        Callables out = new Callables();

        File[] dirListing = root.listFiles( ( dir, name ) -> name.endsWith( ".jar" ) );

        if ( dirListing == null )
        {
            return Callables.empty();
        }

        if ( !allJarFilesAreValidZipFiles( Stream.of( dirListing ) ) )
        {
            throw new ZipException( "Some jar procedure files are invalid, see log for details." );
        }

        URL[] jarFilesURLs = Stream.of( dirListing ).map( this::toURL ).toArray( URL[]::new );

        URLClassLoader loader = new URLClassLoader( jarFilesURLs, this.getClass().getClassLoader() );

        for ( URL jarFile : jarFilesURLs )
        {
            loadProcedures( jarFile, loader, out );
        }
        return out;
    }

    private boolean allJarFilesAreValidZipFiles( Stream jarFiles )
    {
        return jarFiles.allMatch( jarFile ->
        {
            try
            {
                new ZipFile( jarFile ).close();
                return true;
            }
            catch ( IOException e )
            {
                log.error( String.format( "Plugin jar file: %s corrupted.", jarFile ) );
                return false;
            }
        } );
    }

    private Callables loadProcedures( URL jar, ClassLoader loader, Callables target )
            throws IOException, KernelException
    {
        RawIterator,IOException> classes = listClassesIn( jar, loader );
        while ( classes.hasNext() )
        {
            Class next = classes.next();
            target.addAllProcedures( compiler.compileProcedure( next, null, false ) );
            target.addAllFunctions( compiler.compileFunction( next, false ) );
            target.addAllAggregationFunctions( compiler.compileAggregationFunction( next ) );
        }
        return target;
    }

    private URL toURL( File f )
    {
        try
        {
            return f.toURI().toURL();
        }
        catch ( MalformedURLException e )
        {
            throw new RuntimeException( e );
        }
    }

    private RawIterator,IOException> listClassesIn( URL jar, ClassLoader loader ) throws IOException
    {
        ZipInputStream zip = new ZipInputStream( jar.openStream() );

        return new PrefetchingRawIterator,IOException>()
        {
            @Override
            protected Class fetchNextOrNull() throws IOException
            {
                try
                {
                    while ( true )
                    {
                        ZipEntry nextEntry = zip.getNextEntry();
                        if ( nextEntry == null )
                        {
                            zip.close();
                            return null;
                        }

                        String name = nextEntry.getName();
                        if ( name.endsWith( ".class" ) )
                        {
                            String className =
                                    name.substring( 0, name.length() - ".class".length() ).replace( '/', '.' );

                            try
                            {
                                Class aClass = loader.loadClass( className );
                                // We do getDeclaredMethods to trigger NoClassDefErrors, which loadClass above does
                                // not do.
                                // This way, even if some of the classes in a jar cannot be loaded, we still check
                                // the others.
                                aClass.getDeclaredMethods();
                                return aClass;
                            }
                            catch ( UnsatisfiedLinkError | NoClassDefFoundError | Exception e )
                            {
                                log.warn( "Failed to load `%s` from plugin jar `%s`: %s", className, jar.getFile(),
                                        e.getMessage() );
                            }
                        }
                    }
                }
                catch ( IOException | RuntimeException e )
                {
                    zip.close();
                    throw e;
                }
            }
        };
    }

    public static class Callables
    {
        private final List procedures = new ArrayList<>();
        private final List functions = new ArrayList<>();
        private final List aggregationFunctions = new ArrayList<>();

        public void add( CallableProcedure proc )
        {
            procedures.add( proc );
        }

        public void add( CallableUserFunction func )
        {
            functions.add( func );
        }

        public List procedures()
        {
            return procedures;
        }

        public List functions()
        {
            return functions;
        }

        public List aggregationFunctions()
        {
            return aggregationFunctions;
        }

        void addAllProcedures( List callableProcedures )
        {
            procedures.addAll( callableProcedures );
        }

        void addAllFunctions( List callableFunctions )
        {
            functions.addAll( callableFunctions );
        }

        public void addAllAggregationFunctions( List callableFunctions )
        {
            aggregationFunctions.addAll( callableFunctions );
        }

        private static Callables EMPTY = new Callables();

        public static Callables empty()
        {
            return EMPTY;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy