private ComponentRegistry registryWithUnsafeAPI() { ComponentRegistry allComponents = new ComponentRegistry(); allComponents.register( UnsafeAPI.class, ctx -> new UnsafeAPI() ); return allComponents; }
private FieldSetter createInjector( Class<?> cls, Field field ) throws ProcedureException { try { ComponentRegistry.Provider<?> provider = components.providerFor( field.getType() ); if ( provider == null ) { throw new ComponentInjectionException( Status.Procedure.ProcedureRegistrationFailed, "Unable to set up injection for procedure `%s`, the field `%s` " + "has type `%s` which is not a known injectable component.", cls.getSimpleName(), field.getName(), field.getType() ); } MethodHandle setter = MethodHandles.lookup().unreflectSetter( field ); return new FieldSetter( field, setter, provider ); } catch ( IllegalAccessException e ) { throw new ProcedureException( Status.Procedure.ProcedureRegistrationFailed, "Unable to set up injection for `%s`, failed to access field `%s`: %s", e, cls.getSimpleName(), field.getName(), e.getMessage() ); } }
/** * Registers a component, these become available in reflective procedures for injection. * @param cls the type of component to be registered (this is what users 'ask' for in their field declaration) * @param provider a function that supplies the component, given the context of a procedure invocation * @param safe set to false if this component can bypass security, true if it respects security */ public <T> void registerComponent( Class<T> cls, ComponentRegistry.Provider<T> provider, boolean safe ) { if ( safe ) { safeComponents.register( cls, provider ); } allComponents.register( cls, provider ); }
private List<CallableProcedure> compile( Class<?> clazz ) throws KernelException { return new ReflectiveProcedureCompiler( new TypeMappers(), new ComponentRegistry(), new ComponentRegistry(), NullLog.getInstance(), ProcedureConfig.DEFAULT ).compileProcedure( clazz, null, true ); } }
@Test public void shouldInjectLogging() throws KernelException { // Given Log log = spy( Log.class ); components.register( Log.class, ctx -> log ); CallableUserFunction function = procedureCompiler.compileFunction( LoggingFunction.class ).get( 0 ); // When function.apply( new BasicContext(), new AnyValue[0] ); // Then verify( log ).debug( "1" ); verify( log ).info( "2" ); verify( log ).warn( "3" ); verify( log ).error( "4" ); }
@Test public void staticFieldsAreAllowed() throws Throwable { // Given FieldInjections injections = new FieldInjections( new ComponentRegistry() ); // When List<FieldInjections.FieldSetter> setters = injections.setters( ProcedureWithStaticFields.class ); // Then assertEquals( 0, setters.size() ); }
@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 shouldInjectLogging() throws KernelException { // Given Log log = spy( Log.class ); components.register( Log.class, ctx -> log ); CallableProcedure procedure = procedureCompiler.compileProcedure( LoggingProcedure.class, null, true ).get( 0 ); // When procedure.apply( new BasicContext(), new Object[0], resourceTracker ); // Then verify( log ).debug( "1" ); verify( log ).info( "2" ); verify( log ).warn( "3" ); verify( log ).error( "4" ); }
@Test public void shouldNotAllowNonPublicFieldsForInjection() throws Throwable { // Given FieldInjections injections = new FieldInjections( new ComponentRegistry() ); // Expect exception.expect( ProcedureException.class ); exception.expectMessage( "Field `someState` on `ProcedureWithPrivateMemberField` must be non-final and public." ); // When injections.setters( ProcedureWithPrivateMemberField.class ); }
private FieldSetter createInjector( Class<?> cls, Field field ) throws ProcedureException { try { ComponentRegistry.Provider<?> provider = components.providerFor( field.getType() ); if ( provider == null ) { throw new ComponentInjectionException( Status.Procedure.ProcedureRegistrationFailed, "Unable to set up injection for procedure `%s`, the field `%s` " + "has type `%s` which is not a known injectable component.", cls.getSimpleName(), field.getName(), field.getType() ); } MethodHandle setter = MethodHandles.lookup().unreflectSetter( field ); return new FieldSetter( field, setter, provider ); } catch ( IllegalAccessException e ) { throw new ProcedureException( Status.Procedure.ProcedureRegistrationFailed, "Unable to set up injection for `%s`, failed to access field `%s`: %s", e, cls.getSimpleName(), field.getName(), e.getMessage() ); } }
@Test public void inheritanceIsAllowed() throws Throwable { // Given ComponentRegistry components = new ComponentRegistry(); components.register( int.class, ctx -> 1337 ); FieldInjections injections = new FieldInjections( components ); // When List<FieldInjections.FieldSetter> setters = injections.setters( ChildProcedure.class ); // Then ChildProcedure childProcedure = new ChildProcedure(); for ( FieldInjections.FieldSetter setter : setters ) { setter.apply( null, childProcedure ); } assertEquals( 1337, childProcedure.childField ); assertEquals( 1337, childProcedure.parentField ); }
@Test public void shouldInjectLogging() throws KernelException { // Given Log log = spy( Log.class ); components.register( Log.class, ctx -> log ); CallableUserAggregationFunction function = procedureCompiler.compileAggregationFunction( LoggingFunction.class ).get( 0 ); // When UserAggregator aggregator = function.create( new BasicContext() ); aggregator.update( new Object[]{} ); aggregator.result(); // Then verify( log ).debug( "1" ); verify( log ).info( "2" ); verify( log ).warn( "3" ); verify( log ).error( "4" ); }
@Test public void shouldNotLoadNoneWhiteListedFunction() throws Throwable { // Given Log log = spy(Log.class); procedureCompiler = new ReflectiveProcedureCompiler( new TypeMappers(), components, new ComponentRegistry(), log, new ProcedureConfig( Config.defaults( GraphDatabaseSettings.procedure_whitelist, "WrongName" ) ) ); List<CallableUserFunction> method = compile( SingleReadOnlyFunction.class ); verify( log ).warn( "The function 'org.neo4j.kernel.impl.proc.listCoolPeople' is not on the whitelist and won't be loaded." ); assertThat( method.size(), equalTo( 0 ) ); }
@Test public void syntheticsAllowed() throws Throwable { // Given ComponentRegistry components = new ComponentRegistry(); components.register( int.class, ctx -> 1337 ); FieldInjections injections = new FieldInjections( components ); // When List<FieldInjections.FieldSetter> setters = injections.setters( Outer.ClassWithSyntheticField.class ); // Then Outer.ClassWithSyntheticField syntheticField = new Outer().classWithSyntheticField(); for ( FieldInjections.FieldSetter setter : setters ) { setter.apply( null, syntheticField ); } assertEquals( 1337, syntheticField.innerField ); }
/** * Registers a component, these become available in reflective procedures for injection. * @param cls the type of component to be registered (this is what users 'ask' for in their field declaration) * @param provider a function that supplies the component, given the context of a procedure invocation * @param safe set to false if this component can bypass security, true if it respects security */ public <T> void registerComponent( Class<T> cls, ComponentRegistry.Provider<T> provider, boolean safe ) { if ( safe ) { safeComponents.register( cls, provider ); } allComponents.register( cls, provider ); }
@Test public void shouldNotAllowClassesWithNonInjectedFields() throws Throwable { // Given FieldInjections injections = new FieldInjections( new ComponentRegistry() ); // Expect exception.expect( ProcedureException.class ); exception.expectMessage( "Field `someState` on `ProcedureWithNonInjectedMemberFields` " + "is not annotated as a @Context and is not static. " + "If you want to store state along with your procedure, " + "please use a static field." ); // When injections.setters( ProcedureWithNonInjectedMemberFields.class ); }
@Test public void shouldNotLoadNoneWhiteListedFunction() throws Throwable { // Given Log log = spy(Log.class); procedureCompiler = new ReflectiveProcedureCompiler( new TypeMappers(), components, new ComponentRegistry(), log, new ProcedureConfig( Config.defaults( GraphDatabaseSettings.procedure_whitelist, "WrongName" ) ) ); List<CallableUserAggregationFunction> method = compile( SingleAggregationFunction.class ); verify( log ).warn( "The function 'org.neo4j.kernel.impl.proc.collectCool' is not on the whitelist and won't be loaded." ); assertThat( method.size(), equalTo( 0 ) ); }
@Test public void shouldNotLoadAnyFunctionIfConfigIsEmpty() throws Throwable { // Given Log log = spy(Log.class); procedureCompiler = new ReflectiveProcedureCompiler( new TypeMappers(), components, new ComponentRegistry(), log, new ProcedureConfig( Config.defaults( GraphDatabaseSettings.procedure_whitelist, "" ) ) ); List<CallableUserAggregationFunction> method = compile( SingleAggregationFunction.class ); verify( log ).warn( "The function 'org.neo4j.kernel.impl.proc.collectCool' is not on the whitelist and won't be loaded." ); assertThat( method.size(), equalTo( 0 ) ); }
@Test public void shouldNotLoadAnyFunctionIfConfigIsEmpty() throws Throwable { // Given Log log = spy(Log.class); procedureCompiler = new ReflectiveProcedureCompiler( new TypeMappers(), components, new ComponentRegistry(), log, new ProcedureConfig( Config.defaults( GraphDatabaseSettings.procedure_whitelist, "" ) ) ); List<CallableUserFunction> method = compile( SingleReadOnlyFunction.class ); verify( log ).warn( "The function 'org.neo4j.kernel.impl.proc.listCoolPeople' is not on the whitelist and won't be loaded." ); assertThat( method.size(), equalTo( 0 ) ); }
@Test public void shouldReturnEmptySetOnNullArgument() throws Exception { // given ProcedureJarLoader jarloader = new ProcedureJarLoader( new ReflectiveProcedureCompiler( new TypeMappers(), new ComponentRegistry(), registryWithUnsafeAPI(), log, procedureConfig() ), NullLog.getInstance() ); // when ProcedureJarLoader.Callables callables = jarloader.loadProceduresFromDir( null ); // then assertEquals( 0, callables.procedures().size() + callables.functions().size() ); }