JSF 2: Using enums in the rendered attribute - jsf

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).

Related

jackson serializer cover String null to empty String("") and keep object null is null

I have tried several ways
e.g.
1.create a custom JsonSerializer, and override serialize method
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("");
}
and set the JsonSerializer to NullValueSerializer,like this:
objectMapper.getSerializerProvider().setNullValueSerializer(new NullStringSerializer());
but we can not get the Class Type from null. All null will be covert to "" , include the object.
2.if use SimpleModule
SimpleModule simpleModule = new SimpleModule("StringModule", Version.unknownVersion());
simpleModule.addSerializer(Object.class, new NullStringSerializer());
objectMapper.registerModule(simpleModule);
in the serialize method, the param value do not have any properties which is null.
from the resouce code of jackson 2.6.0 , find method serializeFields in MapSerializer.java (my object is a map) line: 545
for (Map.Entry<?,?> entry : value.entrySet()) {
Object valueElem = entry.getValue();
// First, serialize key
Object keyElem = entry.getKey();
if (keyElem == null) {
provider.findNullKeySerializer(_keyType, _property).serialize(null, gen, provider);
} else {
// One twist: is entry ignorable? If so, skip
if (ignored != null && ignored.contains(keyElem)) continue;
keySerializer.serialize(keyElem, gen, provider);
}
// And then value
if (valueElem == null) {
provider.defaultSerializeNull(gen);
} else {...}
}
when the valueElem is null, the provide just covert it to null.
and do not have any interface for me to change the strategy.
I can override MapSerializer ,but I do not know how to set the new MapSerializer to the factory.
Is there any solution?
expect your help,thank you!
find a solution :
the abstract class SerializerProvider has a method named findNullValueSerializer, which is called to get the serializer to use for serializing null values for specified property.
We can override SerializerProvider#findNullValueSerializer and match String class:
#Override
public JsonSerializer<Object> findNullValueSerializer(BeanProperty property) throws JsonMappingException {
if (property.getType().getRawClass().equals(String.class)) {
return EmptyStringSerializer.INSTANCE;
} else {
return super.findNullValueSerializer(property);
}
}
and then set SerializerProvider to our ObjectMapper instance.
done.

JSF SelectOneMenu HashMap Converter has wrong instanceOf

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.

JAX-RS: How to automatically serialize a collection when returning a Response object?

I have a JAXB-annotated employee class:
#XmlRootElement(name = "employee")
public class Employee {
private Integer id;
private String name;
...
#XmlElement(name = "id")
public int getId() {
return this.id;
}
... // setters and getters for name, equals, hashCode, toString
}
And a JAX-RS resource object (I'm using Jersey 1.12)
#GET
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/")
public List<Employee> findEmployees(
#QueryParam("name") String name,
#QueryParam("page") String pageNumber,
#QueryParam("pageSize") String pageSize) {
...
List<Employee> employees = employeeService.findEmployees(...);
return employees;
}
This endpoint works fine. I get
<employees>
<employee>
<id>2</id>
<name>Ana</name>
</employee>
</employees>
However, if I change the method to return a Response object, and put the employee list in the response body, like this:
#GET
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/")
public Response findEmployees(
#QueryParam("name") String name,
#QueryParam("page") String pageNumber,
#QueryParam("pageSize") String pageSize) {
...
List<Employee> employees = employeeService.findEmployees(...);
return Response.ok().entity(employees).build();
}
the endpoint results in an HTTP 500 due to the following exception:
javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message body writer for Java class java.util.ArrayList, and Java type class java.util.ArrayList, and MIME media type application/xml was not found
In the first case, JAX-RS has obviously arranged for the proper message writer to kick in when returning a collection. It seems somewhat inconsistent that this doesn't happen when the collection is placed in the entity body. What approach can I take to get the automatic JAXB serialization of the list to happen when returning a response?
I know that I can
Just return the list from the resource method
Create a separate EmployeeList class
but was wondering whether there is a nice way to use the Response object and get the list to serialize without creating my own wrapper class.
You can wrap the List<Employee> in an instance of GenericEntity to preserve the type information:
http://docs.oracle.com/javaee/6/api/javax/ws/rs/core/GenericEntity.html
You can use GenericEntity to send the collection in the Response. You must have included appropriate marshal/unmarshal library like moxy or jaxrs-jackson.
Below is the code :
#GET
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Path("/")
public Response findEmployees(
#QueryParam("name") String name,
#QueryParam("page") String pageNumber,
#QueryParam("pageSize") String pageSize) {
...
List<Employee> employees = employeeService.findEmployees(...);
GenericEntity<List<Employee>> entity = new GenericEntity<List<Employee>>(Lists.newArrayList(employees))
return Response.ok().entity(entity).build();
}
I resolved this issue by extending the default JacksonJsonProvider class, in particular method writeTo.
Analyzing the source code of this class I found the block where the actual type is instantiated by reflection, so I've modified the source code as below:
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,Object> httpHeaders, OutputStream entityStream) throws IOException {
/* 27-Feb-2009, tatu: Where can we find desired encoding? Within
* HTTP headers?
*/
ObjectMapper mapper = locateMapper(type, mediaType);
JsonEncoding enc = findEncoding(mediaType, httpHeaders);
JsonGenerator jg = mapper.getJsonFactory().createJsonGenerator(entityStream, enc);
jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
// Want indentation?
if (mapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) {
jg.useDefaultPrettyPrinter();
}
// 04-Mar-2010, tatu: How about type we were given? (if any)
JavaType rootType = null;
if (genericType != null && value != null) {
/* 10-Jan-2011, tatu: as per [JACKSON-456], it's not safe to just force root
* type since it prevents polymorphic type serialization. Since we really
* just need this for generics, let's only use generic type if it's truly
* generic.
*/
if (genericType.getClass() != Class.class) { // generic types are other impls of 'java.lang.reflect.Type'
/* This is still not exactly right; should root type be further
* specialized with 'value.getClass()'? Let's see how well this works before
* trying to come up with more complete solution.
*/
//**where the magic happens**
//if the type to instantiate implements collection interface (List, Set and so on...)
//Java applies Type erasure from Generic: e.g. List<BaseRealEstate> is seen as List<?> and so List<Object>, so Jackson cannot determine #JsonTypeInfo correctly
//so, in this case we must determine at runtime the right object type to set
if(Collection.class.isAssignableFrom(type))
{
Collection<?> converted = (Collection<?>) type.cast(value);
Class<?> elementClass = Object.class;
if(converted.size() > 0)
elementClass = converted.iterator().next().getClass();
//Tell the mapper to create a collection of type passed as parameter (List, Set and so on..), containing objects determined at runtime with the previous instruction
rootType = mapper.getTypeFactory().constructCollectionType((Class<? extends Collection<?>>)type, elementClass);
}
else
rootType = mapper.getTypeFactory().constructType(genericType);
/* 26-Feb-2011, tatu: To help with [JACKSON-518], we better recognize cases where
* type degenerates back into "Object.class" (as is the case with plain TypeVariable,
* for example), and not use that.
*/
if (rootType.getRawClass() == Object.class) {
rootType = null;
}
}
}
// [JACKSON-578]: Allow use of #JsonView in resource methods.
Class<?> viewToUse = null;
if (annotations != null && annotations.length > 0) {
viewToUse = _findView(mapper, annotations);
}
if (viewToUse != null) {
// TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer)
ObjectWriter viewWriter = mapper.viewWriter(viewToUse);
// [JACKSON-245] Allow automatic JSONP wrapping
if (_jsonpFunctionName != null) {
viewWriter.writeValue(jg, new JSONPObject(this._jsonpFunctionName, value, rootType));
} else if (rootType != null) {
// TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer)
mapper.typedWriter(rootType).withView(viewToUse).writeValue(jg, value);
} else {
viewWriter.writeValue(jg, value);
}
} else {
// [JACKSON-245] Allow automatic JSONP wrapping
if (_jsonpFunctionName != null) {
mapper.writeValue(jg, new JSONPObject(this._jsonpFunctionName, value, rootType));
} else if (rootType != null) {
// TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer)
mapper.typedWriter(rootType).writeValue(jg, value);
} else {
mapper.writeValue(jg, value);
}
}
}

How to remove the surrounding ??? when message is not found in bundle

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')}"/>

EF Code First - Include(x => x.Properties.Entity) a 1 : Many association

Given a EF-Code First CTP5 entity layout like:
public class Person { ... }
which has a collection of:
public class Address { ... }
which has a single association of:
public class Mailbox { ... }
I want to do:
PersonQuery.Include(x => x.Addresses).Include("Addresses.Mailbox")
WITHOUT using a magic string. I want to do it using a lambda expression.
I am aware what I typed above will compile and will bring back all Persons matching the search criteria with their addresses and each addresses' mailbox eager loaded, but it's in a string which irritates me.
How do I do it without a string?
Thanks Stack!
For that you can use the Select method:
PersonQuery.Include(x => x.Addresses.Select(a => a.Mailbox));
You can find other examples in here and here.
For any one thats still looking for a solution to this, the Lambda includes is part of EF 4+ and it is in the System.Data.Entity namespace; examples here
http://romiller.com/2010/07/14/ef-ctp4-tips-tricks-include-with-lambda/
It is described in this post: http://www.thomaslevesque.com/2010/10/03/entity-framework-using-include-with-lambda-expressions/
Edit (By Asker for readability):
The part you are looking for is below:
public static class ObjectQueryExtensions
{
public static ObjectQuery<T> Include<T>(this ObjectQuery<T> query, Expression<Func<T, object>> selector)
{
string path = new PropertyPathVisitor().GetPropertyPath(selector);
return query.Include(path);
}
class PropertyPathVisitor : ExpressionVisitor
{
private Stack<string> _stack;
public string GetPropertyPath(Expression expression)
{
_stack = new Stack<string>();
Visit(expression);
return _stack
.Aggregate(
new StringBuilder(),
(sb, name) =>
(sb.Length > 0 ? sb.Append(".") : sb).Append(name))
.ToString();
}
protected override Expression VisitMember(MemberExpression expression)
{
if (_stack != null)
_stack.Push(expression.Member.Name);
return base.VisitMember(expression);
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
if (IsLinqOperator(expression.Method))
{
for (int i = 1; i < expression.Arguments.Count; i++)
{
Visit(expression.Arguments[i]);
}
Visit(expression.Arguments[0]);
return expression;
}
return base.VisitMethodCall(expression);
}
private static bool IsLinqOperator(MethodInfo method)
{
if (method.DeclaringType != typeof(Queryable) && method.DeclaringType != typeof(Enumerable))
return false;
return Attribute.GetCustomAttribute(method, typeof(ExtensionAttribute)) != null;
}
}
}

Resources