Reputation: 26874
I don't understand how to represent a Class
in EL expressions.
I have an EL function that takes a class as parameter (namely an Enum class) to return possible enum values.
I want to invoke it as EL expression. E.g. ${myTld:enumer(com.example.enums.MyEnum)}
However:
MyEnum.class
throws an exception such as the expression cannot be evaluatedHow do I express a class in EL without possibly passing by its string representation?
<function>
<description>
Returns the list of enum values for the given enum class
</description>
<name>enumer</name>
<function-class>com.example.Functions</function-class>
<function-signature>List enumer(java.lang.String)</function-signature>
</function>
${tld:enumer('com.example.MyEnum')}
<function-signature>List enumer(java.lang.Class)</function-signature>
Upvotes: 0
Views: 1242
Reputation: 1108742
You can't represent a Class
in plain EL. You can at most pass it through if it were a bean property like so ${myTld:enumer(bean.someEnumClass)}
returning Class<? extends Enum>
, or ${myTld:enumer(bean.someEnum['class'])}
where someEnum
is the actual Enum
, but that's it. Passing around the class name as String
is really your best bet.
If you're already on EL 3.0 (available on Servlet 3.1 containers like Tomcat 8, WildFly 8, etc), an alternative to your custom function is to just import it right away in JSP like below using the new EL 3.0 ImportHandler
API:
${pageContext.ELContext.importHandler.importClass('com.example.enums.MyEnum')}
The enum is then available by ${MyEnum}
.
<c:forEach items="${MyEnum.values()}" var="myEnumValue">
${myEnumValue}<br/>
</c:forEach>
Alternatively, import the ${MyEnum}
globally via a servlet context listener like below:
@WebListener
public class Config implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
JspFactory.getDefaultFactory().getJspApplicationContext(event.getServletContext()).addELContextListener(new ELContextListener() {
@Override
public void contextCreated(ELContextEvent event) {
event.getELContext().getImportHandler().importClass(MyEnum.class.getName());
}
});
}
// ...
}
Upvotes: 3
Reputation: 1010
I guess you wish to pass Class
arguments to your function to improve performance by avoid parsing strings with Class.forName()
each time... True?
If so, I think there is a way... If you know in advance the classes you will pass your function as arguments, use a ServletContextListener
to put them into your application scope. This may look as follows:
@WebListener
public class MyEnumInitializer implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
Map<String, Class<?>> map = new HashMap<>();
map.put("MyEnum1", MyEnum1.class);
map.put("MyEnum2", MyEnum2.class);
// etc.
sce.getServletContext().addAttribute("myEnums", map);
}
public void contextDestroyed(ServletContextEvent sce) {}
}
If you are using a Servlet API version prior to 3.0, discard the @WebListener
annotation above and declare the listener in your web.xml
file as follows:
<listener>
<listener-class>mypackage.MyEnumListener</listener-class>
</listener>
Having your function that takes a Class
, you could use it as follows:
${tld:enumer(myEnums['MyEnum1'])}
There is still a String
-to-Class
mapping, but it involves locating a Class
object using a String
key in a HashMap
, which is faster than resolving the Class
object using reflection.
Moreover, if you ever need to add to this HashMap
a class you can't foresee as of initializing the application, you can still do it wherever you have access to your ServletContext
(the HashMap
isn't immutable). For example, in any method receiving an HttpServletRequest
as a parameter:
public void aMethod(HttpServletRequest req, [other params]) {
// ...
Map<String, Class<?>> myEnums = (Map<String, Class<?>>) req.getSession().getServletContext().getAttribute("myEnums");
if (!myEnums.containsKey("aNewEnum")) {
myEnums.put("aNewEnum", NewEnum.class);
}
// ...
}
Cheers,
Jeff
Upvotes: 3