Thursday, 5 June 2008

JDev ADF: How to log all errors to the database

Sometimes users have the requirement that all application errors are logged to a table. In traditional Oracle development tools like Oracle Forms this was easy, simply override the ON-ERROR trigger and write PL/SQL code to call your favourite database logging code, or simply issue the insert statement to the associated logging table yourself.

In JDeveloper's ADF framework it is just as easy and is achieved in the following steps. Please note this post is written against JDev 11gTP4 so your mileage may vary in whatever version you're using:

In the JDev ADF 11g Guide section 27.8 "Customizing Error Handler", it describes you can override the ADF binding layer DCErrorHandlerImpl by creating your own custom handler. Simple create a class that is a subtype of DCErrorHandlerImpl, and override the chokepoint method reportException to capture all exceptions raised by the ADF stack. It may look something like this:

public class ErrorHandlerImpl extends DCErrorHandlerImpl {

  public ErrorHandlerImpl() { super(true); }

  @Override
  public void reportException(DCBindingContainer dCBindingContainer, Exception exception) {
    super.reportException(dCBindingContainer, exception);
  }
}


Then in the associated DataBindings.cpx file set the ErrorHandlerClass property to specify your new class and package name: eg. view.controller.frameworkExtension.ErrorHandlerImpl.

If you receive an error in this step, the following post may resolve your issues.

Unlike Oracle Forms where the programmer needs to override the ON-ERROR trigger in every form, essentially here we're doing it for the entire ADF application, saving us a fair amount of work.

Next we create a custom client interface method in our application module that (for example – we can do anything we want here) calls a database error logging package/procedure called error_pkg.log_error via JDBC:

public void logError(String message) {
  CallableStatement statement = null;
  try {
    String plsql = "BEGIN error_pkg.log_error(?); END;";
    DBTransaction transaction = getDBTransaction();
    statement = transaction.createCallableStatement(plsql, 1);
    statement.setString(1, message);
    int rows = statement.executeUpdate();
  } catch (SQLException e) {
    throw new JboException(e);
  } finally {
    try {
    } catch (SQLException e) {
      /* ignore */
    }
  }
}


In Oracle Forms the above code would simple be a PL/SQL call within an ON-ERROR PL/SQL trigger. In JDev obviously we work with Java code, and JDBC is used to make the associated database PL/SQL calls.

Once we create the client interface method and expose it in the ApplicationModule, we then need to create a binding to allow the method to be called via our custom error handler reportException() method from previous. We could create a binding in each page's pageDef bindings file to call the custom logging method, but this would be painful in a large application with many pages and the developer could easily forget to add the binding. Instead in 11g we can create an ADF Faces RC page template with an associated page definitions file, ensure to create (all!) our pages based on the page template (which any good application will do of course! ;), and create the methodBinding to call our AM log method from above:

<bindings>
  <methodaction id="logError" instancename="AppModuleDataControl.dataProvider" datacontrol="AppModuleDataControl.dataProvider" requiresupdatemodel="true" action="invokeAction" methodname="logExceptionToDatabase" isviewobjectmethod="false">
    <nameddata ndname="message" ndtype="java.lang.String"/>
  ... and so on ...
  </methodaction>
</bindings>


Of course we don't need to type the above in by hand, it can be easily created via the structure window Add bindings facilities in the JDev IDE.

Finally we return to our custom error handler class ErrorHandlerImpl, and change the reportException() method to use the binding to indirectly call the AM client interface method as follows:

public void reportException(DCBindingContainer dCBindingContainer, Exception exception) {
  BindingContext bindingContext = dCBindingContainer.getBindingContext();
  DCBindingContainer templateBindingContainer = bindingContext.findBindingContainer("view_templatePageDef");

  String error = getDisplayMessage(dCBindingContainer.getBindingContext(), exception);

  OperationBinding operationBinding = templateBindingContainer.getOperationBinding("logError");
  Map params = ob.getParamsMap();
  params.put("message", error);
  ob.execute();

  super.reportException(dCBindingContainer, exception);
}


The caveat to this approach is if the developer chooses to bypass the ADF frameworks error handling mechanisms by raising their own exceptions that don't raise a JboException, or error handling is written into JavaScript, this will bypass the ADF custom error handler.

If you're interested in other posts above JDeveloper, check out the JDev pages on wiki.oracle.com.

6 comments:

Clément said...

Hi Chris,

As you said :
"The caveat to this approach is if the developer chooses to bypass the ADF frameworks error handling mechanisms by raising their own exceptions that don't raise a JboException, or error handling is written into JavaScript, this will bypass the ADF custom error handler."

sometimes errors appear in javascript. How to avoid this?

Thanks

Clément

Chris Muir said...

Hi Clement

Can you be clearer please? Do you wish to avoid errors in JavaScript altogether, a specific error, or log the JavaScript errors to the database?

CM.

Clément said...

Hi Chris,
I would like to "catch" the errors that display in javascript.
For example, this is a DMLConstraintException that is shown in javascript :
http://www.cijoint.fr/cj200905/cijn4PvME8.jpg

I would like to catch it, but i don't know how to do. Is it more clear?

Thanks

Clément

Chris Muir said...

Hi Clement

Actually that's a "JBOException" that's raised by the framework and displayed in JavaScript, I can see the JBO error number. As such that should be caught by the error handling mechanism described in the blog post. Give it a go and let us know what results you get.

CM.

Nguyễn Hồng Nhựt said...

hi,
may i use without data binding?
thanks

Chris Muir said...

Replace the example binding code in reportException() from my post with a JDBC call to the database to do whatever you want. The code will be very similar to my logError() method, except you'll need to obtain a connection to the database using an alternative method from getDBTransaction(), which is ADF BC specific.

CM.