Skip Headers
Oracle® Application Development Framework Developer's Guide
10g Release 3 (10.1.3)
B25386-01
  Go To Documentation Library
Home
Go To Product List
Solution Area
Go To Table Of Contents
Contents
Go To Index
Index

Previous
Previous
Next
Next
 

12.8 Handling and Displaying Exceptions in an ADF Application

Errors thrown by any part of an ADF application are also handled and displayed on the JSF page. By default, all errors thrown in the application are caught by the binding container. When an error is encountered, the binding container routes the error to the application's active error handler. By default, this is the DCErrorHandler. The ReportException(BindingContainer, Exception) 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 error list in a cache.If errors are encountered on the page during the page lifecycle, (for example, during validation), they are also caught by the binding container and cached, and are additionally added to FacesContext.

During the Prepare Render phase, the lifecycle executes the reportErrors(context) method. This method is implemented differently for each view technology. By default, the reportErrors method on the FacesPageLifecycle class:

You can customize this default framework. For example, you can create a custom error handler, or you can change how the lifecycle reports exceptions. You can also customize how a single page handles errors.

12.8.1 How to Change Error Handling

You can change the default error handling by extending the default error handler. Doing so also requires that you create a custom lifecycle class that will call the new error handler during the Prepare Model phase.

You can also create a custom lifecycle class to change how the lifecycle reports errors by overriding the reportErrors method.

If you only want to change how errors are created for a single page, you can create a lifecycle class just for that page.

To create a custom error handler:

  1. Extend the DCErrorHandler class.

    This is the default error handler.

  2. Override the public void reportException(DCBindingContainer, Exception) method. Example 12-11 shows the SRDemoErrorHandler Class that the SRDemo application uses to handle errors.

    Example 12-11 SRDemoErrorHandler Class

    public class SRDemoErrorHandler  extends DCErrorHandlerImpl{
      /**
       * Constructor for custom error handler.
       * 
       * @param setToThrow should exceptions throw or not
       */
      public SRDemoErrorHandler(boolean setToThrow) {
        super(setToThrow);
      }
      public void reportException(DCBindingContainer bc, Exception ex) {
         //Force JboException's reported to the binding layer to avoid
         //printing out the JBO-XXXXX product prefix and code.
        disableAppendCodes(ex);
        super.reportException(bc, ex);
      }
      
      private void disableAppendCodes(Exception ex) {
        if (ex instanceof JboException) {
          JboException jboEx = (JboException) ex;
          jboEx.setAppendCodes(false);
          Object[] detailExceptions = jboEx.getDetails();
          if ((detailExceptions != null) && (detailExceptions.length > 0)) {
            for (int z = 0, numEx = detailExceptions.length; z < numEx; z++) {
              disableAppendCodes((Exception) detailExceptions[z]);
            }
          }
        }
      }
    }
    
    
  3. Globally override the error handler. To do this, you must create a custom page lifecycle class that extends FacesPageLifecycle. In this class, you override the public void prepareModel(LifecycleContext) method, which sets the error handler. To have it set the error handler to the custom handler, have the method check whether or not the custom error handler is the current one in the binding context. If it is not, set it to be. (Because by default the ADFBindingFilter always sets the error handler to be DCErrorHandlerImpl, your method must set it back to the custom error handler.) You must then call super.prepareModel.

    Example 12-12 shows the prepareModel method from the frameworkExt.SRDemoPageLifecycle class that extends the FacesPageLifecycle class. Note that the method checks whether or not the error handler is an instance of the SRDemoErrorHandler, and if it is not, it sets it to the new error handler.

    Example 12-12 preparedModel Method

    public void prepareModel(LifecycleContext ctx) {
        if (!(ctx.getBindingContext().getErrorHandler() instanceof 
              SRDemoErrorHandler)) {
            ctx.getBindingContext().setErrorHandler(new SRDemoErrorHandler(true));
        }
        super.prepareModel(ctx);
    }
    
    
  4. You now must create a new Phase Listener that will return the custom lifecycle. See To create a new phase listener for procedures.

To customize how the lifecycle reports errors:

  1. Create a custom page lifecycle class that extends FacesPageLifecycle.

  2. Override the public void reportErrors(PageLifecycleContext) method to customize the display of error messages.

    Example 12-13 shows the reportErrors method and associated methods in the frameworkExt.SRDemoPageLifecycle class that extends the FacesPageLifecycle class to change how the errors are reported.

    Example 12-13 reportErrors Method in the SRDemoPageLifecycle Class

    public void reportErrors(PageLifecycleContext ctx) {
        DCBindingContainer bc = (DCBindingContainer)ctx.getBindingContainer();
        if (bc != null) {
            List<Exception> exceptions = bc.getExceptionsList();
            if (exceptions != null) {
                Locale userLocale = 
                    ctx.getBindingContext().getLocaleContext().getLocale();
                /*
                 * Iterate over the top-level exceptions in the exceptions list and
                 * call addError() to add each one to the Faces errors list
                 * in an appropriate way.
                 */
                for (Exception exception: exceptions) {
                    try {
                        translateExceptionToFacesErrors(exception, userLocale, 
                                                        bc);
                    } catch (KnowErrorStopException stop) {
                        FacesContext fctx = FacesContext.getCurrentInstance();
                        fctx.addMessage(null, 
                                        JSFUtils.getMessageFromBundle
                                        (stop.getMessage(),
                                         FacesMessage.SEVERITY_FATAL));
                        break;
                    }
                }
            }
        }
    }
     
    protected void translateExceptionToFacesErrors(Exception ex, Locale locale, 
                                                       BindingContainer bc) throws
                                                  KnowErrorStopException {
        List globalErrors = new ArrayList();
        Map attributeErrors = new HashMap();
        processException(ex, globalErrors, attributeErrors, null, locale);
        int numGlob = globalErrors.size();
        int numAttr = attributeErrors.size();
        if (numGlob > 0) {
            for (int z = 0; z < numGlob; z++) {
                String msg = (String)globalErrors.get(z);
                if (msg != null) {
                    JSFUtils.addFacesErrorMessage(msg);
                }
            }
        }
        if (numAttr > 0) {
            Iterator i = attributeErrors.keySet().iterator();
            while (i.hasNext()) {
                String attrNameKey = (String)i.next();
                /*
                 * Only add the error to show to the user if it was related
                 * to a field they can see on the screen. We accomplish this
                 * by checking whether there is a control binding in the current
                 * binding container by the same name as the attribute with
                 * the related exception that was reported.
                 */
                ControlBinding cb = 
                    ADFUtils.findControlBinding(bc, attrNameKey);
                if (cb != null) {
                    String msg = (String)attributeErrors.get(attrNameKey);
                    if (cb instanceof JUCtrlAttrsBinding) {
                        attrNameKey = ((JUCtrlAttrsBinding)cb).getLabel();
                    }
                    JSFUtils.addFacesErrorMessage(attrNameKey, msg);
                }
            }
        }
    }
     
    /**
    * Populate the list of global errors and attribute errors by
    * processing the exception passed in, and recursively processing
    * the detail exceptions wrapped inside of any oracle.jbo.JboException
    * instances.
    *
    * If the error is an attribute-level validation error, we can tell
    * because it should be an instanceof oracle.jbo.AttrValException
    * For each attribute-level error, we retrieve the name of the attribute
    * in error by calling an appropriate getter method on the exception
    * object which exposes this information to us. Since attribute-level
    * errors could be wrapping other more specific attribute-level errors
    * that were the real cause (especially due to Bundled Exception Mode).
    * We continue to recurse the detail exceptions and we only consider
    * relevant to report the exception that is the most deeply nested, since
    * it will have the most specific error message for the user. If multiple
    * exceptions are reported for the same attribute, we simplify the error
    * reporting by only reporting the first one and ignoring the others.
    * An example of this might be that the user has provided a key value
    * that is a duplicate of an existing value, but also since the attribute
    * set failed due to that reason, a subsequent check for mandatory attribute
    * ALSO raised an error about the attribute's still being null.
    *
    * If it's not an attribute-level error, we consider it a global error
    * and report each one.
    *
    * @param ex the exception to be analyzed
    * @param globalErrs list of global errors to populate
    * @param attrErrs map of attrib-level errors to populate, keyed by attr name
    * @param attrName attribute name of wrapping exception (if any)
    * @param locale the user's preferred locale as determined by the browser
    */
    private void processException(Exception ex, List globalErrs, Map attrErrs, 
                                  String attrName, 
                                  Locale locale) throws KnowErrorStopException {
        /*
         * Process the exceptions
         * Start with some special cases that are known bad situations where we
         * need to format some useful advice rather than just parroting the
         * exception text
         */
        if (ex instanceof EJBException) {
            String msg = ex.getLocalizedMessage();
            if (msg == null) {
                msg = firstLineOfStackTrace(ex, true);
            }
            Exception causeEx = ((EJBException)ex).getCausedByException();
            if (causeEx instanceof TopLinkException) {
                int toplinkErrorCode = 
                    ((TopLinkException)causeEx).getErrorCode();
                switch (toplinkErrorCode) {
                case 7060:
                    {
                        throw new KnowErrorStopException("srdemo.topLinkError.7060");
                    }
                case 4002:
                    {
                        throw new KnowErrorStopException("srdemo.topLinkError.4002");
                    }
                }
            }
            globalErrs.add(msg);
        } else if (ex instanceof AdapterException){
            AdapterException causeEx = ((AdapterException)ex);
            
            int err = Integer.parseInt( causeEx.getErrorCode());
            switch (err){
                case 40010:{
                    throw new KnowErrorStopException("srdemo.urlDCError.40010");
                }
                case 29000:{
                    throw new KnowErrorStopException("srdemo.urlDCError.29000");
                }
                default:{
                    throw new KnowErrorStopException("srdemo.urlDCError.other");
                }
            }
            
        } else if (!(ex instanceof JboException)) {
            String msg = ex.getLocalizedMessage();
            if (msg == null) {
                msg = firstLineOfStackTrace(ex, true);
            }
            globalErrs.add(msg);
            /*
           * If this was an unexpected error, print out stack trace
           */
            reportUnexpectedException(ex);
            return;
        }
        if (ex instanceof AttrValException) {
            AttrValException ave = (AttrValException)ex;
            attrName = ave.getAttrName();
            Object obj = attrErrs.get(attrName);
            /*
             * If we haven't already recorded an error for this attribute
             * and if it's a leaf detail, then log it as the first error for
             * this attribute. If there are details, then we'll recurse
             * into the details below. But, in the meantime we've recorded
             * What attribute had the validation error in the attrName
             */
            Object[] details = ave.getDetails();
            if (obj != null) {
                /*
                 * We've already logged an attribute-validation error for this
                 * attribute, so ignore subsequent attribute-level errors
                 * for the same attribute. Note that this is not ignoring
                 * NESTED errors of an attribute-level exception, just the
                 * second and subsequent PEER errors of the first attribute-level
                 * error. This means the user might receive errors on several
                 * different attributes, but for each attribute we're choosing
                 * to tell them about just the first problem to fix.
                 */
                return;
            } else {
                /*
                 * If there aren't any further, nested details, then log first error
                 */
                if ((details == null) || (details.length == 0)) {
                    attrErrs.put(attrName, ave.getLocalizedMessage(locale));
                }
            }
        }
        JboException jboex = (JboException)ex;
        /*
         * It is a JboException so recurse into the exception tree
         */
        Object[] details = jboex.getDetails();
        /*
         * We only want to report Errors for the "leaf" exceptions
         * So if there are details, then don't add an errors to the lists
         */
        if ((details != null) && (details.length > 0)) {
            for (int j = 0, count = details.length; j < count; j++) {
                processException((Exception)details[j], globalErrs, attrErrs, 
                                 attrName, locale);
            }
        } else {
            /*
             * Add a new Error to the collection
             */
            if (attrName == null) {
                String errorCode = jboex.getErrorCode();
                globalErrs.add(jboex.getLocalizedMessage(locale));
            } else {
                attrErrs.put(attrName, jboex.getLocalizedMessage(locale));
            }
            if (!(jboex instanceof ValidationException)) {
                reportUnexpectedException(jboex);
            }
        }
    }
    /**
    * Prints the stack trace for an unexpected exception to standard out.
    *
    * @param ex The unexpected exception to report.
    */
    protected void reportUnexpectedException(Exception ex) {
        ex.printStackTrace();
    }
    /**
    * Picks off the exception name and the first line of information
    * from the stack trace about where the exception occurred and
    * returns that as a single string.
    */
    private String firstLineOfStackTrace(Exception ex, boolean logToError) {
       if (logToError) {
            ex.printStackTrace(System.err);
        }
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        ex.printStackTrace(pw);
        LineNumberReader lnr = 
            new LineNumberReader(new StringReader(sw.toString()));
        try {
            String lineOne = lnr.readLine();
            String lineTwo = lnr.readLine();
            return lineOne + " " + lineTwo;
        } catch (IOException e) {
            return null;
        }
    }
    
    
  3. You now must create a new phase listener that will return the custom lifecycle.

To create a new phase listener

  1. Extend the ADFPhaseListener class.

  2. Override the protected PageLifecycle createPageLifecycle () method to return a new custom lifecycle.

    Example 12-14 shows the createPageLifecycle method in the frameworkExt.SRDemoADFPhaseListener class.

    Example 12-14 createPageLifecycle Method in SRDemoADFPhaseListener

    public class SRDemoADFPhaseListener extends ADFPhaseListener {
      protected PageLifecycle createPageLifecycle() {
        return new SRDemoPageLifecycle();
      }
    }
    
    
  3. Register the phase listener in the faces-config.xml file.

    • Open the faces-config.xml file and select the Overview tab in the editor window. The faces-config.xml file is located in the <View_Project>/WEB-INF directory.

    • In the window, select Life Cycle and click New. Click Help or press F1 for additional help in registering the converter.

To override exception handling for a single page:

  1. Create a custom page lifecycle class that extends the FacesPageLifecycle class.

  2. Override the public void reportErrors(PageLifecycleContext) method to customize the display of error messages. For an example of overriding this method, see To customize how the lifecycle reports errors:.

  3. Open the page definition for the page. In the Structure window, select the page definition node. In the Property Inspector, enter the new class as the value for the ControllerClass attribute.

12.8.2 What Happens When You Change the Default Error Handling

When you create your own error handler, the application uses that class instead of the DCErrorHandler class. Because you created and registered a new lifecycle, that lifecycle is used for the application. This new lifecycle instantiates your custom error handler.

When an error is subsequently encountered, the binding container routes the error to the custom error handler. The reportException(BindingContainer, Exception) method then executes.

If you've overridden the reportErrors method in the custom lifecycle class, then during the Prepare Render phase, the lifecycle executes the new reportErrors(context) method.