Общий конвертер объектов JSF
Я пишу свое первое веб-приложение Java EE 6 в качестве учебного упражнения. Я не использую фреймворк, только JPA 2.0, EJB 3.1 и JSF 2.0.
У меня есть пользовательский конвертер для преобразования объекта JPA, хранящегося в компоненте SelectOne, обратно в Entity. Я использую InitialContext.lookup для получения ссылки на сеанс Bean, чтобы найти соответствующий объект.
Я хотел бы создать общий конвертер Entity, поэтому мне не нужно создавать конвертер на Entity. Я думал, что создам абстрактную сущность и все ее объекты расширят. Затем создайте пользовательский конвертер для абстрактного объекта и используйте его как конвертер для всех сущностей.
Звучит ли это разумно и/или практически невозможно?
Не имеет ли смысл иметь абстрактный объект, просто конвертер, который преобразует любой объект? В этом случае я не уверен, как получить ссылку на соответствующий сеанс Bean.
Я включил свой текущий конвертер, потому что не уверен, что получаю ссылку на мой сеанс Bean наиболее эффективным образом.
package com.mycom.rentalstore.converters;
import com.mycom.rentalstore.ejbs.ClassificationEJB;
import com.mycom.rentalstore.entities.Classification;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
import javax.naming.InitialContext;
import javax.naming.NamingException;
@FacesConverter(forClass = Classification.class)
public class ClassificationConverter implements Converter {
private InitialContext ic;
private ClassificationEJB classificationEJB;
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
try {
ic = new InitialContext();
classificationEJB = (ClassificationEJB) ic.lookup("java:global/com.mycom.rentalstore_RentalStore_war_1.0-SNAPSHOT/ClassificationEJB");
} catch (NamingException e) {
throw new ConverterException(new FacesMessage(String.format("Cannot obtain InitialContext - %s", e)), e);
}
try {
return classificationEJB.getClassificationById(Long.valueOf(value));
} catch (Exception e) {
throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to Classification - %s", value, e)), e);
}
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return String.valueOf(((Classification) value).getId());
}
}
Ответы
Ответ 1
Хорошо, у меня была такая же проблема сегодня, и я решил ее, создав общий ConversionHelper и используя его в конвертере.
Для этого у меня есть EntityService, который является общим SLSB, который я использую для выполнения простых операций CRUD для любого типа сущности. Также мои объекты реализуют интерфейс PersistentEntity, который имеет методы getId и setId, и я храню их с простыми первичными ключами. Что это.
В конце мой конвертер выглядит следующим образом:
@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);
}
}
И мой помощник преобразования выглядит так:
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;
}
}
Теперь создание конвертеров для простых объектов JPA является вопросом дублирования конвертера и изменения 3 параметров.
Это хорошо работает для меня, но я не знаю, лучший ли это подход с точки зрения стиля и производительности. Любые советы будут оценены.
Ответ 2
Я использую карту просмотра JSF 2.0:
@FacesConverter("entityConverter")
public class EntityConverter implements Converter {
private static final String key = "com.example.jsf.EntityConverter";
private static final String empty = "";
private Map<String, Object> getViewMap(FacesContext context) {
Map<String, Object> viewMap = context.getViewRoot().getViewMap();
@SuppressWarnings({ "unchecked", "rawtypes" })
Map<String, Object> idMap = (Map) viewMap.get(key);
if (idMap == null) {
idMap = new HashMap<String, Object>();
viewMap.put(key, idMap);
}
return idMap;
}
@Override
public Object getAsObject(FacesContext context, UIComponent c, String value) {
if (value.isEmpty()) {
return null;
}
return getViewMap(context).get(value);
}
@Override
public String getAsString(FacesContext context, UIComponent c, Object value) {
if (value == null) {
return empty;
}
String id = ((Persistent) value).getId().toString();
getViewMap(context).put(id, value);
return id;
}
}
Ответ 3
Мое решение таково:
@ManagedBean
@SessionScoped
public class EntityConverterBuilderBean {
private static Logger logger = LoggerFactory.getLogger(EntityConverterBuilderBean.class);
@EJB
private GenericDao dao;
public GenericConverter createConverter(String entityClass) {
return new GenericConverter(entityClass, dao);
}
}
public class GenericConverter implements Converter {
private Class clazz;
private GenericDao dao;
public GenericConverter(String clazz, Generic dao) {
try {
this.clazz = Class.forName(clazz);
this.dao = dao;
} catch (Exception e) {
logger.error("cannot get class: " + clazz, e);
throw new RuntimeException(e);
}
}
public Object getAsObject(javax.faces.context.FacesContext facesContext, javax.faces.component.UIComponent uiComponent, java.lang.String s) {
Object ret = null;
if (!"".equals(s)) {
Long id = new Long(s);
ret = dao.findById(clazz, id);
}
return ret;
}
public String getAsString(javax.faces.context.FacesContext facesContext, javax.faces.component.UIComponent uiComponent, java.lang.Object o) {
if (o != null) {
return ((SimpleEntity) o).getId() + "";
} else {
return "";
}
}
}
и на страницах:
<h:selectOneMenu id="x" value="#{controller.x}"
converter="#{entityConverterBuilderBean.createConverter('com.test.model.TestEntity')}">
Ответ 4
Используйте Seam Faces, он предоставляет класс Converter, который делает то, что вы хотите.
org.jboss.seam.faces.conversion.Converter
В то время как проект JBoss, Seam 3 отлично работает с Glassfish 3.1 и новее.
http://seamframework.org/Seam3/FacesModule
В 3.1 он имеет пару дополнительных зависимостей; см. http://blog.ringerc.id.au/2011/05/using-seam-3-with-glassfish-31.html
Ответ 5
Попробуйте использовать Seam Faces из Seam 3.
@Named("DocTypeConverter")
public class DocumentTypeConverter implements Converter, Serializable {
private static final long serialVersionUID = 1L;
@Inject
private DocumentTypeSessionEJB proDocTypeSb;
@Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
DocumentType result = null;
if (value != null && !value.trim().equals("")) {
try {
result = (DocumentType) proDocTypeSb.findById(DocumentType.class, value);
} catch(Exception exception) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid value"));
}
}
return result;
}
@Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
String result = null;
if (value != null && value instanceof DocumentType){
DocumentType docType = (DocumentType) value;
result = docType.getId();
}
return result;
}
}
Ответ 6
Я использую что-то вроде этого:
@Named
public class EntityConverter implements Converter {
@Inject
private EntityManager entityManager;
@Inject
private ConversionService conversionService;
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Class<?> entityType = component.getValueExpression("value").getType(context.getELContext());
Class<?> idType = this.entityManager.getMetamodel().entity(entityType).getIdType().getJavaType();
Object id = this.conversionService.convert(idType, value);
return this.entityManager.getReference(entityType, id); // find() is possible too
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
Object id = this.entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(value);
return this.conversionService.convert(String.class, id);
}
}
ConversionService
определяется следующим образом (реализация вне области действия):
public interface ConversionService {
<T> T convert(Class<T> targetType, Object source);
}
в шаблоне используйте <f:converter binding="#{entityConverter}" />
.
Ответ 7
Чтобы завершить отклик Craig Ringer, вы можете использовать общий org.jboss.seam.faces.conversion.ObjectConverter
Seam 3 FacesModule.
Вы можете получить код здесь: https://github.com/seam/faces/blob/develop/impl/src/main/java/org/jboss/seam/faces/conversion/ObjectConverter.java
Он использует 2 HashMap
(один используется обратным образом) и запасает свои объекты в Conversation
.