How to maintain entity validation (JboException) and ORA-20xxx messages in a central resource bundle in your ADF application that uses JHeadstart

0

As you can imagine, with many validators on many entity objects you will end up with equally many generated message bundles. When you start localizing the error messages, that means you have to maintain many classes. Keeping those messages consistent can become an error-prone task. In this article I will first describe how to use one single resource bundle (generated by JHeadstart) so that you only have to localize one resource bundle, and keeping entity validation error messages consistent becomes easy. Then I will show that this resource bundle can also be used to maintain ORA-20xxx (SQLException, user-defined PL/SQL error) messages, and finally that this is also possible for any other custom JBO exception thrown from the BC layer. ....

Entity validation error messages

By default, the built-in validators of ADF BC throw subclasses of JboException, AttrValException (attributelevel validation) and ValidationException (entity-instance-level validation). Depending on whether the exception is an attribute-level or entity-instance-level validator they are automatically called during the set<Attribute> or validateEntity() method respectively. When an error occurs, a message you specified in the entity object editor is displayed to the user, which includes the error message you specified for the validator in the entity object editor, including a reference to the class name of the exception.

Any message you specify when creating a built-in validator or method validator is stored in an entity-specific, automatically generated message (resource) bundle, for example ‘HR.model.entities.EmployeesImplMsgBundle’.

For our current project what we would like to have instead is an errorcode in the validation rule error-message, as a key to a text error-message later retrieved from a centralized resource bundle. This errorcode will be saved in the standard <EOImpl>MsgBundle file. We want our (custom) key format code to be ‘CXS-xxxxx’. Now JboExceptions thrown by entity validation will provide the NLS key as the message text:

 

Resource bundle

A resource bundle is a Java class that contains a two-dimensional array of key-value pairs representing fixed “boilerplate text”, for example prompts, labels, titels but it can also contain error messages, for example:

package mypackage; import java.util.ListResourceBundle; public class MyMessageBundle extends ListResourceBundle { private static final Object[][] sMessageStrings = new String[][] ( {“CXS-00001″, “Telephone number not more than 15 numbers, please!”}, {“CXS-00002″, “Job must be CLERK, MANAGER, PRESIDENT, or SALESMAN”}, {“TABLE_TITLE_ALLEMPLOYEES”, “All Employees”} ); protected Object[][] getContents() { return sMessageStrings; } }

When using the newest JHeadstart version (10.1.3.2) on your project, you can specify whether the resource bundle is generated as a property file, a Java class or a database table. In this article we use a database table, but the other 2 resource bundle types could be used as well. In fact any resource bundle could be used. Using a database table, JHeadstart generates a Java resource bundle as well. This Java class resource bundle acts as a “façade” to our database table.

The Java resource bundle class delegates retrieval of the translatable strings to its JHeadstart superclass, TranslationTableresourceBundle, which uses a ViewObject in the nested JhsModelService to read the translatable text strings from the JHS_TRANSLATIONS table for the given locale.  For each user session all key-value pairs from this table are queried and are available at the ApplicationResources object (the Java façade resource bundle) on the session context. Calling the method protected getContents() on the ApplicationResources object will cause this method to be invoked on its super class, TranslationTableResources, and return the two-dimensional array of key-value pairs.

Button labels, page header titles, database constraints and other fixed text generated by JHeadstart are generated into this resource bundle. Now we want to maintain our error-code and error-text messages from the EO validation as well in this database table.  To do this ErrorReportingUtils, a class generated by JHeadstart, must be extended.

ErrorReportingUtils

Exceptions thrown by any part of an ADF application, so also by EO validation, are by default caught by the binding container. When an exception is encountered, the binding container routes the exception to the application’s active error handler, which by default is the DCErrorHandlerImpl class. The reportException() method on this class passes the exception to the binding container to process. The binding container then processes the exception by placing it on the exception list in a cache. During the Prepare Render phase, the ADF lifecycle executes the reportErrors() method. By default, the reportErrors() method on the FacesPageLifecycle class accesses the exception list from the binding container, calls the addError() method, which creates and adds the messages to the FacesContext and clears the exceptions list in the binding container.

JHeadstart customizes this default framework behavior by having created a custom ADF lifecycle class, JhsPageLifecycle, which extends the default ADF Faces lifecycle and changes how the lifecycle reports errors by overriding the reportErrors() method:

As you can see a custom error handler, ErrorReportingUtils (a managed bean), is created. This class will transform exceptions into Faces error messages. It will do this only for certain types of exceptions, which can be configured in JhsCommon-beans.xml:

ErrorReportingUtils accesses the exception list from the binding container. For all existing exceptions ErrorReportingUtils will process each exception individually and will finally add the exception message(s) to the FacesContext. It will recursively process detail exceptions wrapped inside a JboException (often a JboException has detail exceptions). In our case, the most important methods of ErrorReportingUtils are:

  • reportErrors() // accesses the exception list from the binding container. For each individual exception the method translateExceptionToFacesErrors(exception,..,..) will be called.
  • translateExceptionToFacesErrors() // translates exceptions to the FacesContext. First this method will call processException(exception,..,..,..,..) for this exception to do all the process to get the error message. This method will finally add the exception error message(s) to the FacesContext.
  • processException() // Processes each exception individually. This method looks what kind of exception it is and populates a list of error messages for current exception. For JboExceptions, and in our case the subclasses AttrValException and ValidationException, this method calls getMessage(exception,..) to get the exception error message.
  • getMessage() // returns the complete exception message:  {productCode}-{errorCode}: {messageBody}

A proper entry point in this class is needed to intercept our specific EO validation JboException where the {messageBody} part of the message has our errorcode with our syntax ‘CXS-xxxxx’. Then, we could subclass ErrorReportingUtils and retrieve the corresponding errortext from our resourceBundle.

In our case, the best place for interception is (and to override in a subclass) the getMessage() method. This method calls the method getLocalizedMessage() on the Exception and returns the complete message for the current locale:

method getMessage() in ErrorReportingUtils

To do this we must create and then configure our subclass called HRErrorReportingUtils in JhsCommon-beans.xml (to replace ErrorReportingUtils):

You have to make a JHS template to generate the correct values in JhsCommon-beans.xml. In our case: HR\misc\facesConfig: JhsCommon-beans.vm.

 

 

 

 

 

 

In the getMessage() method of our subclass we have access to the error message of the Exception, we can check whether this error message starts with our {productCode}, ‘CXS-‘. In that case we can create an object instance of a custom subclass of JboException, called HRMyCustomException:

HRMyCustomException mcex = new HRMyCustomException(productAndErrorCode);

All Exceptions thrown from the ADF BC framework should be subclasses of JboException. Translation of a message’s text occurs at the time the exception calls getLocalizedMessage(). JboExceptions have an attribute that stores the {productCode} part of a message.  A subclass of a JboException could be provided with a value for the error code (product- and error code as one String) in a specialized constructor and, importantly, an own resource bundle.

In our custom subclass, we hard-coded our resource bundle (ApplicationResources.class) in the constructor. When the method getLocalizedMessage() is called on a HRMyCustomException object instance, ApplicationResources.class is used as message bundle, and returns the complete message corresponding to the productAndErrorCode key. The constructor has a product- and error code as one String (first 9 characters) as parameter, and calls his super class constructor (JboException) with our hard-coded resource bundle:

public HRMyCustomException(String productAndErrorCode)
{
super(ApplicationResources.class, productAndErrorCode, null);
}

The super class of our resource bundle, TranslationTableResourceBundle, extends the (standard) java.util.ListResourceBundle to format its messages, and supports NLS translation.

 

 

Finally, the last thing you have to do is copy the error text-messages from all the entity-specific message bundles to the JHS_TRANSLATION table, and give the entity object validators in the entity object editor a specific ‘CXS-xxxxx’ errorcode.

Our JHS_TRANSLATIONS database table in Toad.

When our entity validator throws an exception, now our exception error message is derived from the JHS_TRANSLATIONS table.

 

 

 

 

ORA-20xxx error messages

For our current project we also want to use our centralized resource bundle for ORA-20xxx error messages. ORA-20xxx errors are SQLExceptions in the range from ORA-20000 to ORA-20999, which is reserved for user-defined PL/SQL raise_application_errors.

Just like the getmessage() of ErrorReportingUtils method is a great entry point to override and modify JboExceptions (as is shown previously for entity validation exceptions), the method  getOra20xxx(SQLException) in the same class ErrorReportingUtils is a perfect entry point for ORA-20xxx exceptions, because all SQLExceptions are passing trough it.

Method getOra20xxx() in ErrorReportingUtils

This method can be overridden (like we did before with the getMessage() method) in our subclass, HRErrorReportingUtils. The same kind of solution as with entity validation is chosen: when the overridden method getOra20xxx(SQLException) is called in our subclass, we know that a SQLException has arrived, and we can substitute our HRMyCustomException for the SLQException.

 

 

To get a message from our centralized resource bundle we can call our previously created getMessage() method in the same class, HRErrorReportingUtils (explained above in ‘entity validation error messages’, overridden from ErrorReportingUtils):

String sqlMessage = this.getMessage(sqlException,userLocale);

Now the if-condition in getMessage() of HRErrorReportingUtils can be adjusted to also recognize ‘ORA-20xxx’ exceptions by checking the first part of the errorcode (‘ORA-20’):

if (message.startsWith(“CXS-“)    ||      message.startsWith(“ORA-20″) )      {…//as previously …}

Exactly the same as for the entity validation errors is done, a HRMyCustomException object is created, with the product-code (ORA) and specific error-code(20xxxx) as a String parameter. Then the super.getMessage() method is called (at ErrorReportingUtils) that will return the localized message, but now derived from our database resourcebundle.

We can easily test our application whether our ORA-20xxx errors will have error messages from our centralized resource bundle or not. To do this, you can make a simple database procedure:

create procedure testORA20xxx( p in number := 20001 ) is begin raise_application_error( – p, ‘Hallo’ ); end;

In JDeveloper a trigger can be made, for example one an after update trigger for the email attribute of the employees entity object (Connections Navigator, Your database, Triggers, New):

TRIGGER TRIGGERTEST AFTER UPDATE OF EMAIL ON EMPLOYEES BEGIN   testORA20xxx; END;

And finally, the corresponding error-message will be retrieved out of the centralized database resource bundle:

 

 

 

Custom JboException error messages

The simplest method for displaying your own error messages is to throw a JboException:

throw new oracle.jbo.JboException(“<your message>”);

This stops the transaction and displays your error message text. But most likely you don’t want to hard-code your error messages. You can throw this JboException for example from a custom method on your application module (AM) or from another object from your BC layer. The strategy described above can also be applied to these kind of custom JBOExceptions. The JboException will be intercepted by our HRErrorreportingUtils class in the getMessage() method, and we can get the error message out of our centralized resource bundle.

public void testCXEException() throws JboException   {    throw new JboException(“CXS-00003″);   }

But you could also make a custom method on an AM that can be called for example by a backing bean that adds a product- and error code as parameter:

public void throwHRMyCustomExceptionByProductAndErrorCode(String productAndErrorCode) throws JboException   {    throw new JboException(productAndErrorCode);   }

The product- and error code code can be anything as long as it is in the format ‘XXX-xxxxx’. So arbitrary codes like  ‘NGN-00001’ are possible. You only need to add your specific start-part (officially called ‘product’ code) of ‘XXX-xxxxx’ to the if-condition in the getMessage() method in HRErrorReportingUtils. For example ‘NGN-’:

if (message.startsWith(“CXS-“)    ||         message.startsWith(“ORA-20″)  ||         message.startsWith(“NGN-“))            {…//as previously …}

Then you can call your AM custom method from another method on your AM or from a backing bean  and throw a JboException with your specific product- and error code:
(For this example, the custom operation throwHRMyCustomExceptionByProductAndErrorCode must be present in the pageDefinition)

 

 

The corresponding error-message will now be retrieved out of the centralized database resource bundle.

Share.

About Author

Frank Houweling is an Oracle ADF and Java specialist with AMIS (The Netherlands). He focuses mainly on Oracle Fusion ADF, Java Enterprise development and performance management. During the past years he has been requested several times as troubleshooter of ADF projects with bad performance. As such he has been performing performance analysis, bottleneck detection and developing mitigating solutions based on these analysis. He is also the creator of the AMIS ADF Performance Monitor, an advanced monitor that can identify, report and help solve performance bottlenecks in ADF applications.

Comments are closed.