Implement Converters for Entities with Java Generics

Implement converters for entities with Java Generics

Easiest would be to let all your JPA entities extend from a base entity like this:

public abstract class BaseEntity<T extends Number> implements Serializable {

private static final long serialVersionUID = 1L;

public abstract T getId();

public abstract void setId(T id);

@Override
public int hashCode() {
return (getId() != null)
? (getClass().getSimpleName().hashCode() + getId().hashCode())
: super.hashCode();
}

@Override
public boolean equals(Object other) {
return (other != null && getId() != null
&& other.getClass().isAssignableFrom(getClass())
&& getClass().isAssignableFrom(other.getClass()))
? getId().equals(((BaseEntity<?>) other).getId())
: (other == this);
}

@Override
public String toString() {
return String.format("%s[id=%d]", getClass().getSimpleName(), getId());
}

}

Note that it's important to have a proper equals() (and hashCode()), otherwise you will face Validation Error: Value is not valid. The Class#isAssignableFrom() tests are to avoid failing tests on e.g. Hibernate based proxies without the need to fall back to Hibernate-specific Hibernate#getClass(Object) helper method.

And have a base service like this (yes, I'm ignoring the fact that you're using Spring; it's just to give the base idea):

@Stateless
public class BaseService {

@PersistenceContext
private EntityManager em;

public BaseEntity<? extends Number> find(Class<BaseEntity<? extends Number>> type, Number id) {
return em.find(type, id);
}

}

And implement the converter as follows:

@ManagedBean
@ApplicationScoped
@SuppressWarnings({ "rawtypes", "unchecked" }) // We don't care about BaseEntity's actual type here.
public class BaseEntityConverter implements Converter {

@EJB
private BaseService baseService;

@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}

if (modelValue instanceof BaseEntity) {
Number id = ((BaseEntity) modelValue).getId();
return (id != null) ? id.toString() : null;
} else {
throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e);
}
}

@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}

try {
Class<?> type = component.getValueExpression("value").getType(context.getELContext());
return baseService.find((Class<BaseEntity<? extends Number>>) type, Long.valueOf(submittedValue));
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(String.format("%s is not a valid ID of BaseEntity", submittedValue)), e);
}
}

}

Note that it's registered as a @ManagedBean instead of a @FacesConverter. This trick allows you to inject a service in the converter via e.g. @EJB. See also How to inject @EJB, @PersistenceContext, @Inject, @Autowired, etc in @FacesConverter? So you need to reference it as converter="#{baseEntityConverter}" instead of converter="baseEntityConverter".

If you happen to use such a converter more than often for UISelectOne/UISelectMany components (<h:selectOneMenu> and friends), you may find OmniFaces SelectItemsConverter much more useful. It converts based on the values available in <f:selectItems> instead of making (potentially expensive) DB calls everytime.

Generic JSF entity converter

Well I had the same problem today, and I solved it by creating a generic ConversionHelper and using it in the converter.
For this purpose I have an EntityService which is a generic SLSB that I use to perform simple CRUD operations for any entity type. Also my entities implement a PersistentEntity interface, which has a getId and setId methods and I keep them with simple primary keys. That's it.

In the end my converter looks like this:



@FacesConverter(value = "userConverter", forClass = User.class)
public class UserConverter implements Converter {

@Override
public Object getAsObject(FacesContext ctx, UIComponent component, java.lang.String value) {

return ConversionHelper.getAsObject(User.class, value);
}

@Override
public String getAsString(FacesContext ctx, UIComponent component, Object value) {

return ConversionHelper.getAsString(value);
}
}

And my conversion helper looks like this:



public final class ConversionHelper {

private ConversionHelper() {
}

public static <T> T getAsObject(Class<T> returnType, String value) {

if (returnType== null) {

throw new NullPointerException("Trying to getAsObject with a null return type.");
}

if (value == null) {

throw new NullPointerException("Trying to getAsObject with a null value.");
}

Long id = null;

try {

id = Long.parseLong(value);

} catch (NumberFormatException e) {

throw new ConverterException("Trying to getAsObject with a wrong id format.");
}

try {

Context initialContext = new InitialContext();
EntityService entityService = (EntityService) initialContext.lookup("java:global/myapp/EntityService");

T result = (T) entityService.find(returnType, id);

return result;

} catch (NamingException e) {

throw new ConverterException("EntityService not found.");
}
}

public static String getAsString(Object value) {

if (value instanceof PersistentEntity) {

PersistentEntity result = (PersistentEntity) value;

return String.valueOf(result.getId());
}

return null;
}
}

Now creating converters for simple JPA entities is a matter of duplicate a converter and change 3 parameters.

This is working well for me, but I don't know if it is the best approach in terms of style and performance. Any tips would be appreciated.

h:selectOneMenu generic converter for all entities without calling DB again and again

My first question is, is there a generic converter for all type of entities?

This does indeed not exist in standard JSF. The JSF utility library OmniFaces has such a converter in its assortiment, the omnifaces.SelectItemsConverter. All you need to do is to declare it as converter of an UISelectOne or UISelectMany component as follows:

<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">

See also the SelectItemsConverter showcase. This converter relies on the toString() of the object item. There's also another one, the omnifaces.SelectItemsIndexConverter, which relies instead on the index of the object item in the options list, see also the SelectItemsIndexConverter showcase.

There are currently no other JSF component/utility libraries offering the same.


Second question is, is there a way to get selected object without any converter?

No. Just use the OmniFaces one so that you don't need to create a custom converter which hits the DB. Or if you want to go overboard, create a custom renderer for <h:selectOneMenu> which renders the item index as option value and is able to set it as model value, but that's a lot of more work than a simple converter and you'd still need to do some additional work in order to get the desired object from the list based on the index — which just doesn't make any sense.

See also:

  • How to populate options of h:selectOneMenu from database?
  • Conversion Error setting value for 'null Converter'
  • Implement converters for entities with Java Generics

Using generics for a class how can I convert the values from one type to another?

Maybe you could use the Function<T, R> interface?

public abstract class Field<T> implements FieldType<T> {

...

public <F> T convert(F value, Function<F, T> converter) {
try {
return converter.apply(value);
} catch(Exception e) {
return null;
}
}

...

}

And then specify the converter using a lambda expression or a method reference:

field.convert("1234", BigDecimal::new); //with a method reference
field.convert("1234", s -> new BigDecimal(s)) //with a lambda

This would replace all of your convertXXX methods by one since the return type is inferred from the passed Function.


EDIT:
If you want automatic converting, you would of course have to hard-code these since you wouldn't want to write conversion methods for all 4240 classes in the Java API. This gets messy, though. Maybe something like this in a static helper class or in FieldType itself?

public class WhereverYouWantThis {

private static HashMap<Class<?>, HashMap<Class<?>, Function<?, ?>>> converters = new HashMap<>();

static {
putConverter(String.class, Float.class, Float::parseFloat);
}

private static <T, R> void putConverter(Class<T> t, Class<R> r, Function<T, R> func) {
HashMap<Class<?>, Function<?, ?>> map = converters.get(t);
if(map == null) converters.put(t, map = new HashMap<>());
map.put(r, func);
}

public static <T, R> Function<T, R> getConverter(Class<T> t, Class<R> r) {
HashMap<Class<?>, Function<?, ?>> map = converters.get(t);
if(map == null) return null;
@SuppressWarnings("unchecked")
Function<T, R> func = (Function<T, R>) map.get(r);
return func;
}

public static <T, R> R convert(T o, Class<R> to) {
@SuppressWarnings("unchecked")
Function<T, R> func = (Function<T, R>) getConverter(o.getClass(), to);
return func == null ? null : func.apply(o);
}

}

Arguments against a generic JSF object converter with a static WeakHashMap

This approach is hacky and memory inefficient.

It's "okay" in a small application, but definitely not in a large application with tens or hundreds of thousands of potential entities around which could be referenced in a f:selectItems. Moreover, such a large application has generally a second level entity cache. The WeakHashMap becomes then useless and is only effective when an entity is physically removed from the underlying datastore (and thus also from second level entity cache).

It has certainly a "fun" factor, but I'd really not recommend using it in "heavy production".

If you don't want to use an existing solution from an utility library like OmniFaces SelectItemsConverter as you already found, which is basically completely stateless and doesn't use any DAO/Service call, then your best bet is to abstract all your entities with a common base interface/class and hook the converter on that instead. This only still requires a DAO/Service call. This has been fleshed out in detail in this Q&A: Implement converters for entities with Java Generics.

How to implement entity converter in this example?

You shouldn't use any converter. Keep the ManyToOne association

Assuming you want to create a PlayerGame for an existing game and an existing player, instead of defining

public PlayerGame(Integer id, Integer fkGameId, Integer fkPlayerId)

define

    public PlayerGame(Integer id, Game game, Player player)

(although your ID should rather be auto-generated, and thus not be part of the constructor)

Instead of using

PlayerGame pg = new PlayerGame(1, 2, 3);

Use

PlayerGame pg = new PlayerGame(1, 
entityManager.getReference(Game.class, 2),
entityManager.getReference(Player.class, 3));

Note that, if game and player are the only fields of that entity, it could be ditched completely, and replaced by a ManyToMany association between Player and Game.

I would also rename the field fkGameId to game. What you have there is not an ID, but an instance of Game. fk stands for foreign key, which is a relational database concept, not an OO concept.



Related Topics



Leave a reply



Submit