Apache Daffodil allows execution of Java/Scala external/user defined functions in DFDL Expressions.
The implementer will be expected to provide a JAR via the classpath or Daffodil classpath containing at least 2 classes: a provider class and its associated function class(es).
All providers must be registered in the META-INF/services/org.apache.daffodil.udf.UserDefinedFunctionProvider file, regardless of how many are provided.
The provider class must extend Daffodil's UserDefinedFuntionProvider
class. This class must implement the getUserDefinedFunctionClasses
abstract method, and ensure it returns all the User Defined Functions this class is providing. The UserDefinedFunctionProvider
class provides a createUserDefinedFunction
to lookup and initialize User Defined Functions that have no-argument constructors, based on a supplied name and namespaceURI. If the User Defined Function has a constructor that takes arguments, then the default lookup function cannot be used, and the class must override the look up function with its own implementation for such UDFs.
This class will act as a traditional service provider as explained in the ServiceLoader API, and must have a META-INF/services/org.apache.daffodil.udf.UserDefinedFunctionProvider file in its project. This file must contain the fully qualified name(s) of the provider class(es) in the JAR. Without that file, neither this class nor any of the User Defined Function classes it provides will be visible to Daffodil.
The class can provide as many User Defined Function classes as it wishes, as long as they are made available by the getUserDefinedFunctionClasses
function.
The User Defined Function class must extend Daffodil's UserDefinedFunction
class. This class will contain the actual functionality implementers wish to add to DFDL Expressions. It must be annotated with the UserDefinedFunctionIdentification
annotation class, with the name and namespaceURI fields filled in with how the UDF will be called from the schema. It must also implement a function named evaluate that Daffodil will call to execute the desired UDF functionality. There is no support for overloaded or void evaluate functions.
Daffodil proves two exception classes for users that wish to throw an error from within their UDFs.
UserDefinedFunctionProcessingError
can be thrown when the implementer wishes to cause backtracking during parsingUserDefinedFunctionFatalException
can be thrown when the implementer wishes processing to be aborted all together
All other exceptions are treated as UserDefinedFunctionFatalException
.
Once implemented, a UDF can be expected to have one of the following structures
// sample scala UDF with sbt structure
// based on sample UDF in daffodil-udf/src/test/scala
src/
main/
scala/
org/
sgoodudfs/
example/
StringFunctions/
StringFunctionsProvider.scala //UDF Provider with UDF classes declared within
resources/
META-INF/
services/
org.apache.daffodil.udf.UserDefinedFunctionProvider
or
// sample java UDF with generic structure
// based on sample UDF in daffodil-udf/src/test/java
src/
org/
jgoodudfs/
example/
StringFunctions/
StringFunctionsProvider.java //UDF Provider
Compare.java //UDF class
Replace.java //UDF class
META-INF/
services/
org.apache.daffodil.udf.UserDefinedFunctionProvider
Each UDF is registered by including the fully qualified name of its provider in a text file named META-INF/services/org.apache.daffodil.udf.UserDefinedFunctionProvider
. The META-INF folder must be accessible from the root of whatever paths are on the classpath, otherwise it won't be picked up by ServiceLoader.
To use within a DFDL expression, you will need to either define an xsd namespace or set as your default namespace a value that matches the namespaceURI of the UDF's annotation. Then you can call the function with the name field of the function class's annotation. For example:
<!--
For a UDF with the following annotation
@UserDefinedFunctionIdentification(
name = "replace",
namespaceURI = "http://example.com/ext/stringfunctions")
-->
<!-- within the schema tag -->
xmlns:sdf="http://example.com/ext/stringfunctions"
<!-- within the DFDL expression -->
..."{ sdf:replace(., ' ', '_') }"...
- BigDecimal: Java
- BigInteger: Java
- Boolean: Boxed and Primitive
- Byte: Boxed, Primitive, Array of Primitive
- Double: Boxed and Primitive
- Float: Boxed and Primitive
- Integer: Boxed and Primitive
- Long: Boxed and Primitive
- Short: Boxed and Primitive
- String
- Overloading unsupported
- Void functions unsupported