JSF: как предотвратить stackoverflow из-за рекурсии во время фазы сборки (несмотря на проведенный тест)
Извинения за не абстрагирование этой проблемы в отдельном тестовом случае, я надеюсь, что пример из реального проекта достаточно прост, чтобы описать проблему.
У меня есть веб-приложение JavaEE/JPA2/JSF, в котором каждый элемент @Entity (или подкласс) имеет шаблонную страницу view.xhtml и стандартный составной компонент генератора ссылок: view_link.xhtml, вызывается как GET с идентификатором базы данных, как параметр. Часть (только) каждой страницы просмотра представляет сводку экспертной системы; эта часть может быть абстрагирована как составной компонент для включения на страницу просмотра или где-либо еще.
Я ввел мозаичное всплывающее окно Primefaces p: dialog для отображения этой части итоговой экспертной системы (и любой дополнительной диагностики) при нажатии маленького значка состояния, отображаемого рядом с ссылкой вида. Если вы укажете значок состояния x, он будет выглядеть так:
x Link_to_Element_by_ID
Нажмите "Link_to_Element_by_ID", и он отобразит страницу полного просмотра.
Нажмите значок "x" (индикатор сбоя теста экспертной системы), и он отобразит диалог p: с помощью сводки экспертной системы (только).
Таким образом, часть экспертной системы на странице просмотра совместно используется как составной компонент.
Но это может привести к рекурсии и Stackoverflow, если:
-
Сводка экспертной системы popup p: dialog отображает индикатор состояния элемента проверяемого элемента.
-
Я включаю дополнительные ссылки на представление элемента вместе со статусом (который сам запустит диалог p: для сводки экспертной системы).
Я попытался использовать обработанные тесты, используя атрибут блокировки рекурсии 'preventRecursionOnDialog', но он не работает, по-видимому, потому, что рекурсия происходит во время фазы сборки.
Q: Как заблокировать возможную рекурсию с помощью тестовой переменной?
Кроме того, я попытался использовать тесты c: if вместо JSF 'rendered', но, похоже, проверенная переменная недоступна в @ViewScoped.
Пример, для элемента Activity, где util_primefaces: dialog_summary - это просто индивидуальная инкапсуляция диалога p:.
От util: status_activity.xhtml:
<composite:attribute
name="activity"
required="true"
type="com.example.entity.Activity"
/>
<composite:attribute
name="preventRecursionOnDialog"
required="false"
default="false"
type="java.lang.Boolean"
/>
</composite:interface>
<composite:implementation>
<util_primefaces:dialog_summary
header="Expert system summary report"
rendered="#{not cc.attrs.preventRecursionOnDialog}"
element="#{cc.attrs.activity}">
<!-- causes StackOverflowError -->
<util:warn_insufficient_subactivities
activityContainer="#{cc.attrs.activity}"
humanTypeDescription="composite activity"
preventRecursionOnDialog="true"
/>
<util:expertsystem_activity activity="#{cc.attrs.activity}"/>
</util_primefaces:dialog_summary>
..
<span
onclick="#{not cc.attrs.preventRecursionOnDialog ? ('dialog'.concat(cc.attrs.activity.id).concat('.show();')) : ''}"
style="float:left;"
class="icon-completed-#{cc.attrs.activity.acceptedEffective}-small"
title=".."
> </span>
Использовать: warn_insufficient_subactivities (который показывает, какие субактивности объединенной активности не прошли тест экспертной системы) может вызвать рекурсию:
<cc:interface>
<cc:attribute name="activityContainer" required="true" type="com.example.entity.IActivityContainer"/>
<cc:attribute name="humanTypeDescription" required="true" type="java.lang.String"/>
<cc:attribute
name="preventRecursionOnDialog"
required="false"
default="false"
type="java.lang.Boolean"
/>
</cc:interface>
<cc:implementation>
<h:panelGroup
rendered="#{not cc.attrs.activityContainer.sufficientSubActivitiesAccepted}">
<util:warn_box
message=".."
>
<!-- CAUTION: can cause Stackoverflow when list included in expertsystem p:dialog popup -->
<util:list_activity_compact
list="#{cc.attrs.activityContainer.activities}"
preventRecursionOnDialog="#{cc.attrs.preventRecursionOnDialog}"
rendered="#{not cc.attrs.preventRecursionOnDialog}"
/>
</util:warn_box>
И утилита: list_activity_compact показывает список со значками индикатора состояния (который, в свою очередь, может предложить всплывающее окно p: диалог со сводкой экспертной системы и может рекурсивно) и использовать: view_link:
<cc:interface>
<cc:attribute
name="list" required="true" type="java.util.List"
/>
<cc:attribute
name="preventRecursionOnDialog"
required="false"
default="false"
type="java.lang.Boolean"
/>
</cc:interface>
<cc:implementation>
<h:panelGroup display="block">
<ul class="view-field-list-medium">
<ui:repeat var="a" value="#{cc.attrs.list}">
<li class="view-field-list">
<util:status_activity
activity="#{a}"
preventRecursionOnDialog="#{cc.attrs.preventRecursionOnDialog}"/>
<util:view_link element="#{a}"/>
</li>
</ui:repeat>
</ul>
</h:panelGroup>
</cc:implementation>
Точка вопроса заключается в том, что тест rendered = "# {not cc.attrs.preventRecursionOnDialog}" недостаточен для блокировки рекурсии, даже если часть, которая будет рекурсивно, не будет отображаться (заблокирована обработанным тестом), похоже, что рекурсия может произойти во время фазы сборки JSF.
Кстати. Я часто сталкиваюсь с аналогичной проблемой, когда хочу только сделать конкретный составной компонент привязанным к типу, в пределах подмножества выбора типа; выполнение теста типа в "rendered" недостаточно для предотвращения ошибки типа. Представьте себе, что "значение" может быть одним из многих подклассов Element, включая Activity, но нужно только отобразить следующую составную часть компонента для Activity:
<util:component_for_Activity_only
activity="#{cc.attrs.value}"
rendered="#{cc.attrs.value['class'].simpleName=='Activity'}"
/>
(cf. экземпляр проверки на языке выражений EL и обратите внимание, что это тестовое решение на основе класса String не очень гибко, оно не работает для подклассов или для интерфейсные тесты.)
Опять же, попытка блокировать вызов с помощью "rendered" недостаточна, похоже, что тест типа завершился неудачно уже во время фазы сборки. Решение проблемы рекурсии также предложит решение этой проблемы. Даже введение (наконец) экземпляра в JSF2 (проголосовать здесь пожалуйста http://java.net/jira/browse/JSP_SPEC_PUBLIC-113) здесь не помогло бы, если бы оно использовалось только в 'rendered',
Ответы
Ответ 1
Этот вопрос является прекрасным примером того, почему я не люблю JSF: как только вы делаете что-то нетривиальное (например, - gasp - пытаетесь повторно использовать код в больших масштабах), вам нужны знания о внутренних функциях JSF.
JSF представляет представление с деревом компонентов. Это дерево построено из определения представления обработчиками тегов, имеет статус stateful и живет до тех пор, пока пользователь не покинет представление. Включение составного компонента выполняется обработчиком тегов. c: if также реализуется обработчиком тегов.
Дерево компонентов перемещается в течение каждой фазы жизненного цикла обработки запроса. Однако отдельные компоненты могут решить, обрабатываются ли (или сколько раз) их дети. То, как реализуется атрибут rendered: на каждом этапе компонент проверяет, будет ли он отображаться, и пропускает обработку самого себя (и его дочерних элементов), если нет.
Область просмотра JSF хранится внутри UIViewRoot, которая является корнем node дерева компонентов. Поэтому он недоступен, когда обработчики тегов обрабатываются. Это один из его многочисленных недостатков: -)
Итак, что вы можете сделать?
-
Вы можете включить составной компонент для каждого действия в дереве действий, но поскольку это включение происходит при времени сборки, оно не может выполняться по требованию. Даже устраняя проблемы взаимной рекурсии, скорее всего, расточительно создавать диалог для каждой субзависимости, исходя из того, что пользователь захочет увидеть этот конкретный диалог. Но вы можете, конечно, ограничить рекурсию с помощью c: if, вам просто нужно поместить информацию в область, доступную во время сборки.
-
В качестве альтернативы вы можете создать единое диалоговое окно в дереве компонентов, но при этом оно будет показывать разные действия в разное время, привязывая текущую активность к элементу EL, чью цель вы обновляете. Конечно, это означает, что за один раз отображается только один диалог. Если вам нужно уложить диалоги subactivities, вы можете создать столько диалогов, сколько древо будет глубоким.
-
В качестве альтернативы вы можете создать один компонент, который рекурсивно обрабатывает себя на каждой фазе жизненного цикла обработки запроса. Это может быть либо существующий компонент, который вы адаптируете (например, древовидный компонент), либо написанный с нуля (что не совсем тривиально, потому что дерево компонентов является состоятельным, а состояние должно быть сохранено и восстановлено для каждой итерации дочерних компонентов).
Ответ 2
Отвечая на собственные вопросы после дальнейших исследований и испытаний.
Во-первых, благодаря meriton, ваш ответ точно не ответил на мой вопрос, но поставил меня на правильный путь.
Короткий ответ заключается в том, что вы можете использовать c: если тесты для управления рекурсией во время фазы сборки для @ViewScoped с Mojarra 2.1.18. В моем случае это теперь работает:
<c:if test="#{not cc.attrs.preventRecursionOnDialog}">
Так часто меня приводили к этому ответу (необходимость обновить до Mojarra >= 2.1.18 и объяснения об улучшенной обработке тегов JSTL в @ViewScoped) вкладами BalusC в другие сообщения, включая:
https://java.net/jira/browse/JAVASERVERFACES-1492
Проблема проверки JSF2 Viewscope
http://balusc.blogspot.com.au/2010/06/benefits-and-pitfalls-of-viewscoped.html
JSTL в JSF2 Facelets... имеет смысл?
Каковы основные недостатки Java Server Faces 2.0?
Я считаю этот вопрос чрезвычайно важным для любого, кто работает с JSF! Способность контролировать то, что строится - в отличие от того, что визуализируется - легко в @ViewScoped, решает многие проблемы и открывает множество возможностей, и я рекомендую, чтобы кто-либо, кто серьезно работает с JSF, не торопился, чтобы прочитать замечания BalusC в над ссылками.
BalusC, если вы это прочитали, узнайте, что вы являетесь истинным героем JavaServer Faces и Enterprise Java. От имени каждого энтузиаста JSF я могу поблагодарить вас.
И благодаря Эд Бернсу и Теду Годдарду за отличную работу по отчетности и исправлению этого: https://java.net/jira/browse/JAVASERVERFACES-1492 Это важное улучшение для JSF2.
Наконец, мне пришлось использовать грязный трюк, чтобы установить Mojarra 2.1.21 на NetBeans7.1 + Glassfish3.1.1 (требуется из-за несовместимости сторонних разработчиков), как описано здесь: JSF как обновление до Mojarra 2.1.21 в Netbeans7.1 (только сбой jsf-api.jar и jsf-impl.jar не удается)
Это был отличный результат для моего проекта. Что бы я сделал без Stackoverflow:)