In JSF 2.0, if a message is not found in the message bundle, then by default, the key is surrounded with ???. This is a very usable hint during development. However, in my particular case, I really would like that those ??? were not present. I prefer that only the key would be rendered.
E.g. when I do
#{msg.hello}
and the key 'hello' doesn't exist, then the page displays
???hello???
but I would like to show the bare key
hello
The message bundle is loaded in a JSF page as follows:
<f:loadBundle basename="resources.text" var="msg" />
The <f:loadBundle> tag doesn't seem to have an attribute to manipulate the way values are retrieved from that bundle. Should I overwrite some class or how to intercept the way messages are retrieved from the bundle?
I've found a very interesting article on this: Context Sensitive Resource Bundle entries in JavaServer Faces applications – going beyond plain language, region & variant locales. However, in my case, I just want to omit the ???. I think this solution is rather complicated. How can I achieve it anyway?
The basename can point to a fullworthy ResourceBundle class. E.g.
<f:loadBundle basename="resources.Text" var="msg" />
with
package resources;
public class Text extends ResourceBundle {
public Text() {
setParent(getBundle("resources.text", FacesContext.getCurrentInstance().getViewRoot().getLocale()));
}
#Override
public Enumeration<String> getKeys() {
return parent.getKeys();
}
#Override
protected Object handleGetObject(String key) {
return parent.getObject(key);
}
}
You can overridde the bundle message handling in handleGetObject. JSF by default (by spec) calls getObject(), catches MissingResourceException and returns "???" + key + "???" when caught. You can do it differently.
#Override
protected Object handleGetObject(String key) {
try {
return parent.getObject(key);
} catch (MissingResourceException e) {
return key;
}
}
You could also create a simple bean that takes care of the string manipulation. This approach is a lot better if you don't need to remove the default surroundings everywhere but only on a specific place(s). The second function is a lot safer to use, since it also takes care of the case where translation starts and ends with the ???.
#ApplicationScoped
#Named
public class LocaleUtils {
public String getMessage(String s) {
return clearMessage(s);
}
public Object getMessage(ResourceBundle propertyResourceBundle, String key) {
try {
return propertyResourceBundle.getObject(key);
}
catch (MissingResourceException e) {
return clearMessage(key);
}
}
private static String clearMessage(String s) {
String clearMessage = s;
String prefix = "???", suffix = "???";
if (s != null && s.startsWith(prefix) && s.endsWith(suffix)) {
s = s.substring(prefix.length());
clearMessage = s.substring(0, s.length() - suffix.length());
}
return clearMessage;
}
}
Usage:
<h:outputText value="#{localeUtils.getMessage(msg['hello'])}"/>
<h:outputText value="#{localeUtils.getMessage(msg, 'hello')}"/>
Related
I have a custom JSF input component, named inputPeriod, which is designed to input date-periods. Each period has a from and to date. The functionality of the component is achieved with Javascript, which generates a JSON string and submits it to the component. The input component then use a default converter which converts the JSON periods into a list of Period objects and sets them on my managed bean. This all works perfectly.
The source of the problem I am having, is that now I want to use the same component with EJB entities. I have a Banner entity with a one-to-many relationship with a BannerPeriod entity. Each instance of the BannerPeriod entity takes a from (begins) and to (ends) date, exactly like the existing Period object I am using with my input component. I have implemented a new converter for this:
#ManagedBean
#RequestScoped
public class BannerPeriodConverter implements Converter {
#Override
public Object getAsObject(FacesContext fc, UIComponent uic, String str) {
if (str != null) {
Date from = null, to = null;
try {
JSONObject period = new JSONObject(str);
if (period.has("from")) {
from = new Date(period.getLong("from"));
}
if (period.has("to")) {
to = new Date(period.getLong("to"));
}
} catch (JSONException ex) {
throw new ConverterException(ex);
}
BannerPeriod bp = new BannerPeriod();
bp.setBegins(from);
bp.setEnds(to);
return bp;
}
return null;
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object o) {
if (o != null && o instanceof BannerPeriod) {
BannerPeriod bp = (BannerPeriod) o;
JSONObject period = new JSONObject();
try {
period.put("from", bp.getBegins() != null ? bp.getBegins().getTime() : (Object) null);
period.put("to", bp.getEnds() != null ? bp.getEnds().getTime() : (Object) null);
} catch (JSONException ex) {
throw new ConverterException(ex);
}
return period.toString();
}
return "";
}
}
The converter works fine with the component. The issue I am having is that when I edit a banner with existing banner periods, the entities lose their primary key. So, when I submit my form, instead of updating existing periods, I either get a duplicate exception or the existing periods are created again, making actual duplicates in the database.
So my question is, what can I do to avoid this? My guess would be that the input component somehow needs to keep the primary key on the existing entities, but how can I best make something like that? At the moment, the input component is completely detached from the entities and my EJB project. The input component is even located in its own JSF project, while the converter above is located in an EJB project. By default the input component works with a plain Period object, which has no primary key at all. It should continue to do so.
Or maybe this should be solved in some other way?
In your getAsObject() you're creating a completely unmanaged instance of BannerPeriod instead of obtaining the one straight from DB via JPA.
BannerPeriod bp = new BannerPeriod();
bp.setBegins(from);
bp.setEnds(to);
return bp;
Persisting it will of course create a new entry in DB as it's unmanaged by JPA.
Basically, you should instead be obtaining the instance from the DB via JPA:
#EJB
private BannerPeriodService service;
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// ...
return service.find(from, to);
}
wherein the BannerPeriodService#find() obtains the desired instance via EntityManager.
But this approach is pretty clumsy. In case of entities from the DB, the canonical approach is to use their technical/natural identifier for this, such as the autogenerated primary key.
E.g. (null/instanceof checks and so on omitted):
#EJB
private BannerPeriodService service;
public Object getAsString(FacesContext context, UIComponent component, Object value) {
Long id = ((BannerPeriod) value).getId();
return id.toString();
}
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Long id = Long.valueOf(value);
return service.find(id);
}
No need to mess up with JSON format. If you actually need them in JSON format for some unclear reason, then you're going in the wrong direction by using a JSF converter for this.
I understand that hitting the DB in a converter is a relatively expensive job. In that case, the OmniFaces SelectItemsConverter may be what you're looking for.
In my Entity class I have a HashMap. Now I'm trying to create a Select of this Map to be able to select on of the objects. So I created following classes:
HorseConverter:
#Named
public class HorseConverter implements Converter{
#EJB
private HorseBean bean;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return bean.getHorse(Long.valueOf(value));
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if(!(value instanceof Horse)){
throw new ConverterException(new FacesMessage("Object is not a Horse"));
} else {
Horse h = (Horse) value;
return Long.toString(h.getId());
}
}
}
Race Entity:
public Map<Horse, Integer> getHorses() {
return horses;
}
public void setHorses(HashMap<Horse, Integer> horses) {
this.horses = horses;
}
And my view:
Horse:
<h:selectOneMenu value="#{betController.horse}" converter="#{horseConverter}">
<f:selectItems value="#{raceController.selectedRace.horses}" var="h" itemLabel="#{h.nickName}" itemValue="#{h}"/>
</h:selectOneMenu>
Seems like the value I'm getting isn't an instance of Horse. I checked the following link:
https://stackoverflow.com/tags/selectonemenu/info So it seems that the key is automaticly used as value. But even writing h.key doesn't make a difference.
EDIT:
Here is my hash and equals code from the Horse Entity:
#Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + (int) (this.id ^ (this.id >>> 32));
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Horse other = (Horse) obj;
if (this.id != other.id) {
return false;
}
return true;
}
You can't use var on a Map value. This specific <f:selectItems> construct works only if you use List<Horse> instead of Map<Horse, Integer>.
public List<Horse> getHorses() {
return horses;
}
If you really want to use a Map, then you should be returning a Map<String, Horse>, where String is the nickname of the Horse.
public Map<String, Horse> getHorses() {
return horses;
}
In case of using a Map value, don't forget to remove the var:
<f:selectItems value="#{raceController.selectedRace.horses}" />
The map's key becomes the option label and the map's value becomes the option value.
Unrelated to the concrete problem, a HashMap is by nature unordered. If you want to show the dropdown items in insertion order, rather use LinkedHashMap.
Have you overriden hashcode() and equals() in your Horse() object?
Your Converter needs equals() overriden in order to work. If you don't do this, only two references to the same instance of Horse() will be equal, rather than two seperate instances that have exactly the same state. Collections create an implicit copy to compare, so you won't have a single instance on the heap in this case.
Don't forget that the argument in the equals() object is Object(), NOT Horse().
If you don't override hashcode(), the hashcode will be different for every instance of Horse. This means that you will struggle to find the right Horse for comparison, even if your Horses are logically equivalent, because again, you'll have more than one instance that you will be comparing in order to find your Horse in your HashMap.
For further information, see this chapter of Effective Java by Joshua Bloch.
How do you reference an constants with EL on a JSP page?
I have an interface Addresses with a constant named URL. I know I can reference it with a scriplet by going: <%=Addresses.URL%>, but how do I do this using EL?
EL 3.0 or newer
If you're already on Java EE 7 / EL 3.0, then the #page import will also import class constants in EL scope.
<%# page import="com.example.YourConstants" %>
This will under the covers be imported via ImportHandler#importClass() and be available as ${YourConstants.FOO}.
Note that all java.lang.* classes are already implicitly imported and available like so ${Boolean.TRUE} and ${Integer.MAX_VALUE}. This only requires a more recent Java EE 7 container server as early versions had bugs in this. E.g. GlassFish 4.0 and Tomcat 8.0.0-1x fails, but GlassFish 4.1+ and Tomcat 8.0.2x+ works. And you need to make absolutely sure that your web.xml is declared conform the latest servlet version supported by the server. Thus with a web.xml which is declared conform Servlet 2.5 or older, none of the Servlet 3.0+ features will work.
Also note that this facility is only available in JSP and not in Facelets. In case of JSF+Facelets, your best bet is using OmniFaces <o:importConstants> as below:
<o:importConstants type="com.example.YourConstants" />
Or adding an EL context listener which calls ImportHandler#importClass() as below:
#ManagedBean(eager=true)
#ApplicationScoped
public class Config {
#PostConstruct
public void init() {
FacesContext.getCurrentInstance().getApplication().addELContextListener(new ELContextListener() {
#Override
public void contextCreated(ELContextEvent event) {
event.getELContext().getImportHandler().importClass("com.example.YourConstants");
}
});
}
}
EL 2.2 or older
This is not possible in EL 2.2 and older. There are several alternatives:
Put them in a Map<String, Object> which you put in the application scope. In EL, map values are accessible the usual Javabean way by ${map.key} or ${map['key.with.dots']}.
Use <un:useConstants> of the Unstandard taglib (maven2 repo here):
<%# taglib uri="http://jakarta.apache.org/taglibs/unstandard-1.0" prefix="un" %>
<un:useConstants className="com.example.YourConstants" var="constants" />
This way they are accessible the usual Javabean way by ${constants.FOO}.
Use Javaranch's CCC <ccc:constantsMap> as desribed somewhere at the bottom of this article.
<%# taglib uri="http://bibeault.org/tld/ccc" prefix="ccc" %>
<ccc:constantsMap className="com.example.YourConstants" var="constants" />
This way they are accessible the usual Javabean way by ${constants.FOO} as well.
If you're using JSF2, then you could use <o:importConstants> of OmniFaces.
<html ... xmlns:o="http://omnifaces.org/ui">
<o:importConstants type="com.example.YourConstants" />
This way they are accessible the usual Javabean way by #{YourConstants.FOO} as well.
Create a wrapper class which returns them through Javabean-style getter methods.
Create a custom EL resolver which first scans the presence of a constant and if absent, then delegate to the default resolver, otherwise returns the constant value instead.
The following does not apply to EL in general, but instead to SpEL (Spring EL) only (tested with 3.2.2.RELEASE on Tomcat 7).
I think it is worth mentioning it here in case someone searches for JSP and EL (but uses JSP with Spring).
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<spring:eval var="constant" expression="T(com.example.Constants).CONSTANT"/>
You usually place these kinds of constants in a Configuration object (which has getters and setters) in the servlet context, and access them with ${applicationScope.config.url}
You can't. It follows the Java Bean convention. So you must have a getter for it.
I'm defining a constant in my jsp right at the beginning:
<%final String URI = "http://www.example.com/";%>
I include the core taglib in my JSP:
<%#taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
Then, I make the constant available to EL by following statement:
<c:set var="URI" value="<%=URI%>"></c:set>
Now, I can use it later. Here an example, where the value is just written as HTML comment for debugging purposes:
<!-- ${URI} -->
With your constant class, you can just import your class and assign the constants to local variables. I know that my answer is a sort of quick hack, but the question also bumps up when one wants to define constants directly in the JSP.
I implemented like:
public interface Constants{
Integer PAGE_SIZE = 20;
}
-
public class JspConstants extends HashMap<String, String> {
public JspConstants() {
Class c = Constants.class;
Field[] fields = c.getDeclaredFields();
for(Field field : fields) {
int modifier = field.getModifiers();
if(Modifier.isPublic(modifier) && Modifier.isStatic(modifier) && Modifier.isFinal(modifier)) {
try {
Object o = field.get(null);
put(field.getName(), o != null ? o.toString() : null);
} catch(IllegalAccessException ignored) {
}
}
}
}
#Override
public String get(Object key) {
String result = super.get(key);
if(StringUtils.isEmpty(result)) {
throw new IllegalArgumentException("Check key! The key is wrong, no such constant!");
}
return result;
}
}
Next step put instance of this class into servlerContext
public class ApplicationInitializer implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
sce.getServletContext().setAttribute("Constants", new JspConstants());
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
add listener to web.xml
<listener>
<listener-class>com.example.ApplicationInitializer</listener-class>
</listener>
access in jsp
${Constants.PAGE_SIZE}
Static properties aren't accessible in EL. The workaround I use is to create a non-static variable which assigns itself to the static value.
public final static String MANAGER_ROLE = 'manager';
public String manager_role = MANAGER_ROLE;
I use lombok to generate the getter and setter so that's pretty well it. Your EL looks like this:
${bean.manager_role}
Full code at https://rogerkeays.com/access-java-static-methods-and-constants-from-el
Yes, you can. You need a custom tag (if you can't find it somewhere else). I've done this:
package something;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.TreeMap;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.taglibs.standard.tag.el.core.ExpressionUtil;
/**
* Get all class constants (statics) and place into Map so they can be accessed
* from EL.
* #author Tim.sabin
*/
public class ConstMapTag extends TagSupport {
public static final long serialVersionUID = 0x2ed23c0f306L;
private String path = "";
private String var = "";
public void setPath (String path) throws JspException {
this.path = (String)ExpressionUtil.evalNotNull ("constMap", "path",
path, String.class, this, pageContext);
}
public void setVar (String var) throws JspException {
this.var = (String)ExpressionUtil.evalNotNull ("constMap", "var",
var, String.class, this, pageContext);
}
public int doStartTag () throws JspException {
// Use Reflection to look up the desired field.
try {
Class<?> clazz = null;
try {
clazz = Class.forName (path);
} catch (ClassNotFoundException ex) {
throw new JspException ("Class " + path + " not found.");
}
Field [] flds = clazz.getDeclaredFields ();
// Go through all the fields, and put static ones in a Map.
Map<String, Object> constMap = new TreeMap<String, Object> ();
for (int i = 0; i < flds.length; i++) {
// Check to see if this is public static final. If not, it's not a constant.
int mods = flds [i].getModifiers ();
if (!Modifier.isFinal (mods) || !Modifier.isStatic (mods) ||
!Modifier.isPublic (mods)) {
continue;
}
Object val = null;
try {
val = flds [i].get (null); // null for static fields.
} catch (Exception ex) {
System.out.println ("Problem getting value of " + flds [i].getName ());
continue;
}
// flds [i].get () automatically wraps primitives.
// Place the constant into the Map.
constMap.put (flds [i].getName (), val);
}
// Export the Map as a Page variable.
pageContext.setAttribute (var, constMap);
} catch (Exception ex) {
if (!(ex instanceof JspException)) {
throw new JspException ("Could not process constants from class " + path);
} else {
throw (JspException)ex;
}
}
return SKIP_BODY;
}
}
and the tag is called:
<yourLib:constMap path="path.to.your.constantClass" var="consts" />
All public static final variables will be put into a Map indexed by their Java name, so if
public static final int MY_FIFTEEN = 15;
then the tag will wrap this in an Integer and you can reference it in a JSP:
<c:if test="${consts['MY_FIFTEEN'] eq 15}">
and you don't have to write getters!
You can. Try in follow way
#{T(com.example.Addresses).URL}
Tested on TomCat 7 and java6
Even knowing its a little late, and even knowing this is a little hack - i used the following solution to achieve the desired result. If you are a lover of Java-Naming-Conventions, my advice is to stop reading here...
Having a class like this, defining Constants, grouped by empty classes to create kind of a hierarchy:
public class PERMISSION{
public static class PAGE{
public static final Long SEE = 1L;
public static final Long EDIT = 2L;
public static final Long DELETE = 4L;
...
}
}
can be used from within java as PERMISSION.PAGE.SEE to retrieve the value 1L
To achieve a simliar access-possibility from within EL-Expressions, I did this:
(If there is a coding-god - he hopefully might forgive me :D )
#Named(value="PERMISSION")
public class PERMISSION{
public static class PAGE{
public static final Long SEE = 1L;
public static final Long EDIT = 2L;
public static final Long DELETE = 4L;
...
//EL Wrapper
public Long getSEE(){
return PAGE.SEE;
}
public Long getEDIT(){
return PAGE.EDIT;
}
public Long getDELETE(){
return PAGE.DELETE;
}
}
//EL-Wrapper
public PAGE getPAGE() {
return new PAGE();
}
}
finally, the EL-Expression to access the very same Long becomes: #{PERMISSION.PAGE.SEE} - equality for Java and EL-Access. I know this is out of any convention, but it works perfectly fine.
#Bozho already provided a great answer
You usually place these kinds of constants in a Configuration object (which has getters and setters) in the servlet context, and access them with ${applicationScope.config.url}
However, I feel an example is needed so it brings a bit more clarity and spare someone's time
#Component
public Configuration implements ServletContextAware {
private String addressURL = Addresses.URL;
// Declare other properties if you need as also add corresponding
// getters and setters
public String getAddressURL() {
return addressURL;
}
public void setServletContext(ServletContext servletContext) {
servletContext.setAttribute("config", this);
}
}
There is a workaround that is not exactly what you want, but lets you active almost the same with touching scriptlets in a quite minimal way. You can use scriptlet to put value into a JSTL variable and use clean JSTL code later in the page.
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%# page import="com.whichever.namespace.Addresses" %>
<c:set var="ourUrl" value="<%=Addresses.URL%>"/>
<c:if test='${"http://www.google.com" eq ourUrl}'>
Google is our URL!
</c:if>
I have an input (JSF) that should be bound to a property in my bean. This property represents another bean and has an auxiliar method that checks if it's null (I use this method a lot).
The problem is that the binding is failing to get the proper getter and setter. Instead of reading the method that returns the bean, it reads the one that return a boolean value.
The property name is guest. The methods are:
getGuest;
setGuest;
isGuest (checks if guest is null).
JSF is trying to bind the object to isGuest and setGuest, instead of getGuest and setGuest.
I cannot rename isGuest to guestIsNull or something, because that would'nt make to much sense (see the class below).
Finally, my question is: how can I bind this property to the object without renaming my methods? Is it possible?
I also accept suggestions of a better method name (but the meaning must be the same).
Entity
#Entity
public class Passenger {
private Employee employee;
private Guest guest;
public Passenger() {
}
#Transient
public boolean isEmployee() {
return null != this.employee;
}
#Transient
public boolean isGuest() {
return null != this.guest;
}
#OneToOne
public Employee getEmployee() {
return this.employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
#OneToOne
public Guest getGuest() {
return this.guest;
}
public void setGuest(Guest guest) {
this.guest = guest;
}
}
JSF
<h:inputText value="#{passenger.employee}" />
<h:inputText value="#{passenger.guest}" />
Change the method name to isGuestNull.
The problem you're seeing is due to the fact that the EL lets you use getFoo or isFoo as the naming style for getter methods that return booleans.
No, that's not possible. You've to rename them.
Another way is to add a single getter returning an enum which covers all cases.
public enum Type {
GUEST, EMPLOYEE;
}
public Type getType() {
return guest != null ? Type.GUEST
: employee != null ? Type.EMPLOYEE
: null;
}
with
<h:something rendered="#{passenger.type == 'GUEST'}">
Binding to any property using any method is possible and quite easy if you create your custom ELResolver (apidocs). elresolvers are registered in faces config, and they are responsible, given an Object and a String defining a property, for determining the value and type of the given properties (and, as the need arises, to change it).
You could easily write your own ELResolver that would only work for your chosen, single type, and use (for example in a switch statement) the specific methods you need to write and read properties. And for other types it would delegate resolving up the resolver chain. It's really easy to do, much easier than it sounds.
But don't do it. The standard naming pattern of properties predates EL by many years. It is part of the JavaBeans™ standard - one of the very few undisputed standards in Javaland, working everywhere - from ant scripts, through spring configuration files to JSF. Seeing methods isPerson and getPerson in one class actually makes me fill uneasy, as it breaks something I always take for granted and can always count on.
If you like DDD and want to have your method's names pure, use an adapter. It's easy, fun, and gives a couple of additional lines, which is not something to sneer at if you get paid for the ammount of code produced:
public class MyNotReallyBean {
public String checkName() { ... }
public String lookUpLastName() { ... }
public String carefullyAskAboutAge() { ... }
public class BeanAdapter {
public String getName() { return checkName(); }
public String getLastName() { return lookUpLastName(); }
public String getAge() { return carefullyAskAboutAge(); }
}
private static BeanAdapter beanAdapter = new BeanAdapter();
private BeanAdapter getBeanAdapter(){ return beanAdapter; }
}
Is there any way to check declaratively whether an enum has a specified value. For example:
<h:graphicImage name="error.png" library="images"
rendered="#{viewController.current.status == Status.ERROR}" />
It's a little bit tedious to define a method in the managed beand that checks this for every enum value, e.g.
public boolean isStateIsError() {
return current.getStatus() == Status.ERROR;
}
Is there a shorter/better way of doing this?
Until EL 3.0 it's not possible to import enums in EL scope. You can however just treat and compare them like strings, i.e. the enum constant value must be quoted like below.
<h:graphicImage name="error.png" library="images"
rendered="#{viewController.current.status eq 'ERROR'}" />
I know this question is a bit older now, but i had the same problem and found another solution, which i want to share :
Create a Custom EL-Resolver and use enums and java constants as objects in jsf el:
<h:graphicImage name="error.png" library="images"
rendered="#{viewController.current.status == Status.ERROR}" />
But before you can use enums this way you have to do 3 steps.
1. step - Copy this Class and replace "MY_ENUM" through your enumClass (in the example above it would be "Status")
public class EnumCache {
private Map<String, Object> propertCache = new HashMap<String, Object>();
private Map<String, Class> baseCache = new HashMap<String, Class>();
private static EnumCache staticEnumCache = null;
public static EnumCache instance() {
if (staticEnumCache == null) { staticEnumCache = new EnumCache(); }
return staticEnumCache;
}
private EnumCache() {
List<Class<?>> classes = new ArrayList<Class<?>>();
classes.add(MY_ENUM.class);
for(Class clazz : classes) {
try {
baseCache.put(clazz.getSimpleName(), clazz);
Method m = clazz.getMethod("values", (Class[]) null);
Enum<?>[] valueList = (Enum[]) m.invoke(null, (Object[]) null);
for (Enum<?> en : valueList) {
propertCache.put(clazz.getSimpleName() + "." + en.name(), en);
}
} catch (Exception e) {
System.err.println(clazz.getSimpleName(), e);
}
}
}
public Object getValueForKey(String key) {
return propertCache.get(key);
}
public Class getClassForKey(String key) {
return baseCache.get(key);
}
}
2. step - add this EnumResolver - This class will map your JSF expression to the enum in cache (step 1)
public class MyEnumResolver extends ELResolver {
public Object getValue(ELContext context, Object base, Object property) {
Object result = null;
if (base == null) {
result = EnumCache.instance().getClassForKey(property + "");
} else if (base instanceof Class) {
result = EnumCache.instance().getValueForKey(((Class) base).getSimpleName() + "." + property);
}
if (result != null) {
context.setPropertyResolved(true);
}
return result;
}
public Class<?> getCommonPropertyType(ELContext context, Object base) {
return null;
}
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
return null;
}
public Class<?> getType(ELContext context, Object base, Object property) {
return null;
}
public boolean isReadOnly(ELContext context, Object base, Object property) {
return false;
}
public void setValue(ELContext context, Object base, Object property, Object arg3) {
}
}
3. step - register the EnumResolver in faces-config.xml
<faces-config>
<application>
<el-resolver>com.asd.MyEnumResolver</el-resolver>
</application>
</faces-config>
NOTE:
If you want to access your java constants this way, you just have to extend the constructor of the enumCache class.
This (untestet) example should work:
baseCache.put(CLASS_WITH_CONSTANTS.getSimpleName(), clazz);
for (Field field : CLASS_WITH_CONSTANTS.getDeclaredFields()) {
try {
propertCache.put(CLASS_WITH_CONSTANTS.getSimpleName() + "."
+ field.getName(), field.get(null));
} catch (Exception e) { }
}
Hope this reduced but working code can help anybody.
Update
I see this benefits:
If you use strings in jsf (viewController.current.status == 'ERROR_abcdefg'), you can misspell the value and wont recognise it so fast.
With my solution you would get an error while loading the jsf file, because the enum could not be resolved.
You can see in the sourcecode that "ERROR" is value of the enum "STATUS".
When you compare two values in el, the class of the enums will be compared too.
So for example PersonState.ACTIV is not the same like AccounState.ACTIV.
When i have to change my enum value from PersonState.ACTIV to PersonState.ACTIVATED i can search for the String "PersonState.ACTIV" in my sourcecode. searching for "ACTIV" would have much more matches.
I solved a similar problem by statically dumping all the enum keys (which are used in the rendered UI components) in a map and then I use a static getByKey method to convert the value from the UI into an actual native enum in the setter, throwing an Exception if the value provided is invalid:
public enum ReportType {
FILING("F", "Filings"),
RESOLUTION("R", "Resolutions"),
BASIS("B", "Bases"),
STAFF("T", "Staff Counts"),
COUNTS("I", "Counts");
private String key;
private String label;
private static Map<String, ReportType> keyMap = new HashMap<String, ReportType>();
static {
for(ReportType type : ReportType.values()) {
keyMap.put(type.getKey(), type);
}
}
private ReportType(String _key, String _label) {
this.key = _key;
this.label = _label;
}
public String getKey() {
return this.key;
}
public String getLabel() {
return this.label;
}
public static List<ReportType> getValueList() {
return Arrays.asList(ReportType.values());
}
public static ReportType getByKey(String _key) {
ReportType result = keyMap.get(_key);
if(result == null) {
throw new IllegalArgumentException("Invalid report type key: " + _key);
}
return result;
}
}
In the UI tier, the enum key is used as the value and the enum label is used as the label:
<f:selectItems var="rptTypeItem" value="#{reportController.allReportTypes}"
itemLabel="#{rptTypeItem.label}" itemValue="#{rptTypeItem.key}"/>
In the managed bean, I convert the enum into a renderable list, using the getValueList() from the enum:
public List<ReportType> getAllReportTypes() {
return ReportType.getValueList();
}
Finally, the [g|s]etters in the managed bean look as follows:
public String getReportType() {
return this.crtRptType.getKey();
}
public void setReportType(String _val) {
this.crtRptType = ReportType.getByKey(_val);
}
I think it could be done it the following way:
Create a method in you bean that would return the list of enums, for example
public Status[] getStatuses() {
Status.values();
}
then you can use the enum in EL like this
<h:graphicImage name="error.png" library="images"
rendered="#{viewController.current.status == someBean.statuses[0]}" />
assuming that the order of enum members is not going to be changed (for ex. here statuses[0] is ERROR). However, I would fix the positions like this:
public Status[] getStatuses() {
Status myStatuses = new Status [2]; // or whatever number of statuses you are going to use in UI
myStatuses [0] = Status.ERROR;
myStatuses [1] = Status.RUNNING;
return myStatuses;
}
This is still not dynamic solution, but it's better than hard-coding in EL. Might be especially useful when you'r using localization for you statuses (enum values depending on locale/translation).