Introduction
When writing ADF Mobile applications, you need to handle application errors and exceptions. These errors might be unexpected, for example a failing connection to a remote server, or expected errors like violation of some application business rule. The errors or exceptions might occur in the primary request thread, or in a secondary thread running some background task. When your application supports multiple languages, you want to show the error in the correct language. This article describes how you can handle errors in these different situations,
Main Article
In “big” ADF, you can use (a subclass of) oracle.jbo.JboException to throw an exception. If you throw such an exception in the model layer, the ADF binding layer will gracefully handle the exception and convert it to a JSF message with error severity. In the view/controller layer, you can call reportException(JboException ex) on the DCBindingContainer instance to achieve the same result.
In ADF Mobile a similar concept exists,you can use (a subclass of) oracle.adfmf.framework.exception.AdfException to throw an exception.
throw new AdfException("My error message",AdfException.ERROR);
When you use code like above, then by default this exception will be handled gracefully and you will get a popup in your mobile application showing the message severity and message text.
It doesn’t matter whether you throw the exception in a managed bean or inside a “model” layer class like your data control bean.
However, if you use the same code inside a piece of Java logic that runs in a background thread, the mobile user will NOT see an error popup on the screen.
Runnable runnable = new Runnable() { public void run() { // this exception will be lost, not error popup shown in mobile app throw new AdfException("My (lost) error message in background",AdfException.ERROR); } }; Thread thread = new Thread(runnable); thread.start();
The background thread will stop silently, without notifying the user. Now, this might be the desired behavior, but there might be situations where you do want to notify the user when an error occurs in the background. To achieve this we can invoke a method in the ADF Mobile JavaScript API. Here is the code snippet to do this:
Runnable runnable = new Runnable() { public void run() { AdfmfContainerUtilities.invokeContainerJavaScriptFunction(AdfmfJavaUtilities.getFeatureName(), "adf.mf.api.amx.addMessage", new Object[] {AdfException.ERROR, "My error message in background",null,null }); } }; Thread thread = new Thread(runnable); thread.start();
We use the AdfmfContainerUtilities.invokeContainerJavaScriptFunction to invoke the JavaScript method. More info on this method can be found in the JavaDoc. The adf.mf.api.amx.addMessage JavaScript function that we call is actually the same method that is used under the covers when we throw an AdfException in the primary request thread. So, the user will nicely get the same error popup, regardless of the error message is coming from the main thread or a background thread. The addMessage method takes 4 parameters: the severity, the summary message, the detail message and a clientComponentId. The last two arguments can be left null as you can see in the code snippet. Note that if you supply a detail message, it will be shown on a new line with the same font size as the summary message.
Internationalization
To show an exception error message in the language of the user, ADF Mobile uses standard Java resource bundles. The AdfException contains a constructor where you can pass in a resource bundle name and bundle message key, so the following code should read the actual message from a resource bundle.
private static final String XLF_BUNDLE_NAME = "oracle.ateam.errorhandling.demo.mobile.ViewControllerBundle"; throw new AdfException(AdfException.ERROR, XLF_BUNDLE_NAME, "MY_ERROR_MESSAGE",null);
Unfortunately, there is a bug (16955188) which prevents this code from working. When using this constructor of AdfException, a MissingResourceException will be thrown. The work around is to lookup the resource bundle yourself using the oracle.adfmf.util.BundleFactory class and then use the oracle.adfmf.util.Utility class to get the actual message:
ResourceBundle bundle = BundleFactory.getBundle(XLF_BUNDLE_NAME); String message = Utility.getResourceString(bundle, "MY_ERROR_MESSAGE",null); throw new AdfException(message,AdfException.ERROR);
Or, when running in the background
ResourceBundle bundle = BundleFactory.getBundle(XLF_BUNDLE_NAME); String message = Utility.getResourceString(bundle, "MY_ERROR_MESSAGE_BG",null); AdfmfContainerUtilities.invokeContainerJavaScriptFunction(AdfmfJavaUtilities.getFeatureName(), "adf.mf.api.amx.addMessage", new Object[] {AdfException.ERROR, message,null,null });
Note that directly calling Utility.getResourceString(bundleName, messageKey) without retrieving the resource bundle instance first would not work as you then hit the same bug as in the AdfException constructor.
MessageUtils Convenience Class
To prevent yourself and your fellow developers from remembering each time how to handle an exception depending on the situation, we recommend to use a MessageUtils convenience class similar to the one shown below.
package oracle.ateam.errorhandling.demo.mobile; import java.util.ResourceBundle; import oracle.adfmf.framework.api.AdfmfContainerUtilities; import oracle.adfmf.framework.api.AdfmfJavaUtilities; import oracle.adfmf.framework.exception.AdfException; import oracle.adfmf.util.BundleFactory; import oracle.adfmf.util.Utility; public class MessageUtils { public static void handleError(Throwable throwable, String severity) { if (AdfmfJavaUtilities.isBackgroundThread()) { addJavaScriptMessage(severity, throwable.getLocalizedMessage()); } else { throw new AdfException(throwable, severity); } } public static void handleError(Throwable throwable) { if (AdfmfJavaUtilities.isBackgroundThread()) { addJavaScriptMessage(AdfException.ERROR, throwable.getLocalizedMessage()); } else { throw new AdfException(throwable); } } public static void handleError(String message, String severity) { if (AdfmfJavaUtilities.isBackgroundThread()) { addJavaScriptMessage(severity, message); } else { throw new AdfException(message, severity); } } public static void handleError(String severity, String bundleName, String messageKey, Object[] params) { ResourceBundle bundle = BundleFactory.getBundle(bundleName); String message = Utility.getResourceString(bundle, messageKey, params); if (AdfmfJavaUtilities.isBackgroundThread()) { addJavaScriptMessage(severity, message); } else { throw new AdfException(message, severity); } } public static void addJavaScriptMessage(String severity, String message) { AdfmfContainerUtilities.invokeContainerJavaScriptFunction(AdfmfJavaUtilities.getFeatureName(), "adf.mf.api.amx.addMessage", new Object[] { severity, message, null,null }); } }
As you can see, the work around to obtain the resource bundle is now hidden in this class, and we check dynamically whether we are running in the background.By using a class like this, you no longer have to bother on which thread your code is running, and you can freely move code from the main thread to the background thread.
A sample error handler application (JDeveloper 11.1.2.4) that illustrates the above techniques and includes this utility class can be downloaded here.