This chapter explains how to validate configurations and handle contradictions.
This chapter covers the following topics:
This chapter describes how to handle:
Validation, which is the act of checking that a configuration is valid and complete
Logical exceptions, which are the representation in the CIO of contradictions, (violations of your configuration rules that are presented to the end user)
Programming exceptions, which are raised by your code
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:
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.
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.
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.
Use LogicalException.isOverridable() to determine whether the exception is an instance of LogicalOverridableException, which can be overridden with its override() method.
Use LogicalException.getExceptionCause() to get the runtime node that caused the failure.
Use LogicalException.getReasons() to get a list of Reason objects for the failure. See Generating Error Messages from Contradictions.
Use LogicalException.getMessage() to provide a message containing both the cause and the reasons.
Use LogicalException.getMessageHeader() to provide a message containing only the causes. You can pass a caption argument to this method, which is the string to use as the node name. Use this caption as an alternative to the node caption provided by the CIO for the message.
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:
Use Reason.translate() to get the message associated with this reason.
Use Reason.getNode() to get the node associated with this reason.
Use Reason.getType() to get the type of reason held in this object.
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 } } }
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.
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.
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:
If your throwable exception is one that extends java.lang.Error or java.lang.RuntimeException, it is fatal. The runtime Oracle Configurator does the following:
Drops any open transactions
Kills the configuration session, but allows the end user to start a new session
Caution: Your code should not ignore or swallow such exceptions; doing so can lead to problems that are difficult to debug.
In the case of a fatal exception, your code should throw an unchecked exception, as shown in Raising Fatal Exceptions.
If your throwable exception does not extend Error or RuntimeException, then it is nonfatal. The runtime Oracle Configurator does the following:
Rolls back the transaction, which undoes the work done by the Configurator Extension
Uses the message for exception to create an InformationalMessage object (described in Presenting Messages for Exceptions)
Allows the user’s configuration session to continue
Allows other Configurator Extensions bound to the same triggering event to run
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.
public void setBoolean (BooleanFeature bf) { try { bf.setState(IState.TRUE); } catch (LogicalException le) { throw new CheckedToUncheckedException(le); } }
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.
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.