Affects: All versions
I am using SpEL in a platform I have built where users are able to set expressions which refer to dynamic types also created by them.
So, for example, consider the following structure is available at evaluation runtime:
public class Type implements java.lang.reflect.Type {
}
public class Object extends Type {
private String name;
private List<ObjectProperty> properties = new ArrayList<>();
and
public class ObjectProperty {
private String name;
private Type type;
public class EnumType extends Type {
private final String name;
private final List<String> values;
And, for example, the following dynamic types defined (defined statically in this example but created by user at runtime via a UI):
private static final EnumType genre = new EnumType("Genre", List.of("FICTION", "STORY_TELLING", "DRAMA"));
private static final Object authorType = new Object("Author",List.of(
new ObjectProperty("id", new BasicType(Long.class)),
new ObjectProperty("name", new BasicType(String.class))
));
private static final Object bookType = new Object("Book",
List.of(
new ObjectProperty("id", new BasicType(Long.class)),
new ObjectProperty("name", new BasicType(String.class)),
new ObjectProperty("author", authorType),
new ObjectProperty("genre", genre)
));
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
Expression expression = spelExpressionParser.parseExpression("#book");
StandardEvaluationContextcontext = new StandardEvaluationContext();
context.setVariable("book", bookType);
return expression.getValueTypeDescriptor(context);
I would want this to return a TypeDescriptor
that describes the Book
dynamic Type
, but unfortunately it does not due to the implementation in VariableReference
:
@Override
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
if (this.name.equals(THIS)) {
return state.getActiveContextObject();
}
if (this.name.equals(ROOT)) {
TypedValue result = state.getRootContextObject();
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue());
return result;
}
TypedValue result = state.lookupVariable(this.name);
//////// Perhaps I misunderstand something, but shouldn't the exitTypeDescriptor be taken
//////// directly from result.typeDescriptor?
Object value = result.getValue();
if (value == null || !Modifier.isPublic(value.getClass().getModifiers())) {
// If the type is not public then when generateCode produces a checkcast to it
// then an IllegalAccessError will occur.
// If resorting to Object isn't sufficient, the hierarchy could be traversed for
// the first public type.
this.exitTypeDescriptor = "Ljava/lang/Object";
}
else {
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(value);
}
// a null value will mean either the value was null or the variable was not found
return result;
}
There is also the issue of TypeLocator
not supporting java.lang.reflect.Type
but rather java.lang.Class
directly. This would help as I could do:
if (type instanceof Object object) {
return new TypedValue(object, new TypeDescriptor(ResolvableType.forType(object), null, null));
}
The use case being this expression: T(Genre).FICTION
.
I was able to get all this working using extenders and reflection hacking.
Comment From: sbrannen
java //////// Perhaps I misunderstand something, but shouldn't the exitTypeDescriptor be taken //////// directly from result.typeDescriptor?
No.
result.typeDescriptor
is a TypeDescriptor
; whereas, exitTypeDescriptor
is a String
which is included in the byte code generated during expression compilation.
Comment From: sbrannen
Hi @asafbennatan,
Congratulations on submitting your first proposal for the Spring Framework! 👍
That's certainly an interesting idea; however, support for dynamic type systems is beyond the scope of the Spring Expression Language (SpEL).
In light of that, I am closing this issue.