如何在 JSF 2 中创建自定义转换器?

发布于 2024-12-13 10:51:28 字数 2822 浏览 3 评论 0原文

我有一个名为“操作”的实体:

@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 是一个具有 viewScopeManagedBeanproductb 是一个具有 sessionScope 的 ManagedBean code> 其中有一个 product 这是我的实体。产品包含一个操作,所以是这样的:(

字母c有控制的意思,所有与我的实体产品相关的操作都应该由这个bean来处理,好吗?)

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

为什么会发生这种情况?

I have this Entity called 'Operation':

@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
}

I retrieve the operations this way: (It could be through an EJB instance but just to keep it local and as an example, okay? ;) )

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;
}

So I'm trying to get the selected operation in this selectOneMenu:

The productc is a ManagedBean which has a viewScope, productb is a ManagedBean with a sessionScope which has a product which is my entity. The product contais one operation, so is something like this:

(the letter c has the meaning of control, where all operations related about my entity product should be handled by this bean, okay?)

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

The converter is the same as @BalusC is suggest before:

@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;
    }

Which retrieve the operation selected as showed in the log:

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

So when I try to submit the form gives the follow error:

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

Why is this happening?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

鲜血染红嫁衣 2024-12-20 10:51:28

您正在尝试将一个复杂的对象作为 HTTP 请求参数传递,该参数只能是 String。 JSF/EL 具有用于原语及其包装器(例如intInteger)甚至枚举的内置转换器。但对于所有其他类型,您确实需要编写一个自定义转换器。在这种情况下,您需要编写一个在 StringOperation 之间进行转换的转换器。然后将String用作选项值(在浏览器中打开页面,右键单击并查看源代码并注意<选项值>)。然后将 Operation 用作模型值。 String 应唯一标识Operation 对象。您可以为此使用操作 ID。

但在这种特殊情况下,有了这样的硬编码映射和相对简单的模型,我认为使用 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() 中返回 ID,并在 getAsObject() 中使用操作 DAO/EJB 通过 ID 获取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 请求参数


更新关于验证错误:值无效错误,这意味着Operationequals()方法> 类损坏或丢失。在验证期间,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();
}

You're trying to pass a complex object around as HTTP request parameter which can be only be a String. JSF/EL has builtin converters for primitives and its wrappers (e.g. int, Integer) and even enums. But for all other types you really need to write a custom converter. In this case you need to write a converter which converts between String and Operation. The String is then used as option value (open page in browser, rightclick and View Source and notice the <option value>). The Operation is then used as model value. The String should uniquely identify the Operation object. You could use operation ID for this.

But in this particular case, with such a hardcoded map and a relatively simple model, I think it's easier to use an enum instead.

public enum Operation {

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

    private String label;

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

    public string getLabel() {
        return label;
    }

}

with

private Operation operation; // +getter +setter

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

and

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

But if those values have actually to be retrieved from the DB and its size is undefinied, then you still really need a custom converter. You could in getAsString() return the ID and in getAsObject() use the operation DAO/EJB to get an Operation by the ID.

@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;
    }

}

Use it as follows:

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

As to why it's a @ManagedBean instead of @FacesConverter, read this: Converting and validating GET request parameters.


Update as to the Validation Error: value not valid error, this means that the equals() method of the Operation class is broken or missing. During validation, JSF compares the submitted value with the list of available values by Object#equals(). If no one of the list matches with the submitted value, then you'll see this error. So, ensure that equals() is properly implemented. Here's a basic example which compares by the DB technical identity.

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

Don't forget to implement hashCode() as well:

public int hashCode() {
    return (id != null) 
         ? (getClass().hashCode() + id.hashCode())
         : super.hashCode();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文