1

Exception handler (blatantly stolen from here) :

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

The factory is registered in faces-config.xml (I guess all other things on the EJB/JPA side are unnecessary here). Exceptions.is() is an OmniFaces utility method.

When any exception as mentioned in the ladder is thrown, it should be registered as validation violation i.e. model values should not be updated nor action(Listener) methods, if involved any, should be executed/triggered as if some conversion/validation failed.


Where this is required :

This is essentially required, when dealing with optimistic locking on the persistence layer. For example, if a row/rows in a <p/h:dataTable> is attempted to delete (by pressing an Ajaxical command button or link) which is already modified by another user in another session behind the back, the javax.persistence.OptimisticLockException should be thrown which happens correctly using this exception handler mechanism.

Once that exception occurs, the system should keep on throwing this exception forever in all subsequent attempts to delete the same row/s until the user explicitly updates the stale values in those rows by triggering another brand new synchronous or asynchronous request (that should not involve deleting the stale rows as obvious).

This would turn out to be true only for the very first attempt to delete the stale row/s. In the following attempt, the row/s with stale values will be liable to be deleted because once this exception is thrown and a message is rendered in the very first attempt, the data table will also have already been updated with the latest updated row version. Therefore, in the immediate following request, the client will be sending rows with the latest updated row version in each row which will not be detected as concurrent modifications by the persistence provider as obvious. This is perfectly legit for the persistence provider to delete those rows. This may give end-users a bad experience - at least not so good as it should.

How can this be achieved using this exception handling mechanism - when any exception as specified by the exception handler as above, a user-friendly message should be rendered (which happens correctly) and neither model values nor action(Listener) methods should be triggered as if a conversion or validation is violated (i.e. the target <p:dataTable> or any other UI component holding rows should not get updated)?


EDIT :

The managed bean (view scoped) :

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

The data table :

<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>
Community
  • 1
  • 1
Tiny
  • 27,221
  • 105
  • 339
  • 599
  • *"i.e. model values should not be updated nor action(Listener) methods"* But I gather that those exceptions are thrown during invoke application phase? – BalusC Jul 20 '15 at 05:39
  • When one of the mentioned exceptions is thrown, the UI component holding data should not be updated in any feasible way, if this is not possible. – Tiny Jul 20 '15 at 07:47
  • Yes, I understood that part and I can answer that, but I'm trying to take a step back as this sounds somewhat fishy. E.g, Is the bean request scoped instead of view scoped? Or is the model refreshed during render response phase instead of invoke application phase directly after service call? – BalusC Jul 20 '15 at 07:47
  • Update the question. Beans are generally view scoped. – Tiny Jul 20 '15 at 08:21
  • Oh, it's a lazy data model. As to the concrete problem, does `partialViewContext.getRenderIds().clear()` work out for you? (ignoring how you (auto)update messages) – BalusC Jul 20 '15 at 08:36
  • Where to put that line? – Tiny Jul 20 '15 at 09:47
  • In the exception handler, when you need to show the message. If target message component doesn't have an `autoUpdate`, either make it so, or add its client ID to `getRenderIds()` afterwards. – BalusC Jul 20 '15 at 09:47
  • I added `facesContext.getPartialViewContext().getRenderIds().clear();`, `facesContext.getPartialViewContext().getRenderIds().add("messages");` and `facesContext.addMessage("messages", FacesMessage);` in the same order in one of the `if-else if-else` ladders along with ``. It prevents the table from being updated but the message is not displayed on ``. – Tiny Jul 20 '15 at 10:16
  • I removed only the data table using `facesContext.getPartialViewContext().getRenderIds().remove("form:dataTable");` before adding a `FacesMessage`. This works as needed but then it requires a unique fixed data table id, `form:dataTable` everywhere. Other data tables with a different id in the same or different page/naming container will not avail of this thing. – Tiny Jul 20 '15 at 10:39
  • I'd expect the `autoUpdate` to work here. Apparently it's scanned and added too early. – BalusC Jul 20 '15 at 11:01
  • ::fore-palm:: Sorry, I had been trying using a different XHTML file. The suggested approach works as needed. May please add it as an answer. Thank you. – Tiny Jul 20 '15 at 12:05

1 Answers1

2

and neither model values nor action(Listener) methods should be triggered

It would in first place be strange that the listed business service exceptions are thrown in other phase than the invoke application phase. Those exceptions are actually thrown during invoke application phase, right? That would be too late already to skip the update model values and invoke application phases.

The usual flow in an action method is:

  • if necessary: preprocess data, e.g. prepare service method arguments.
  • call service method (which can throw any of those exceptions).
  • if necessary: postprocess data, e.g. refreshing the bean's properties when not doing PRG.

So, if an exception is thrown from the service method, then the postprocessing step would already be skipped and the (view scoped) bean would still hold the old properties. The enduser would only be able to re-execute the same request when the bean is request scoped, or when the bean's properties are refreshed during render response.

That you're facing this problem thus suggests that the bean is request scoped, or that you're refreshing the bean's properties during render response, e.g. (in)directly in a getter method. As per the comments and the updated question, LazyDataModel is doing that. Perhaps reasonable in most cases, but definitely not in your case.


i.e. the target <p:dataTable> or any other UI component holding rows should not get updated

Anything you specify in <p:ajax update> or <f:ajax render> is in the API available by PartialViewContext#getRenderIds(). This returns a mutable collection, so you can simply clear out it.

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

Do it at the same moment you're handling the exception and adding the faces message.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Exceptions from the service layer cannot be thrown without a service call to an EJB which is to be made by an action(Listener) in turn in this case. I forgot to consider this intuitive thing which is not even directly related to JSF. I felt very bad after I realized the real story. I had been thinking merely in one direction. Otherwise, the question format could have been completely different. – Tiny Jul 20 '15 at 22:44
  • How would you otherwise have asked the question? – BalusC Jul 21 '15 at 06:58
  • Straight to the point with the data table and the managed bean code snippets beforehand and with a question like, "How to prevent a UI component (like a ``) from being Ajaxically updated, when an exception (or better one of predefined exceptions) is thrown from the service layer?" (mentioning how the application handles exceptions using an exception handler factory) omitting the "*Where this is required*" section in its entirely or perhaps, reducing it to a single line or two (especially the confusing part, "*Model values should not be updated nor action(Listener) methods*") – Tiny Jul 21 '15 at 08:56