Как создать собственный конвертер в JSF 2? - PullRequest
2 голосов
/ 04 ноября 2011

У меня есть этот объект под названием «Операция»:

@Entity
@Table(name="operation")
public class Operation implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    private Integer id;

    @NotNull(message="informe um tipo de operação")
    private String operation;

    //bi-directional many-to-one association to Product
    @OneToMany(mappedBy="operation")
    private List<Product> products;

    // getter and setters
}

Я получаю операции следующим образом: (Это может быть через экземпляр EJB, но только для того, чтобы сохранить его локальным и, как пример, хорошо?;))

public Map<String, Object> getOperations() {
    operations = new LinkedHashMap<String, Object>();
    operations.put("Select an operation", new Operation());
    operations.put("Donation", new Operation(new Integer(1), "donation"));
    operations.put("Exchange", new Operation(new Integer(2), "exchange"));

    return operations;
}

Итак, я пытаюсь получить выбранную операцию в этом selectOneMenu:

productc - это ManagedBean с viewScope, productb - это ManagedBean с sessionScope с product, который является моей сущностью. Продукт включает один operation, поэтому он выглядит примерно так:

(буква c имеет значение контроля, где все операции, связанные с моим продуктом сущности, должны обрабатываться этим компонентом, хорошо?)

Product productc (ViewScope) 
-- ProductBean productb (SessionScope)
---- Product product (Entity)
-------- Operation operation (Entity)

Преобразователь такой же, как и @BalusC, который предлагается ранее:

@ManagedBean
@RequestScoped
public class OperationConverter implements Converter {

    @EJB
    private EaoOperation operationService;

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (!(value instanceof Operation) || ((Operation) value).getId() == null) {
            return null;
        }

        return String.valueOf(((Operation) value).getId());
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || !value.matches("\\d+")) {
            return null;
        }

        Operation operation = operationService.find(Integer.valueOf(value));
        System.out.println("Getting the operation value = " + operation.getOperation() );

        if (operation == null) {
            throw new ConverterException(new FacesMessage("Unknown operation ID: " + value));
        }

        return operation;
    }

, которые извлекают операцию, выбранную, как показано в журнале:

FINE: SELECT ID, OPERATION FROM operation WHERE (ID = ?)
    bind => [1 parameter bound]
INFO: Getting the operation value = exchange

Поэтому при попытке отправить форму выдает следующую ошибку:

form_add_product:operation: Validation error: the value is not valid

Почему это происходит?

1 Ответ

3 голосов
/ 04 ноября 2011

Вы пытаетесь передать сложный объект в качестве параметра HTTP-запроса, который может быть только String. JSF / EL имеет встроенные конвертеры для примитивов и их оболочек (например, int, Integer) и даже перечислений. Но для всех остальных типов вам действительно нужно написать собственный конвертер. В этом случае вам нужно написать конвертер, который конвертирует между String и Operation. String затем используется в качестве значения параметра (откройте страницу в браузере, щелкните правой кнопкой мыши и Просмотреть исходный код и обратите внимание на <option value>). Operation затем используется в качестве значения модели. String должен однозначно идентифицировать объект Operation. Вы можете использовать идентификатор операции для этого.

Но в данном конкретном случае, с такой жестко закодированной картой и относительно простой моделью, я думаю, что вместо нее проще использовать enum.

public enum Operation {

    DONATION("Donation"), EXCHANGE("Exchange");

    private String label;

    private Operation(String label) {
        this.label = label;
    }

    public string getLabel() {
        return label;
    }

}

с

private Operation operation; // +getter +setter

public Operation[] getOperations() {
    return Operation.values();
}

и

<h:selectOneMenu value="#{bean.operation}">
    <f:selectItems value="#{bean.operations}" var="operation" itemValue="#{operation}" itemLabel="#{operation.label}" />
</h:selectOneMenu>

Но если эти значения имеют на самом деле для извлечения из БД и его размер не определен, то вам все равно действительно нужен пользовательский конвертер. В getAsString() вы можете вернуть идентификатор, а в getAsObject() использовать операцию DAO / EJB, чтобы получить Operation по идентификатору.

@ManagedBean
@RequestScoped
public class OperationConverter implements Converter {

    @EJB
    private OperationService operationService;

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        // Convert here Operation object to String value for use in HTML.
        if (!(value instanceof Operation) || ((Operation) value).getId() == null) {
            return null;
        }

        return String.valueOf(((Operation) value).getId());
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        // Convert here String submitted value to Operation object.
        if (value == null || !value.matches("\\d+")) {
            return null;
        }

        Operation operation = operationService.find(Long.valueOf(value));

        if (operation == null) {
            throw new ConverterException(new FacesMessage("Unknown operation ID: " + value));
        }

        return operation;
    }

}

Используйте его следующим образом:

<h:selectOneMenu ... converter="#{operationConverter}">

Относительно того, почему это @ManagedBean вместо @FacesConverter, прочитайте это: Преобразование и проверка параметров запроса GET .


Обновление в отношении ошибки Validation Error: value not valid, это означает, что метод equals() класса Operation поврежден или отсутствует. Во время проверки JSF сравнивает представленное значение со списком доступных значений по Object#equals(). Если ни один из списка не соответствует представленному значению, вы увидите эту ошибку. Итак, убедитесь, что equals() правильно реализован. Вот базовый пример, который сравнивает по технической идентичности БД.

public boolean equals(Object other) {
    return (other instanceof Operation) && (id != null) 
         ? id.equals(((Operation) other).id) 
         : (other == this);
}

Не забудьте также реализовать hashCode():

public int hashCode() {
    return (id != null) 
         ? (getClass().hashCode() + id.hashCode())
         : super.hashCode();
}
...