簡體   English   中英

在JSF中將異常注冊為轉換/驗證沖突

[英]Register exceptions as a conversion/validation violation in JSF

異常處理程序(從此處公然被盜):

public final class ApplicationExceptionHandler extends ExceptionHandlerWrapper {

    private final ExceptionHandler wrapped;

    public ApplicationExceptionHandler(ExceptionHandler wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public void handle() throws FacesException {
        FacesContext facesContext = FacesContext.getCurrentInstance();

        for (Iterator<ExceptionQueuedEvent> iter = getUnhandledExceptionQueuedEvents().iterator(); iter.hasNext();) {
            Throwable exception = Exceptions.unwrap(iter.next().getContext().getException());

            if (Exceptions.is(exception, EntityNotFoundException.class)) {
                FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_FATAL, "Summary", "Message");
                facesContext.addMessage(null, facesMessage);
            } else if (Exceptions.is(exception, DatabaseConstraintViolationException.class)) {
                FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_FATAL, "Summary", "Message");
                facesContext.addMessage(null, facesMessage);
            } else if (Exceptions.is(exception, DuplicateEntityException.class)) {
                FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_FATAL, "Summary", "Message");
                facesContext.addMessage(null, facesMessage);
            } else if (Exceptions.is(exception, EntityAlreadyModifiedException.class)) { // OptimisticLockException
                FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_FATAL, "Summary", "Message");
                facesContext.addMessage(null, facesMessage);
            } else if (Exceptions.is(exception, DatabaseException.class)) {
                FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_FATAL, "Summary", "Message");
                facesContext.addMessage(null, facesMessage);
            } else {
                // Render a global error page, if any other exceptions occur.
                String errorPageLocation = "/WEB-INF/error_pages/GeneralError.xhtml";
                facesContext.setViewRoot(facesContext.getApplication().getViewHandler().createView(facesContext, errorPageLocation));
                facesContext.getPartialViewContext().setRenderAll(true);
                facesContext.renderResponse();
            }
        }

        // getWrapped().handle();
    }

    @Override
    public ExceptionHandler getWrapped() {
        return wrapped;
    }
}

工廠注冊在faces-config.xml (我想這里不需要EJB / JPA方面的所有其他內容)。 Exceptions.is()是OmniFaces實用程序方法。

當引發梯子中提到的任何異常時,應將其注冊為驗證沖突,即,不應更新模型值,也不應執行/觸發action(Listener)方法(如果涉及),就好像某些轉換/驗證失敗一樣。


需要這樣做的地方:

在處理持久層上的樂觀鎖定時,這實際上是必需的。 例如,如果嘗試刪除<p/h:dataTable>的行/行(通過按Ajaxical命令按鈕或鏈接),而該用戶已經在后面的另一個會話中的另一個用戶修改了該行,則為javax.persistence.OptimisticLockException使用此異常處理程序機制時,應該引發javax.persistence.OptimisticLockException正確發生。

一旦發生該異常,系統應在所有后續刪除同一行的嘗試中永遠永遠拋出該異常,直到用戶通過觸發另一個全新的同步或異步請求來顯式更新這些行中的陳舊值(這不應該涉及刪除陳舊的行)。

事實證明,這僅適用於首次嘗試刪除過時的行的情況。 在下面的嘗試中,具有陳舊值的行可能會被刪除,因為一旦拋出此異常並在第一次嘗試中呈現了一條消息,數據表也將已經使用最新的更新的行版本進行了更新。 。 因此,在緊隨其后的請求中,客戶端將發送每行中具有最新更新的行版本的行,這顯然不會被持久性提供程序檢測為並發修改。 持久性提供程序刪除這些行是完全合法的。 這可能會給最終用戶帶來不好的體驗-至少不如應有的那樣好。

如何使用這種異常處理機制來實現這一點-當上述異常處理程序指定的任何異常時,都應呈現用戶友好的消息(正確發生),並且不應觸發模型值或action(Listener)方法如果違反了轉換或驗證(即目標<p:dataTable>或其他任何包含行的UI組件不應更新)?


編輯:

托管Bean(視圖范圍):

@Named
@ViewScoped
public class Bean extends LazyDataModel<Entity> implements Serializable {

    @Inject
    private Service service;
    private List<Entity> selectedValues; // Getter & setter.

    private static final long serialVersionUID = 1L;

    @Override
    public List<Entity> load(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, Object> filters) {

        setRowCount(service.rowCount());
        // Other logic.
        return service.getList(first, pageSize, map, filters);
    }

    public void delete(ActionEvent event) {
        if (service.delete(selectedValues)) { // EJB call to delete selected rows in <p:dataTable>.
            // FacesMeaage - delete succeeded.
        } else {
            // FacesMeaage - delete failed.
        }
    }

    // This method is invoked before delete() that
    // just warns the user about deletion of rows using <p:confirmDialog>.
    public void confirmDelete(ActionEvent event) {
        if (selectedValues != null && !selectedValues.isEmpty()) {
            // FacesMessage - rows are selected/checked in <p:dataTable>.
        } else {
            // FacesMessage - rows are not selected/checked in <p:dataTable>.
        }
    }
}

數據表:

<p:dataTable id="dataTable"
             var="row"
             value="#{bean}"
             lazy="true"
             sortMode="multiple"
             selection="#{bean.selectedValues}"
             rows="10">

    <p:column selectionMode="multiple">
        <f:facet name="footer">

            <p:commandButton oncomplete="PF('confirmDelete').show()"
                             update=":form:confirmDialog"
                             process=":form:dataTable"
                             actionListener="#{bean.confirmDelete}"/> <!---->
        </f:facet>
    </p:column>

    ...
    ...
    ...

    <p:column headerText="Edit">
        <p:rowEditor/>
    </p:column>
</p:dataTable>

<p:confirmDialog id="confirmDialog"
                 widgetVar="confirmDelete"
                 message="Message from bean">

    <p:commandButton id="confirmDelete"
                     value="Yes"
                     process="@this"
                     update="dataTable messages"
                     oncomplete="PF('confirmDelete').hide()"
                     actionListener="#{bean.delete}"/> <!---->

    <p:commandButton id="declineDelete"
                     value="No" 
                     onclick="PF('confirmDelete').hide()"
                     type="button"/>
    </p:confirmDialog>

既不應該觸發模型值也不應該觸發action(Listener)方法

首先,列出的業務服務異常會在調用應用程序階段以外的其他階段拋出,這很奇怪。 這些異常實際上是在調用應用程序階段拋出的,對吧? 跳過更新模型值並調用應用程序階段已經為時已晚。

動作方法的通常流程是:

  • 如有必要:預處理數據,例如准備服務方法參數。
  • 調用服務方法(可以拋出任何這些異常)。
  • 如有必要:后處理數據,例如在不執行PRG時刷新bean的屬性。

因此,如果從service方法拋出異常,那么將已經跳過了后處理步驟,並且(視圖作用域)bean仍將保留舊屬性。 最終用戶僅在對bean進行請求范圍限定時或在渲染響應期間刷新bean的屬性時才能夠重新執行相同的請求。

因此,您面臨的這個問題表明該bean是請求范圍的,或者在渲染響應期間正在刷新bean的屬性,例如(直接)在getter方法中。 根據評論和更新的問題, LazyDataModel正在執行此操作。 在大多數情況下可能是合理的,但在您的情況下絕對不是。


即目標<p:dataTable>或其他任何包含行的UI組件都不應更新

您在<p:ajax update><f:ajax render>指定的任何內容都在PartialViewContext#getRenderIds()可用的API中。 這將返回一個可變的集合,因此您可以簡單地清除它。

facesContext.getPartialViewContext().getRenderIds().clear();

在處理異常並添加faces消息的同時執行此操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM