Nuxeo Server

How to Bubble Errors from the Core Layer in the JSF UI

This page is scheduled for review and update. Check back soon for updated content!

The Nuxeo Platform proposes an easy model for implementing custom logic through the Event Listener system. Sometimes, you want to "bubble" in the UI up to the user errors that happens in that lower layer. This page explains how this can be done by using the RecoverableClientException mechanism, throughout an example that executes a chain in the listener which can easily be configured using Studio. In the end, this provides a nice pattern for implementing integrity checks based on automation.

Make sure that none of the listeners running before your custom listener modify the current document, otherwise your pending changes will be lost when coming back on the form (a rollback would have been done).

Example

  1. Create a custom listener (e.g. listening to About To Create event). This listener is going to trigger your Automation chain.

    package org.nuxeo.sample;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.nuxeo.ecm.automation.AutomationService;
    import org.nuxeo.ecm.automation.OperationContext;
    import org.nuxeo.ecm.core.api.ClientException;
    import org.nuxeo.ecm.core.api.DocumentModel;
    import org.nuxeo.ecm.core.api.RecoverableClientException;
    import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
    import org.nuxeo.ecm.core.event.DeletedDocumentModel;
    import org.nuxeo.ecm.core.event.Event;
    import org.nuxeo.ecm.core.event.EventListener;
    import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
    import org.nuxeo.ecm.platform.web.common.exceptionhandling.ExceptionHelper;
    import org.nuxeo.runtime.api.Framework;
    
    public class ListenerIntegrityCheck implements EventListener {
    
        private static final Log log = LogFactory.getLog(ListenerTest.class);
    
        public void handleEvent(Event event) throws ClientException {
            String eventName = event.getName();
            if (!DocumentEventTypes.ABOUT_TO_CREATE.equals(eventName)) {
                return;
            }
            if (!(event.getContext() instanceof DocumentEventContext)) {
                return;
            }
            DocumentEventContext ctx = (DocumentEventContext) event.getContext();
            DocumentModel doc = ctx.getSourceDocument();
            // Skip shallow document
            if (doc instanceof DeletedDocumentModel) {
                return;
            }
            if (doc.isProxy() || doc.isVersion()) {
                return;
            }
                AutomationService service = Framework.getService(AutomationService.class);
            OperationContext automationCtx = new OperationContext();
            automationCtx.setInput(doc);
            try {
                service.run(automationCtx, "testValidation");
            } catch (Exception e) {
                // The last validation operation throw an exception if there is at
                // least one invalidation
                Throwable unwrappedException = ExceptionHelper.unwrapException(e);
                if (unwrappedException instanceof RecoverableClientException) {
                    event.markBubbleException();
                    event.markRollBack();
                    throw new ClientException(unwrappedException);
                } else {
                    log.error("Error");
                }
            }
        }
    }
    
  2. Create your custom operation, which is going to be deployed in your Studio Automation chain.

    package org.nuxeo.sample;
    import org.nuxeo.ecm.automation.OperationContext;
    import org.nuxeo.ecm.automation.core.Constants;
    import org.nuxeo.ecm.automation.core.annotations.Context;
    import org.nuxeo.ecm.automation.core.annotations.Operation;
    import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
    import org.nuxeo.ecm.core.api.ClientException;
    import org.nuxeo.ecm.core.api.DocumentModel;
    @Operation(id = OperationValidation1.ID, category = Constants.CAT_DOCUMENT, label = "OperationValidation1", description = "")
    public class OperationValidation1 {
        public static final String ID = "OperationValidation1";
        @Context
        OperationContext ctx;
        @OperationMethod
        public DocumentModel run(DocumentModel input) throws ClientException {
            if (!input.getTitle().equals("title")) {
                String value = "- the document title is invalid - <br/>";
    // Collecting different invalidation message in one context variable
                ctx.put("messageError", value);
            }
            return input;
        }
    }
    
    
  3. Check one last validation rule and throw a CustomBubbleException in case of one validation at least has failed.

    package org.nuxeo.sample;
    import org.nuxeo.ecm.automation.OperationContext;
    import org.nuxeo.ecm.automation.core.Constants;
    import org.nuxeo.ecm.automation.core.annotations.Context;
    import org.nuxeo.ecm.automation.core.annotations.Operation;
    import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
    import org.nuxeo.ecm.core.api.ClientException;
    import org.nuxeo.ecm.core.api.DocumentModel;
    @Operation(id = OperationValidation2.ID, category = Constants.CAT_DOCUMENT, label = "OperationValidation2", description = "")
    public class OperationValidation2 {
        public static final String ID = "OperationValidation2";
        @Context
        OperationContext ctx;
        @OperationMethod
        public void run(DocumentModel input) throws ClientException {
            if (input.getId() == null) {
    // Collecting different invalidation message in one context variable
                Object messageError = ctx.get("messageError");
                String error = "- the document ID is not compliant -";
                ctx.put("messageError",
                        (messageError != null ? messageError.toString() : "")
                                + error);
            }
    // Retrieving all invalidation messages to throw in RecoverableException
            Object message = ctx.get("messageError");
            if (message != null) {
                throw new CustomBubbleException("Process Invalidation",
                        "Invalidations:<br/>" + message.toString(), null);
            }
        }
    }
    
  4. Check also the custom exception.

    package org.nuxeo.sample;
    import org.nuxeo.ecm.core.api.RecoverableClientException;
    public class CustomBubbleException extends RecoverableClientException {
        private static final long serialVersionUID = 1L;
        public CustomBubbleException(String message, String localizedMessage,
                String[] params) {
            super(message, localizedMessage, params);
        }
    
        public CustomBubbleException(String message, String localizedMessage,
                String[] params, Throwable cause) {
            super(message, localizedMessage, params, cause);
        }
    }
    
  5. Finally, you can create in Studio your chain after exporting your custom operations.

    Here is the result after trying to create a document: The end user sees error message displaying all collected invalidations:


2 months ago Loubna Benzaama Fix Showcase Content add-on doc
2 years ago Solen Guitter 16
2 years ago Solen Guitter 15
4 years ago Manon Lumeau 14
4 years ago Alain Escaffre 13
4 years ago Solen Guitter 12
4 years ago Florent Guillaume 11
4 years ago Vladimir Pasquier 10
5 years ago Solen Guitter 9
5 years ago Manon Lumeau 8
5 years ago Vladimir Pasquier 7
5 years ago Vladimir Pasquier 6
5 years ago Vladimir Pasquier 5
5 years ago Vladimir Pasquier 4
5 years ago Vladimir Pasquier 3
5 years ago Vladimir Pasquier 2
5 years ago Alain Escaffre 1
History: Created by Alain Escaffre

We'd love to hear your thoughts!

All fields required