Created
February 4, 2016 08:30
-
-
Save lutovich/3b677f2b1fbf5ee38109 to your computer and use it in GitHub Desktop.
Neo4j Procedure able to create/execute/drop other procedures from specified JAR files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import java.net.MalformedURLException; | |
import java.net.URL; | |
import java.net.URLClassLoader; | |
import java.nio.file.Path; | |
import java.nio.file.Paths; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.Objects; | |
import java.util.concurrent.locks.Lock; | |
import java.util.concurrent.locks.ReentrantLock; | |
import java.util.stream.Stream; | |
import org.neo4j.graphdb.GraphDatabaseService; | |
import org.neo4j.helpers.Exceptions; | |
import org.neo4j.logging.Log; | |
import org.neo4j.procedure.Context; | |
import org.neo4j.procedure.Name; | |
import org.neo4j.procedure.PerformsWrites; | |
import org.neo4j.procedure.Procedure; | |
import static java.util.stream.Collectors.joining; | |
public class MetaProcedures | |
{ | |
private static Map<String,ProcedureInvoker> procedures = new HashMap<>(); | |
private static Lock lock = new ReentrantLock(); | |
@Context | |
public GraphDatabaseService db; | |
@Context | |
public Log log; | |
@Procedure | |
public Stream<Output> createProcedure( | |
@Name( "jarPath" ) String jarPath, | |
@Name( "name" ) String name ) | |
{ | |
lock.lock(); | |
try | |
{ | |
log.info( "createProcedure(): jarPath: '" + jarPath + "' name: '" + name + "'" ); | |
try | |
{ | |
if ( procedures.containsKey( name ) ) | |
{ | |
log.info( "createProcedure(): Procedure " + name + " already exists" ); | |
return Stream.of( new Output( "FAILURE: Procedure " + name + " already exists" ) ); | |
} | |
String[] classAndMethod = name.split( "\\." ); | |
Object procedure = new JarWorker( jarPath ).createProcedure( classAndMethod[0] ); | |
ProcedureInvoker invoker = new ProcedureInvoker( procedure, classAndMethod[1] ); | |
procedures.put( name, invoker ); | |
return Stream.of( new Output( "SUCCESS: created" ) ); | |
} | |
catch ( Exception e ) | |
{ | |
log.info( "createProcedure(): FAILURE", e ); | |
return failureStream( e ); | |
} | |
} | |
finally | |
{ | |
lock.unlock(); | |
} | |
} | |
@Procedure | |
@PerformsWrites | |
public Stream<Output> executeProcedure( @Name( "name" ) String name ) | |
{ | |
lock.lock(); | |
try | |
{ | |
log.info( "callProcedure(): name: '" + name + "'" ); | |
ProcedureInvoker invoker = procedures.get( name ); | |
if ( invoker == null ) | |
{ | |
log.info( "callProcedure(): Procedure " + name + " not defined" ); | |
return Stream.of( new Output( "FAILURE: Procedure " + name + " not defined" ) ); | |
} | |
try | |
{ | |
return Stream.of( new Output( "SUCCESS: " + invoker.invoke() ) ); | |
} | |
catch ( Exception e ) | |
{ | |
log.info( "callProcedure(): FAILURE", e ); | |
return failureStream( e ); | |
} | |
} | |
finally | |
{ | |
lock.unlock(); | |
} | |
} | |
@Procedure | |
public Stream<Output> dropProcedure( @Name( "name" ) String name ) | |
{ | |
lock.lock(); | |
try | |
{ | |
log.info( "dropProcedure(): name: '" + name + "'" ); | |
ProcedureInvoker invoker = procedures.remove( name ); | |
if ( invoker == null ) | |
{ | |
log.info( "dropProcedure(): Procedure " + name + " does not exist" ); | |
return Stream.of( new Output( "SUCCESS: Procedure " + name + " does not exist" ) ); | |
} | |
return Stream.of( new Output( "SUCCESS: Procedure " + name + " dropped" ) ); | |
} | |
finally | |
{ | |
lock.unlock(); | |
} | |
} | |
private static Stream<Output> failureStream( Exception e ) | |
{ | |
return Stream.of( new Output( "FAILURE: " + Exceptions.stringify( e ) ) ); | |
} | |
public static class Output | |
{ | |
public String value; | |
public Output( String value ) | |
{ | |
this.value = value; | |
} | |
} | |
private class JarWorker | |
{ | |
final ClassLoader classLoader; | |
JarWorker( String jarPath ) | |
{ | |
try | |
{ | |
URL[] urls = {Paths.get( jarPath ).toAbsolutePath().toUri().toURL()}; | |
this.classLoader = new URLClassLoader( urls, MetaProcedures.class.getClassLoader() ); | |
} | |
catch ( MalformedURLException e ) | |
{ | |
throw new RuntimeException( e ); | |
} | |
} | |
Object createProcedure( String name ) | |
{ | |
log.info( "Loading class: " + name ); | |
try | |
{ | |
Class<?> clazz = classLoader.loadClass( name ); | |
Object procedure = clazz.newInstance(); | |
setField( procedure, GraphDatabaseService.class, db ); | |
setField( procedure, Log.class, log ); | |
return procedure; | |
} | |
catch ( Exception e ) | |
{ | |
throw new RuntimeException( e ); | |
} | |
} | |
void setField( Object procedure, Class<?> clazz, Object value ) | |
{ | |
Field[] fields = procedure.getClass().getFields(); | |
for ( Field field : fields ) | |
{ | |
if ( field.isAnnotationPresent( Context.class ) && field.getType().equals( clazz ) ) | |
{ | |
try | |
{ | |
field.set( procedure, value ); | |
} | |
catch ( IllegalAccessException e ) | |
{ | |
throw new RuntimeException( e ); | |
} | |
} | |
} | |
} | |
} | |
private static class ProcedureInvoker | |
{ | |
final Object procedure; | |
final Method method; | |
ProcedureInvoker( Object procedure, String methodName ) throws Exception | |
{ | |
this.procedure = procedure; | |
this.method = findMethod( procedure, methodName ); | |
} | |
String invoke() throws Exception | |
{ | |
Object invocationResult = method.invoke( procedure ); | |
if ( method.getReturnType().equals( void.class ) ) | |
{ | |
return "VOID procedure invoked"; | |
} | |
return ((Stream<?>) invocationResult).map( Objects::toString ).collect( joining( ", " ) ); | |
} | |
Method findMethod( Object procedure, String name ) throws Exception | |
{ | |
Method[] methods = procedure.getClass().getMethods(); | |
for ( Method method : methods ) | |
{ | |
if ( method.getName().equals( name ) && | |
method.isAnnotationPresent( Procedure.class ) && | |
(method.getReturnType().equals( Stream.class ) || method.getReturnType().equals( void.class )) && | |
method.getParameterCount() == 0 ) | |
{ | |
return method; | |
} | |
} | |
throw new RuntimeException( "No suitable method found" ); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage would be:
Person
procedure: https://gist.github.com/lutovich/27e4b8842982b16b8b07