Extending EQL
The EntityQuery
infrastructure provides some hooks you can use to extend the EQL support with application specific methods.
Custom value conversion
When converting an EQL query all value arguments are first converted to an EQType
representation before being converted into their respective Java type.
Actual type conversion is then done via the Spring ConversionService
.
To create a custom conversion you can simply register a Converter
that converts from the relevant EQType
to the property type.
The following table shows how EQL arguments will be converted to their respective EQType
:
Argument value | EQType |
---|---|
name |
|
'name' |
|
(name, 'name') |
- - |
users(name, 'name') |
[arguments] - - |
By default EntityModule registers id-based lookups for all its registered entities.
So supposing you have an entity User
with id 1 and you want to query on a property creator of type User
, the following query would work: creator = 1
.
When building the EntityQuery
the value 1 would be used as the id to find the User
instance, and the latter would be used as the argument for the final query.
If we want to replace the custom behavior and allow the user to be specified by username instead, we could easily register a custom converter.
public class EQValueToUserConverter implements Converter<EQValue, User>
{
...
@Override
public User convert( EQValue source ) {
return userRepository.findByUsername( source.getValue() );
}
}
...
converterRegistry.addConverter( new EQValueToUserConverter(...) );
This would allow us to execute the queries like creator = john
or creator in (john, jane)
.
Any type-specific converter will take precedence over the defaults.
NOTE The example above would only work if the username can never contain any whitespace. If it can, then we would have to specify it as a String instead and write a converter for
EQString
instead ofEQValue
.
Adding custom functions
An EQL function is represented by a unique name and can optionally take a number of arguments for its execution.
Adding custom functions is as easy as simply defining a @Component
that implements the EntityQueryFunctionHandler
interface.
All components of this type will be detected and checked when executing an EQL query.
The handler will be called with the required contextual data for the return type requested.
If you want to use a function to compare a property that has a Date
type, your function should return a Date
instance as well.
A single handler can support multiple functions and requested return types.
Simple EntityQuery function that always returns the String hello
/**
* Simple EntityQuery function that always returns the String 'hello'.
* Example eql: name = hello() or name in (hello(), 'goodbye')
*/
@Component
public class HelloFunction implements EntityQueryFunctionHandler
{
@Override
public boolean accepts( String functionName, TypeDescriptor desiredType ) {
return "hello".equals( functionName );
}
@Override
public Object apply( String functionName,
EQType[] arguments,
TypeDescriptor desiredType,
EQTypeConverter argumentConverter ) {
return "hello";
}
}
Custom EQL translation
You can register an EntityQueryConditionTranslator
attribute on any entity property.
If a translator instance is present, it will be called during the parsing phase of an EQL statement into an EntityQuery
.
Ensuring a field search is always case insensitive
configuration
.withType( Group.class )
.properties(
props -> props.property( "name" ).attribute( EntityQueryConditionTranslator.class, EntityQueryConditionTranslator.ignoreCase() )
)
Define a search text property that actually searches on other fields
configuration.withType( Note.class )
.properties( props -> props.property( "text" )
.valueFetcher( entity -> "" )
.propertyType( TypeDescriptor.valueOf( String.class ) )
.viewElementType( ViewElementMode.CONTROL, BootstrapUiElements.TEXTAREA )
.attribute( EntityQueryConditionTranslator.class,
EntityQueryConditionTranslator.expandingOr( "name", "content" ) )
.hidden( true )
)