Best practice for custom exception handling (custom error messages) coming from the database

Hi,

  1. I get a foreign key constraint violation message when trying to delete a record in a table which has references in another table, which is absolutely normal. Now I’m trying to handle the error in an internal class and customize the message shown to the client. Which solution do you recommend (extending RuntimeException and throwing it from the middleware to the client?) The documentation is not very clear. Other threads discuss only cuba.uniqueConstraintViolationPattern which is not useful in this case.

  2. A second question: Working with a screen extending AbstractLookup, I want to override the method which saves the instance in that screen. After the user creates, edits or removes a record, a Confirmation dialog opens: “Are you sure you want to delete selected elements?”. I want to override the ok action of that dialog (assuming that it is the right place where the lookup-screen is commited). Also it has something to do with the first question (this way I can include the commit method in a try-catch block and customize my message)
    Thanks.

Hi Tudor,

  1. The recommended way to handle exceptions is to register a Client-Level Exception Handler. It must be a managed bean of the client tier extending the AbstractGenericExceptionHandler class. In the constructor, define class names of exceptions to handle. For example:

    package com.company.sample.web;

    import com.haulmont.cuba.gui.WindowManager;
    import com.haulmont.cuba.gui.components.Frame;
    import com.haulmont.cuba.gui.exception.AbstractGenericExceptionHandler;
    import org.springframework.stereotype.Component;

    import javax.annotation.Nullable;

    @Component(“sample_ForeignKeyViolationExceptionHandler”)
    public class ForeignKeyViolationExceptionHandler extends AbstractGenericExceptionHandler {

     public ForeignKeyViolationExceptionHandler() {
         super("java.sql.SQLIntegrityConstraintViolationException");
     }
    
     @Override
     protected void doHandle(String className, String message, @Nullable Throwable throwable, WindowManager windowManager) {
         windowManager.showNotification("Cannot delete object", "There are references from other objects", Frame.NotificationType.ERROR);
     }
    

    }

  2. The removal is performed by the RemoveAction. You can customize its behavior, for example:

    package com.company.sample.web.customer;

    import com.haulmont.cuba.gui.components.AbstractLookup;
    import com.haulmont.cuba.gui.components.actions.RemoveAction;

    import javax.inject.Named;
    import java.util.Map;

    public class CustomerBrowse extends AbstractLookup {

     @Named("customersTable.remove")
     private RemoveAction customersTableRemove;
    
     @Override
     public void init(Map<String, Object> params) {
         customersTableRemove.setBeforeActionPerformedHandler(() -> {
             System.out.println("before remove");
             return true; // to continue
         });
    
         customersTableRemove.setAfterRemoveHandler(removedItems -> {
             System.out.println("removed " + removedItems);
         });
     }
    

    }

If it’s not enough, create your own action (possibly extending RemoveAction), override its actionPerform() method and use the action in the table and Remove button.

1 Like

Hi Konstantin,

Thanks for the replies.
Regarding the foreign key exception handler, it didn’t work initially because the master exception class was not correct. I replaced this code:


    public ForeignKeyViolationExceptionHandler(){
        super("java.sql.SQLIntegrityConstraintViolationException");
    }

with this one:


    public ForeignKeyViolationExceptionHandler(){
        super("org.postgresql.util.PSQLException");
    }

and so it worked!
(otherwise I got the same standard message as in the attachment)

errormsg

Yes, the exception class may depend on the database type in use. You could also list all possible exception types in the constructor, and even additionally implement the canHandle() method for more fine-grained control of what exceptions to handle.

Another question regarding exceptions.
Say I have an SQL exception (for ex. foreign key violation like in the ex. above) on a screen commit. Is there a way to pass parameters to the exception class located in the gui module (ForeignKeyViolationExceptionHandler) from the web module?
I want a more specific error message which includes deleted row values: the message should look something like this: “You cannot delete the product: “+ sProductName +”. It has references in other objects!” .
In this example project: GitHub - cuba-platform/sample-data-manipulation: DEPRECATED. See https://www.cuba-platform.com/guides/intro-working-with-data-in-cuba
; errors with parameters are thrown from the middleware. I tried to use classic try - catch blocks, encapsulating screen commit, but it won’t catch exceptions form the middleware.
Thanks.

You cannot pass any parameters to exception handlers, they can use only the information from the exception itself. However catching exceptions on screen commit should work. Could you provide the code of your try-catch block?

Of course:
On the init method of my AbstractLookup screen I have:


@Override
public void init(Map<String, Object> params) {
    super.init(params);
      
    RemoveAction removeAtributeAction = new RemoveAction(atributesTable, true, "removeAtributeAction"){
        @Override
        public void actionPerform(Component component) {
            try {
                super.actionPerform(component);
                showMessageDialog("Test", "Test message", MessageType.WARNING);
            } catch (Exception exc){
                showMessageDialog("Error", "My custom error message", MessageType.WARNING);
            }
        }
    };
    removeAtributeAction.setCaption("Remove item");
    atributesTable.addAction(removeAtributeAction);
}

and in the screen xml I’ve changed the standard remove action with my custom action:


        <treeTable id="atributesTable"
                   editable="true"
                   multiselect="true"
                   width="100%">
            <actions>
                <action id="create"
                        openType="DIALOG"/>
                <action id="edit"
                        openType="DIALOG"/>
                <action id="removeAtributeAction"/>
            </actions>
            <columns>
                <column id="atribute"
                        editable="false"/>
                <column id="selected"
                        editable="true"/>
            </columns>
            <rows datasource="atributesDs"/>
            <rowsCount/>
            <buttonsPanel id="buttonsPanel"
                          alwaysVisible="true">
                <button id="createBtn"
                        action="atributesTable.create"/>
                <button id="editBtn"
                        action="atributesTable.edit"/>
                <button id="removeBtn"
                        action="atributesTable.removeAtributeAction"/>
            </buttonsPanel>
        </treeTable>

At runtime, without try-catch, the super.actionPerform(component); command generates the foreign key violation error (first the deletion confirmation dialog is shown, assuming the user clicks OK).
With try-catch, as shown above, the program seems to continue execution after super.actionPerform(component); and both the deletion confirmation message and the Test message are shown simultaneously. So My custom error message is never reached.
It behaves as if the actionperform method will execute in a different thread.

Thanks,
Tudor

Hi Tudor,

You override the wrong method: actionPerform() just invokes the confirmation dialog and returns, the actual removing is done later in a callback method invoked by the OK action of the dialog. So you should override this callback method:

RemoveAction removeAction = new RemoveAction(atributesTable, true) {
    @Override
    protected void doRemove(Set selected, boolean autocommit) {
        try {
            super.doRemove(selected, autocommit);
            showNotification("Removed");
        } catch (Exception e) {
            showNotification("Error: " + e.toString());
        }
    }
};

Thank you very much. It works perfectly.

A post was split to a new topic: Client-level exception handler is not triggered

This post demonstrates a way to override the removeaction, that is not working properly anymore with Cuba 7, as there seem to be several interface changes.
What would be the correct new way, to override the action? The actual problem is, that I cannot instantiate a valid RemoveAction base object with a valid, non-null (injected) removeOperation field, resulting in a NullPointerException.

Thanks,
Dietmar

2 Likes

Hi Konstantin,

How can you catch a specific Exception instead of the top level exception? Would it be possible to distinguish the “ForeignKeyViolationException” from the “UniqueKeyConstraint” for example ?

Thanks!