Я хочу использовать <o:validateBean> для проверки моего компонента на уровне класса. Боб это:
@Named
@ConversationScoped
public class ValidateClassLevelBean implements Serializable {
...
@Inject
private Conversation con;
@Min(value = 2)
@Max(value = 5)
private int objectCount;
@Valid
private List<DemoData> listData;
public String reInit() {
if (!con.isTransient()) {con.end();}
con.begin();
listData = new ArrayList<>(objectCount);
for (int i = 0; i < objectCount; i++) {
listData.add(new DemoData("bbb", 3));
}
return "validProduct_2";
}
public String action() {
for (DemoData d : listData) {
logger.info("[listData: <" + d.toString() + ">]");
}
con.end();
return "index";
}
...
}
Фейслет выглядит так:
<h:form id = "classLevelFormCustomCopier">
...
<table>
<ui:repeat value = "#{validateClassLevelBean.listData}" var = "data" varStatus = "loop">
<tr>
<td>
<h:inputText id = "dataInput"
value = "#{validateClassLevelBean.listData[loop.index]}"/>
</td>
</tr>
</ui:repeat>
</table>
<h:commandButton value = "submit" action = "#{validateClassLevelBean.action}">
<f:ajax execute = "@form" render = "@form"/>
</h:commandButton>
<o:validateBean value = "#{validateClassLevelBean}"
validationGroups = "de.test.wholebeanvalidation.validProduct.DemoDataGroup" />
В DemoData
нет ничего особенного (существует forClass
-конвертер):
@ValidDemoData(groups = DemoDataGroup.class)
public class DemoData implements Serializable {
...
private String strData;
private int intData;
public DemoData() {
this.strData = "";
this.intData=0;
}
public DemoData(DemoData d) {
this.strData =d.getStrData();
this.intData = d.getIntData();
}
...
}
Аннотация ограничения проверки:
@Constraint(validatedBy = { DemoDataValidator.class })
@Documented
@Target({ TYPE, METHOD, FIELD })
@Retention(RUNTIME)
public @interface ValidDemoData {
String message() default "Invalid product";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Валидатор:
public class DemoDataValidator implements ConstraintValidator<ValidDemoData, ValidateClassLevelBean> {
...
@Override
public void initialize(ValidDemoData constraintAnnotation) {
logger.info("[ init ]");
}
@Override
public boolean isValid(ValidateClassLevelBean value, ConstraintValidatorContext context) {
...
return true;
}
}
Удалил copier = "..."
и появилось исключение:
[2024-07-27T18:02:30.870+0200] [Payara 6.2024.1] [SCHWERWIEGEND] [] [org.omnifaces.taghandler.ValidateBean] [tid: _ThreadID=158 _ThreadName=http-thread-pool::http-listener-2(3)] [timeMillis: 1722096150870] [levelValue: 1000] [[
Exception occured while doing validation.
java.lang.ClassCastException: class java.lang.Class cannot be cast to class java.lang.reflect.ParameterizedType (java.lang.Class and java.lang.reflect.ParameterizedType are in module java.base of loader 'bootstrap')
at org.omnifaces.util.Reflection.setBeanPropertyWithDefaultValue(Reflection.java:410)
at org.omnifaces.util.Reflection.getBeanProperty(Reflection.java:397)
at org.omnifaces.util.Reflection.getBase(Reflection.java:336)
at org.omnifaces.util.Reflection.setBeanProperties(Reflection.java:314)
at org.omnifaces.taghandler.ValidateBean$3.invoke(ValidateBean.java:456)
at org.omnifaces.taghandler.ValidateBean$ValidateBeanCallback.run(ValidateBean.java:751)
at org.omnifaces.util.Events.lambda$wrap$1(Events.java:334)
at org.omnifaces.util.Events$4.afterPhase(Events.java:370)
at org.omnifaces.eventlistener.CallbackPhaseListener.afterPhase(CallbackPhaseListener.java:69)
at com.sun.faces.lifecycle.Phase.handleAfterPhase(Phase.java:148)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:78)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:159)
at jakarta.faces.webapp.FacesServlet.executeLifecyle(FacesServlet.java:691)
at jakarta.faces.webapp.FacesServlet.service(FacesServlet.java:449)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1554)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:331)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:211)
at org.glassfish.tyrus.servlet.TyrusServletFilter.doFilter(TyrusServletFilter.java:83)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:253)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:211)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:257)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:166)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:757)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:577)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:158)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:372)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:239)
at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:520)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:217)
at org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:190)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:535)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:515)
at java.base/java.lang.Thread.run(Thread.java:1583)
Спасибо за ваши усилия по этому делу. Я думал, что @ValidXyz(groups=...)
-аннотация принадлежит проверяемому классу, Product
в демонстрационном примере - всему вспомогательному компоненту в моем случае (если этот пример работает, я добавлю больше полей в вспомогательный компонент для проверки комбинации полей).
Аннотацию ограничения проверки следует помещать в класс модели, а не в класс контроллера.
Обновил мой код и исходную публикацию.
(существует forClass-Converter): Ого, так класс DemoData
на самом деле обрабатывается как свойство, а не как bean/entity/dto/vo? Это было неожиданно. Изначально я ожидал, что вы просто показали упрощенный пример xhtml в начальном вопросе, в котором свойства bean-компонента вообще не упоминаются, в то время как я использовал тот же пример xhtml на витрине, как было указано в исходном вопросе, в результате чего на свойства bean-компонента были правильно указаны. Этот вариант использования никогда не рассматривался и не анализировался при разработке o:validateBean. Мне нужно будет еще раз посмотреть здесь.
Я уже не уверен, связана ли эта проблема с <o:validateBean>
. Когда я отказываюсь от всех проверок и сосредотачиваюсь только на сборе данных через <ui:repeat>
в сочетании с forClass
конвертером, конвертер forClass
не вызывается для элементов списка, и в итоге я получаю ситуацию «Строка вместо DemoData». Возможно, это скорее проблема моджарры, чем проблема OmniFaces.
Вы правы, это работает, когда конвертер явно зарегистрирован в h:inputText. Это не проблема Мохарры/Faces, это проблема EL/дженериков. Вот близкие вопросы и ответы по этому поводу: stackoverflow.com/q/19872633
существует forClass-конвертер
В конечном итоге наблюдаемая проблема вызвана не <o:validateBean>
. Это вызвано стиранием общего типа и тем, что EL (эти #{..}
вещи) в текущей версии не может с этим справиться. Устанавливая новый элемент в List
, EL не знает, каков общий тип списка, и поэтому предполагает значение по умолчанию String
.
Вы можете решить эту проблему, явно зарегистрировав преобразователь во входном компоненте или, как вы обнаружили, используя простой массив вместо List
.
<h:inputText id = "dataInput"
value = "#{validateClassLevelBean.listData[loop.index]}"
converter = "yourDemoDataConverter" />
Интересно, что этот факт не упоминается в книгах и документах. Еще раз спасибо за ваши усилия по этому делу.
К вашему сведению: я заметил, что <o:validateBean showMessageFor = "@violating"> не работал в этом конкретном случае использования, поэтому я исправил это: github.com/omnifaces/omnifaces/issues/827 . Вам также может оказаться полезным соответствующий интеграционный тест: github.com/omnifaces/omnifaces/commit/…
На мой взгляд, такой пример для проверки стоит опубликовать на более видном месте. Спасибо.
После небольшой очистки кода я не могу воспроизвести его с помощью OmniFaces 4.4. Я удалил только
@ValidProduct
из класса управляемого компонента, поскольку он на самом деле принадлежит классуDemoData
(см. также демонстрационный пример). Я не использовал копировальный аппарат, так как в данном случае это не требуется. Когда я неправильно сохраняю@ValidProduct
в компоненте управления, я получаю другое исключениеHV000030: No validator could be found for constraint 'com.example.ValidProduct' validating type 'com.example.ValidateClassLevelBean'.
.