diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/proc/ResourceInjectionTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/proc/ResourceInjectionTest.java index 6a67a4d9e8d61..5f5961bb7b782 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/proc/ResourceInjectionTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/proc/ResourceInjectionTest.java @@ -19,6 +19,7 @@ */ package org.neo4j.kernel.impl.proc; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -28,13 +29,18 @@ import java.util.stream.Stream; import org.neo4j.helpers.collection.Iterators; -import org.neo4j.kernel.api.exceptions.KernelException; import org.neo4j.kernel.api.exceptions.ProcedureException; import org.neo4j.kernel.api.proc.BasicContext; 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; import org.neo4j.procedure.Context; import org.neo4j.procedure.Procedure; +import org.neo4j.procedure.UserAggregationFunction; +import org.neo4j.procedure.UserAggregationResult; +import org.neo4j.procedure.UserAggregationUpdate; +import org.neo4j.procedure.UserFunction; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -51,13 +57,29 @@ public class ResourceInjectionTest @Rule public ExpectedException exception = ExpectedException.none(); + private ReflectiveProcedureCompiler compiler; + private Log log = mock(Log.class); + @Before + public void setUp() + { + ComponentRegistry safeComponents = new ComponentRegistry(); + ComponentRegistry allComponents = new ComponentRegistry(); + safeComponents.register( MyAwesomeAPI.class, ( ctx ) -> new MyAwesomeAPI() ); + allComponents.register( MyAwesomeAPI.class, ( ctx ) -> new MyAwesomeAPI() ); + allComponents.register( MyUnsafeAPI.class, ( ctx ) -> new MyUnsafeAPI() ); + + compiler = new ReflectiveProcedureCompiler( new TypeMappers(), safeComponents, allComponents, log, + ProcedureConfig.DEFAULT ); + } + @Test public void shouldCompileAndRunProcedure() throws Throwable { // Given - CallableProcedure proc = compile( ProcedureWithInjectedAPI.class, true ).get( 0 ); + CallableProcedure proc = + compiler.compileProcedure( ProcedureWithInjectedAPI.class, Optional.empty(), true ).get( 0 ); // Then List out = Iterators.asList( proc.apply( new BasicContext(), new Object[0] ) ); @@ -72,12 +94,12 @@ public void shouldFailNicelyWhenUnknownAPI() throws Throwable { //When exception.expect( ProcedureException.class ); - exception.expectMessage( "Unable to set up injection for procedure `procedureWithUnknownAPI`, " + + exception.expectMessage( "Unable to set up injection for procedure `ProcedureWithUnknownAPI`, " + "the field `api` has type `class org.neo4j.kernel.impl.proc.ResourceInjectionTest$UnknownAPI` " + "which is not a known injectable component." ); // Then - compile( procedureWithUnknownAPI.class, true ); + compiler.compileProcedure( ProcedureWithUnknownAPI.class, Optional.empty(), true ); } @@ -85,7 +107,8 @@ public void shouldFailNicelyWhenUnknownAPI() throws Throwable public void shouldCompileAndRunUnsafeProcedureUnsafeMode() throws Throwable { // Given - CallableProcedure proc = compile( procedureWithUnsafeAPI.class, true ).get( 0 ); + CallableProcedure proc = + compiler.compileProcedure( ProcedureWithUnsafeAPI.class, Optional.empty(), true ).get( 0 ); // Then List out = Iterators.asList( proc.apply( new BasicContext(), new Object[0] ) ); @@ -101,7 +124,8 @@ public void shouldCompileAndRunUnsafeProcedureUnsafeMode() throws Throwable public void shouldFailNicelyWhenUnsafeAPISafeMode() throws Throwable { //When - List procList = compile( procedureWithUnsafeAPI.class, false ); + List procList = + compiler.compileProcedure( ProcedureWithUnsafeAPI.class, Optional.empty(), false ); verify( log ) .warn( "org.neo4j.kernel.impl.proc.listCoolPeople is not " + "available due to not having unrestricted access rights, check configuration." ); @@ -121,6 +145,134 @@ public void shouldFailNicelyWhenUnsafeAPISafeMode() throws Throwable } + @Test + public void shouldCompileAndRunUserFunctions() throws Throwable + { + // Given + CallableUserFunction proc = + compiler.compileFunction( FunctionWithInjectedAPI.class).get( 0 ); + + // When + Object out = proc.apply( new BasicContext(), new Object[0] ); + + // Then + assertThat( out, equalTo( "[Bonnie, Clyde]" ) ); + } + + @Test + public void shouldFailNicelyWhenFunctionUsesUnknownAPI() throws Throwable + { + //When + exception.expect( ProcedureException.class ); + exception.expectMessage( "Unable to set up injection for procedure `FunctionWithUnknownAPI`, " + + "the field `api` has type `class org.neo4j.kernel.impl.proc.ResourceInjectionTest$UnknownAPI` " + + "which is not a known injectable component." ); + + // Then + compiler.compileFunction( FunctionWithUnknownAPI.class ); + + } + + @Test + public void shouldFailNicelyWhenUnsafeAPISafeModeFunction() throws Throwable + { + //When + List procList = + compiler.compileFunction( FunctionWithUnsafeAPI.class); + verify( log ) + .warn( "org.neo4j.kernel.impl.proc.listCoolPeople is not " + + "available due to not having unrestricted access rights, check configuration." ); + + assertThat( procList.size(), equalTo( 1 ) ); + try + { + procList.get( 0 ).apply( new BasicContext(), new Object[0] ); + fail(); + } + catch ( ProcedureException e ) + { + assertThat( e.getMessage(), containsString( + "org.neo4j.kernel.impl.proc.listCoolPeople is not " + + "available due to not having unrestricted access rights, check configuration." ) ); + } + + } + + @Test + public void shouldCompileAndRunUserAggregationFunctions() throws Throwable + { + // Given + CallableUserAggregationFunction proc = + compiler.compileAggregationFunction( AggregationFunctionWithInjectedAPI.class).get( 0 ); + // When + proc.create( new BasicContext() ).update( new Object[]{} ); + Object out = proc.create( new BasicContext() ).result(); + + + // Then + assertThat( out, equalTo( "[Bonnie, Clyde]" ) ); + } + + @Test + public void shouldFailNicelyWhenAggregationFunctionUsesUnknownAPI() throws Throwable + { + //When + exception.expect( ProcedureException.class ); + exception.expectMessage( "Unable to set up injection for procedure `AggregationFunctionWithUnknownAPI`, " + + "the field `api` has type `class org.neo4j.kernel.impl.proc.ResourceInjectionTest$UnknownAPI` " + + "which is not a known injectable component." ); + + // Then + compiler.compileAggregationFunction( AggregationFunctionWithUnknownAPI.class ); + + } + + @Test + public void shouldFailNicelyWhenUnsafeAPISafeModeAggregationFunction() throws Throwable + { + //When + List procList = + compiler.compileAggregationFunction( AggregationFunctionWithUnsafeAPI.class); + verify( log ) + .warn( "org.neo4j.kernel.impl.proc.listCoolPeople is not " + + "available due to not having unrestricted access rights, check configuration." ); + + assertThat( procList.size(), equalTo( 1 ) ); + try + { + procList.get(0).create( new BasicContext() ).update( new Object[]{} ); + Object out = procList.get(0).create( new BasicContext() ).result(); + fail(); + } + catch ( ProcedureException e ) + { + assertThat( e.getMessage(), containsString( + "org.neo4j.kernel.impl.proc.listCoolPeople is not " + + "available due to not having unrestricted access rights, check configuration." ) ); + } + + } + + @Test + public void shouldFailNicelyWhenAllUsesUnsafeAPI() throws Throwable + { + //When + compiler.compileFunction( FunctionsAndProcedureUnsafe.class ); + compiler.compileProcedure( FunctionsAndProcedureUnsafe.class, Optional.empty(), false ); + compiler.compileAggregationFunction( FunctionsAndProcedureUnsafe.class ); + // Then + + verify( log ) + .warn( "org.neo4j.kernel.impl.proc.listCoolPeople is not " + + "available due to not having unrestricted access rights, check configuration." ); + verify( log ) + .warn( "org.neo4j.kernel.impl.proc.safeUserFunctionInUnknownAPIClass is not " + + "available due to not having unrestricted access rights, check configuration." ); + verify( log ) + .warn( "org.neo4j.kernel.impl.proc.listCoolPeopleProcedure is not " + + "available due to not having unrestricted access rights, check configuration." ); + } + public static class MyOutputRecord { public String name; @@ -169,7 +321,50 @@ public Stream listCoolPeople() } } - public static class procedureWithUnknownAPI + public static class FunctionWithInjectedAPI + { + @Context + public MyAwesomeAPI api; + + @UserFunction + public String listCoolPeople() + { + return api.listCoolPeople().toString(); + } + } + + public static class AggregationFunctionWithInjectedAPI + { + @Context + public MyAwesomeAPI api; + + @UserAggregationFunction + public VoidOutput listCoolPeople() + { + return new VoidOutput(api); + } + + public static class VoidOutput + { + private MyAwesomeAPI api; + public VoidOutput(MyAwesomeAPI api){ + this.api=api; + } + @UserAggregationUpdate + public void update() + { + } + + @UserAggregationResult + public String result() + { + return api.listCoolPeople().toString(); + } + } + } + + + public static class ProcedureWithUnknownAPI { @Context public UnknownAPI api; @@ -181,7 +376,49 @@ public Stream listCoolPeople() } } - public static class procedureWithUnsafeAPI + public static class FunctionWithUnknownAPI + { + @Context + public UnknownAPI api; + + @UserFunction + public String listCoolPeople() + { + return api.listCoolPeople().toString(); + } + } + + public static class AggregationFunctionWithUnknownAPI + { + @Context + public UnknownAPI api; + + @UserAggregationFunction + public VoidOutput listCoolPeople() + { + return new VoidOutput(api); + } + + public static class VoidOutput + { + private UnknownAPI api; + public VoidOutput(UnknownAPI api){ + this.api=api; + } + @UserAggregationUpdate + public void update() + { + } + + @UserAggregationResult + public String result() + { + return api.listCoolPeople().toString(); + } + } + } + + public static class ProcedureWithUnsafeAPI { @Context public MyUnsafeAPI api; @@ -193,15 +430,90 @@ public Stream listCoolPeople() } } - private List compile( Class clazz, boolean safe ) throws KernelException + public static class FunctionWithUnsafeAPI { - ComponentRegistry safeComponents = new ComponentRegistry(); - ComponentRegistry allComponents = new ComponentRegistry(); - safeComponents.register( MyAwesomeAPI.class, (ctx) -> new MyAwesomeAPI() ); - allComponents.register( MyAwesomeAPI.class, (ctx) -> new MyAwesomeAPI() ); - allComponents.register( MyUnsafeAPI.class, (ctx) -> new MyUnsafeAPI() ); + @Context + public MyUnsafeAPI api; + + @UserFunction + public String listCoolPeople() + { + return api.listCoolPeople().toString(); + } + } + public static class AggregationFunctionWithUnsafeAPI + { + @Context + public MyUnsafeAPI api; + + @UserAggregationFunction + public VoidOutput listCoolPeople() + { + return new VoidOutput(api); + } - return new ReflectiveProcedureCompiler( new TypeMappers(), safeComponents, allComponents, log, - ProcedureConfig.DEFAULT ).compileProcedure( clazz, Optional.empty(), safe ); + public static class VoidOutput + { + private MyUnsafeAPI api; + public VoidOutput(MyUnsafeAPI api){ + this.api=api; + } + @UserAggregationUpdate + public void update() + { + } + + @UserAggregationResult + public String result() + { + return api.listCoolPeople().toString(); + } + } + } + + public static class FunctionsAndProcedureUnsafe + { + @Context + public MyUnsafeAPI api; + + @UserAggregationFunction + public VoidOutput listCoolPeople() + { + return new VoidOutput( api ); + } + + public static class VoidOutput + { + private MyUnsafeAPI api; + + public VoidOutput( MyUnsafeAPI api ) + { + this.api = api; + } + + @UserAggregationUpdate + public void update() + { + } + + @UserAggregationResult + public String result() + { + return api.listCoolPeople().toString(); + } + } + + @Procedure + public Stream listCoolPeopleProcedure() + { + return api.listCoolPeople().stream().map( MyOutputRecord::new ); + } + + @UserFunction + public String safeUserFunctionInUnknownAPIClass() + { + return "a safe function"; + } } + }