Validation, Contradictions, and Exceptions

This chapter explains how to validate configurations and handle contradictions.

This chapter covers the following topics:

Introduction to Validation, Contradictions, and Exceptions

This chapter describes how to handle:

Validating Configurations

Validating a configuration means checking whether it is valid (that is, the selections in it do not violate any configuration rules) and whether it is complete (that is, all components in it are satisfied).

The CIO validates a configuration after a transaction is committed or rolled back. See Using Logic Transactions for a description of what happens in a transaction.

Validation checking and reporting occur when a logical transaction is ended by using Configuration.commitConfigTransaction() or Configuration.rollbackConfigTransaction().

After a committal or rollback, the CIO traverses the nodes of the Model, checking for validation failures, selected items and unsatisfied items. These are kept in a set of collections maintained on the Configuration object.

All validation failures are saved to the CZ_CONFIG_MESSAGES table, which provides information on both the configuration header and the trackable instance header that the failure belongs to. For more information about the CZ_CONFIG_MESSAGES table, see the CZ eTRM on MetaLink, Oracle’s technical support Web site.

After the transaction is committed, you can call the methods of oracle.apps.cz.cio.Configuration listed in the table Methods for Validating Configurations:

Methods for Validating Configurations
Method Description
getValidationFailures() Returns a collection of ValidationFailure objects. Call this after committing or rolling back a transaction, in order to inspect the list of validation failures.
getSelectedItems() Returns a collection of selected items as StatusInfo objects indicating the set of selected (true) items in the Configuration.
isUnsatisfied() Returns TRUE if the configuration is incomplete.
getUnsatisfiedItems() Returns a collection of unsatisfied items as StatusInfo objects indicating the set of unsatisfied items in the Configuration.
getInformationalMessages() Gets a collection of StatusInfo objects describing all the informational messages in the configuration. These messages are created explicitly by external callers or Configurator Extensions or by the CIO in response to an exception thrown by a Configurator Extension.
getUnsatisfiedRuleMessages() Gets a list of messages for unsatisfied relations in the configuration.

To determine whether a configuration has validation failures, call getValidationFailures() and check whether the collection it returns is empty.

Validation failures are instances of the class StatusInfo. A StatusInfo object has a reference to the runtime node, which you obtain with its getNode() method. Use StatusInfo.getStatus() to return the current status of the node.

The status of a node has a life cycle. The stages in the life cycle are represented by the constants described in the table Life Cycle of StatusInfo Objects. As nodes become selected, or unsatisfied, or have validation failures, they have a status reflected by StatusInfo.STATUS_NEW. If they continue to be selected since the last transaction their status is StatusInfo.STATUS_EXISTING. If they become deselected, their status becomes StatusInfo.STATUS_DELETED until the next transaction at which time they are removed from the collection.

Life Cycle of StatusInfo Objects
StatusInfo Constant Status Description
STATUS_NEW The node has newly attained this status since the last check.
STATUS_EXISTING The node already had this status during the last check, and it still does.
STATUS_DELETED The node has newly lost this status since the last check.
STATUS_REMOVED The node had the deleted status during the last check, so it is removed.

If you are writing a Configurator Extension that validates a configuration, the method that you bind to the onConfigValidate event should return a list of CustomValidationFailure objects in the event of a validation failure. This allows you to return more than one failure. Your validation method can include several tests. You can track which tests failed, and determine why the tests failed. If the validation fails, then information about the failure is gathered by the CIO in a List of CustomValidationFailure objects. The information in these objects is presented to the user in a message, and does not persist after the presentation.

In general, if a Configurator Extension needs to return a violation message about a particular runtime node, you have to create a CustomValidationFailure object and pass it the runtime node, the message, and boolean parameter indicating whether to persist the failure. The code fragment in Returning a List of Validation Failures illustrates this point.

Returning a List of Validation Failures

public List validateMin() {
...
IRuntimeNode node;
ArrayList failures = new ArrayList();
...
//check to see if the value in the config is not at least the min value 
if( !
(val >= min) )
    failures.add( new CustomValidationFailure("Value less than minimum", node, true) );
    if(failures.isEmpty())
        return null;
    else 
        return failures;
...
}

If the violation persists after the next user action, the Configurator Extension should not need to create a new CustomValidationFailure, but should instead return a StatusInfo object with the same status (STATUS_EXISTING). This value prevents the CIO from returning the previously seen violation message as a new violation message (STATUS_NEW), which might be annoying for the user. However, if the user explicitly makes the same invalid selection again, then the message is presented again.

You should use the form of the constructor for CustomValidationFailure that sets the boolean parameter willPersist to true. This keeps the failure from disappearing once the message is displayed to the user, which can lead to a situation in which invalid configurations are displayed as valid.

Invalidating a configuration with a Configurator Extension (by creating CustomValidationFailure objects) can sometimes lead to performance issues, since the validation tests are run each time the enclosing transaction is committed. One way to avoid this is to place the validation tests outside the transaction, or bind the validating Configurator Extension to an event other than onConfigValidate.

Another way to alleviates this performance issue is to persist the validation failure, as shown in Returning a List of Validation Failures, because if the boolean parameter willPersist is true, then the validation tests are not run each time the enclosing transaction is committed. However, if you are programmatically marking the configuration as invalid in this way, you must remove the persisted failure when configuration becomes valid again. To remove the persisted failure, you can remove the CustomValidationFailure in the following way:

CustomValidationFailure cvf = findPreviousCustomValidationFailure(node);
cvf.removeCustomValidationFailure();

Note that in this example findPreviousCustomValidationFailure() is a your custom method for finding the failure for a given node. One way of implementing this is by maintaining a Map object in your code in which the keys are nodes and the values are CustomValidationFailure objects. You should clear the map in when your terminates so that Java garbage collection will release the memory.

Handling Logical Contradictions

When you make a logic request to modify the state of a configuration, for instance by using IState.setState(), the result may be a failure of the request because of a logical contradiction. Such a failure creates and throws a logical exception, accessed through either of these objects:

See Overriding Contradictions for details on using LogicalOverridableException to override the contradiction.

Generating Error Messages from Contradictions

The CIO, especially the LogicalException object, uses the Reason object to wrap the information returned by contradictions, in order to include error message information from the table FND_NEW_ MESSAGES. You can use the following methods in your own code:

Using Reasons to Generate Error Messages illustrates one way to generate error messages from Reasons.

Using Reasons to Generate Error Messages

import oracle.apps.cz.cio.Configuration;
import oracle.apps.cz.cio.ConfigTransaction;
import oracle.apps.cz.cio.IRuntimeNode;
import oracle.apps.cz.cio.Option;
import oracle.apps.cz.cio.IOption;
import oracle.apps.cz.cio.LogicalException;
import oracle.apps.cz.cio.NoSuchChildException;

import com.sun.java.util.collections.ArrayList;
import com.sun.java.util.collections.List;


/*
 * Prints reasons for a logical exception, using methods in Reason class.
 */

public class UsingReasonstoGenerateErrorMessages {

    /* 
     * @param config In a CX, bind this parameter to the System Parameter "Configuration"
     */
    public void testMyRule(Configuration config) {
        try {

            ConfigTransaction tr = null;
            IOption myOption = null;
            boolean isException = false;
            List listOfReasons = new ArrayList();

            try {
                tr = config.beginConfigTransaction();

                // Perform an action that might trigger an error
                myOption = (IOption)config.getRootComponent().getChildByName("MyFeature").getChildByName("MyOption");
                myOption.select();

            } catch(NoSuchChildException nsce){
                System.out.println("Child node not found.");
            } catch(LogicalException le){
                // Get information about exception
                isException = true;
                listOfReasons= le.getReasons(); 
                System.out.println("Expected exception " +  le.getExceptionCause() + " : message  "  + le.getMessage());
            }

            if(!isException || listOfReasons.isEmpty()){
                System.out.println("Did not get expected contradiction and/or listReasons is empty.");
            }
            config.rollbackConfigTransaction(tr);

        } catch(LogicalException le){
            System.out.println("The transaction was rolled back.");
            le.printStackTrace();
            // Here, you should log the exception and stack trace to a file
        }
    }
}

Overriding Contradictions

Your runtime Oracle Configurator or Configurator Extension can provide a message to your user, and ask whether the contradiction should be overridden.

If a logical contraction can be overridden, then a LogicalOverridableException is signalled, instead of a LogicalException. LogicalOverridableException is a subclass of LogicalException that adds an override() method. Use LogicalOverridableException.override() to override the contradiction.

Both types of exceptions (LogicalException and LogicalOverridableException) may be thrown from any of the "set" methods (like setState()) or from Configuration.commitConfigTransaction().

If you want to override the overridable exception you have to call its override() method, which can also throw a LogicalException. This means that even when you try to override the exception you still trigger a contradiction and cannot continue. If the override succeeds, then you still need to call commitConfigTransaction() to close the transaction. If you don't want to override or if you get a LogicalException you need to call rollbackConfigTransaction() to purge it. The Handling and Overriding Logical Exceptions is a fragment of pseudocode that illustrates this point. Note that the operations represented with [ASK "text"] and [SHOW "text"] are not part of the CIO but suggest where your own custom application should try to handle the situation.

Handling and Overriding Logical Exceptions

...
ConfigTransaction tr = null;

try {
    try {
        // begin a transaction
        tr = config.beginConfigTransaction();

        // call the "set" method
        opt1.setState(IState.TRUE);
        // commit the transaction
        config.commitConfigTransaction(tr);
    }
    catch(LogicalOverridableException loe) {
        proceed = [ASK "Do you want to override?"];
        if (! proceed) {
            config.rollbackConfigTransaction(tr);
        }
        else {
            try {
                // override the contradiction and ...
                loe.override(); // returns a list of failed requests
               // ... finish the transaction
               config.commitConfigTransaction(tr);
            }
            catch (LogicalException le) {
                // we cannot do anything
                [SHOW "Cannot be overriden"]
                     config.rollbackConfigTransaction(tr);
            }
        }
    }
    catch (LogicalException le) {
        // we cannot do anything
        [SHOW "Cannot be overriden"]
            config.rollbackConfigTransaction(tr);
    }
} catch (LogicalException le) {
    throw new CheckedToUncheckedException(le);
}
...

In Handling and Overriding Logical Exceptions, the statement loe.override(); returns a list of failed requests. See Failed Requests.

Handling Exceptions

This section describes how to handle exceptions raised by the CIO.

Caution: Improper handling of exceptions is the source of many problems that are difficult to diagnose. See Handling Exceptions Properly for more information.

Handling Types of Exceptions

When a Configurator Extension is invoked, the runtime Oracle Configurator wraps a transaction around this invocation. This transaction enables the work of the Configurator Extension to be either committed or rolled back, as necessary. See Using Logic Transactions for background.

If your Configurator Extension needs to handle an exception, you can choose the type of exception to throw. The runtime Oracle Configurator handles the exception as follows:

Raising Fatal Exceptions

If your Configurator Extension code encounters an unexpected problem that you cannot handle, you should convert the exception that you caught into an unchecked exception. For this purpose, use the exception oracle.apps.cz.utilities.CheckedToUncheckedException, which extends RuntimeException.

CheckedToUncheckedException allows you to change a checked exception into an unchecked one, as shown in Raising a Fatal Exception. The new unchecked exception contains the messages and stack traces from both the original checked exception and the new unchecked exception. However, extra properties of specialized checked exceptions that you throw as a CheckedToUncheckedException are not retained in the new unchecked exception.

Raising a Fatal Exception

public void setBoolean (BooleanFeature bf)
{
  try {
    bf.setState(IState.TRUE);
  }
  catch (LogicalException le) {
    throw new CheckedToUncheckedException(le);
  }
}

Presenting Messages for Exceptions

If you want to present messages to the end user without rolling back the transaction, your Configurator Extension should add a new InformationalMessage, by calling Configuration.addInformationalMessage() on the Configuration object for the session, as shown in Presenting an Informational Message. In , the desc parameter could be bound to anything in the Model that returns the string that supplies the text for the message (such as the value of a TextFeature node, a literal, or a certain System Parameters). The node parameter could be bound to the node on which the exception occurs.

Presenting an Informational Message

public void nodeMessage(String desc, IRuntimeNode node) throws LogicalException 
   { 
     try 
     { 
       Configuration config = node.getConfiguration(); 
       ConfigTransaction tr = config.beginConfigTransaction(); 
       InformationalMessage iMsg = new InformationalMessage("The node is: " + desc, node); 
       config.addInformationalMessage(iMsg); 
       config.commitConfigTransaction(tr); 
     }catch (LogicalException le){ 
       throw le; 
     } 
   } 

You can call Configuration.getInformationalMessages() to get a collection of StatusInfo objects that describe all the InformationalMessages in the configuration. For information on the StatusInfo object, see Validating Configurations.

Note: You can only use addInformationalMessage() to present a message from a Configurator Extension to the end user. After the message is dismissed by the user it disappears, without passing any information back to the runtime Oracle Configurator. You cannot use an InformationalMessage object to get a response from the end user in reaction to a message.

Compatibility of Certain Deprecated Exceptions

The exceptions FuncCompMessageException and FuncCompErrorException were introduced in a previous version of the CIO, but are now deprecated, and are retained only for backward compatibility with existing code. Even though these two exceptions extend RuntimeException, they are not fatal in the CIO. They are treated as non-fatal exceptions, as described in Handling Types of Exceptions.

Caution: The classes FuncCompMessageException and FuncCompErrorException are now deprecated, but are retained for backward compatibility with existing code.

A FuncCompErrorException rolls back the open transaction, and allows the end user's configuration session to continue. In general, you should not throw a FuncCompErrorException unless you have very good reasons to believe that the exception is benign and that the user should also be notified of it. You should document these reasons in your code.

A FuncCompMessageException allowed you to present a dialog box displaying a specified message, and the name of the Functional Companion that raised the exception. When the end user dismissed the dialog box, the runtime Oracle Configurator committed the open CIO transaction, and allowed the end user to proceed with the configuration session. It was possible that the Model could be left in an uncertain state. In the current version of the CIO, the transaction is rolled back, instead of committed.