use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.
the class ProcedureCompiler method compileAggregationFunction.
List<CallableUserAggregationFunction> compileAggregationFunction(Class<?> fcnDefinition) throws KernelException {
try {
List<Method> methods = Arrays.stream(fcnDefinition.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(UserAggregationFunction.class)).collect(Collectors.toList());
if (methods.isEmpty()) {
return emptyList();
}
assertValidConstructor(fcnDefinition);
List<CallableUserAggregationFunction> out = new ArrayList<>(methods.size());
for (Method method : methods) {
String valueName = method.getAnnotation(UserAggregationFunction.class).value();
String definedName = method.getAnnotation(UserAggregationFunction.class).name();
QualifiedName funcName = extractName(fcnDefinition, method, valueName, definedName);
if (config.isWhitelisted(funcName.toString())) {
out.add(compileAggregationFunction(fcnDefinition, method, funcName));
} else {
log.warn(String.format("The function '%s' is not on the allowlist and won't be loaded.", funcName.toString()));
}
}
out.sort(Comparator.comparing(a -> a.signature().name().toString()));
return out;
} catch (KernelException e) {
throw e;
} catch (Exception e) {
throw new ProcedureException(Status.Procedure.ProcedureRegistrationFailed, e, "Failed to compile function defined in `%s`: %s", fcnDefinition.getSimpleName(), e.getMessage());
}
}
use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.
the class ProcedureRegistry method register.
/**
* Register a new function.
*
* @param function the function.
*/
public void register(CallableUserAggregationFunction function, boolean overrideCurrentImplementation, boolean builtIn) throws ProcedureException {
UserFunctionSignature signature = function.signature();
QualifiedName name = signature.name();
if (functions.get(name) != null) {
throw new ProcedureException(Status.Procedure.ProcedureRegistrationFailed, "Unable to register aggregation function, because the name `%s` is already in use as a function.", name);
}
CallableUserAggregationFunction oldImplementation = aggregationFunctions.get(name);
if (oldImplementation == null) {
aggregationFunctions.put(name, function, signature.caseInsensitive());
} else {
if (overrideCurrentImplementation) {
aggregationFunctions.put(name, function, signature.caseInsensitive());
} else {
throw new ProcedureException(Status.Procedure.ProcedureRegistrationFailed, "Unable to register aggregation function, because the name `%s` is already in use.", name);
}
}
if (builtIn) {
builtInAggregatingFunctionIds.add(aggregationFunctions.idOf(name));
}
}
use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.
the class UserAggregationFunctionTest method shouldNotLoadNoneWhiteListedFunction.
@Test
void shouldNotLoadNoneWhiteListedFunction() throws Throwable {
// Given
Log log = spy(Log.class);
procedureCompiler = new ProcedureCompiler(new TypeCheckers(), components, new ComponentRegistry(), log, new ProcedureConfig(Config.defaults(GraphDatabaseSettings.procedure_allowlist, List.of("WrongName"))));
List<CallableUserAggregationFunction> method = compile(SingleAggregationFunction.class);
verify(log).warn("The function 'org.neo4j.procedure.impl.collectCool' is not on the allowlist and won't be loaded.");
assertThat(method.size()).isEqualTo(0);
}
use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.
the class ProcedureCompilation method compileAggregation.
/**
* Generates code for a user-defined aggregation function.
* <p>
* Given a user-defined aggregation function defined by
*
* <pre>
* class MyClass {
* {@literal @}Context
* public Log log;
*
* {@literal @}UserAggregationFunction
* public Adder create() {
* return new Adder;
* }
* }
*
* class Adder {
* private long sum;
* {@literal @}UserAggregationUpdate
* public void update(long in) {
* sum += in;
* }
* {@literal @}UserAggregationResult
* public long result() {
* return sum;
* }
* }
* }
* </pre>
* <p>
* we will generate two classe looking something like
*
* <pre>
* class Generated implements CallableUserAggregationFunction {
* public static UserFunctionSignature SIGNATURE;
* public static FieldSetter SETTER_0;
*
* public UserAggregator create(Context ctx) {
* try {
* MyClass userClass = new MyClass();
* userClass.log = (Log) SETTER_0.get(ctx);
* return new GeneratedAdder(userClass.create());
* } catch (Throwable T) {
* throw new ProcedureException([appropriate error msg], T);
* }
* }
*
* public UserFunctionSignature signature() {return SIGNATURE;}
* }
* class GeneratedAdder implements UserAggregator {
* private Adder adder;
*
* GeneratedAdder(Adder adder) {
* this.adder = adder;
* }
*
* void update(AnyValue[] in) {
* adder.update(((NumberValue) in).longValue());
* }
*
* AnyValue result() {
* return adder.result(Values.longValue(adder.result());
* }
* }
* </pre>
* <p>
* where the static fields are set once during loading via reflection.
*
* @param signature the signature of the user-defined function
* @param fieldSetters the fields to set before each call.
* @param create the method that creates the aggregator
* @param update the update method of the aggregator
* @param result the result method of the aggregator
* @return a CallableUserFunction delegating to the underlying user-defined function.
* @throws ProcedureException if something went wrong when compiling the user-defined function.
*/
static CallableUserAggregationFunction compileAggregation(UserFunctionSignature signature, List<FieldSetter> fieldSetters, Method create, Method update, Method result) throws ProcedureException {
ClassHandle handle;
try {
CodeGenerator codeGenerator = codeGenerator();
Class<?> aggregator = generateAggregator(codeGenerator, update, result, signature);
try (ClassGenerator generator = codeGenerator.generateClass(PACKAGE, className(signature), CallableUserAggregationFunction.class)) {
// static fields
FieldReference signatureField = generator.publicStaticField(typeReference(UserFunctionSignature.class), SIGNATURE_NAME);
List<FieldReference> fieldsToSet = createContextSetters(fieldSetters, generator);
// CallableUserAggregationFunction::create
try (CodeBlock method = generator.generate(AGGREGATION_CREATE)) {
method.tryCatch(body -> createAggregationBody(body, fieldSetters, fieldsToSet, create, aggregator), onError -> onError(onError, format("function `%s`", signature.name())), param(Throwable.class, "T"));
}
// CallableUserFunction::signature
try (CodeBlock method = generator.generateMethod(UserFunctionSignature.class, "signature")) {
method.returns(getStatic(signatureField));
}
handle = generator.handle();
}
Class<?> clazz = handle.loadClass();
// set all static fields
setAllStaticFields(signature, fieldSetters, create, clazz);
return (CallableUserAggregationFunction) clazz.getConstructor().newInstance();
} catch (Throwable e) {
throw new ProcedureException(Status.Procedure.ProcedureRegistrationFailed, e, "Failed to compile function defined in `%s`: %s", create.getDeclaringClass().getSimpleName(), e.getMessage());
}
}
use of org.neo4j.kernel.api.procedure.CallableUserAggregationFunction in project neo4j by neo4j.
the class ResourceInjectionTest method shouldCompileAndRunUserAggregationFunctions.
@Test
void shouldCompileAndRunUserAggregationFunctions() throws Throwable {
// Given
CallableUserAggregationFunction proc = compiler.compileAggregationFunction(AggregationFunctionWithInjectedAPI.class).get(0);
// When
proc.create(prepareContext()).update(new AnyValue[] {});
AnyValue out = proc.create(prepareContext()).result();
// Then
assertThat(out).isEqualTo(stringValue("[Bonnie, Clyde]"));
}
Aggregations