Building Configurator Extensions

This chapter describes how to code and build Configurator Extensions, including suggestions for effective development practices and avoiding common mistakes.

This chapter covers the following topics:

Overview of Building Configurator Extensions

To understand the terms and concepts used in this section, see Configurator Extension Basics and the chapter on Configurator Extensions in the Oracle Configurator Developer User’s Guide.

The figure Overview of Configurator Extension Development shows the relationship of a Java development environment to the Oracle Configurator Developer environment when creating Configurator Extensions. In the Java development environment, you compile Java classes and add them to Java archive files. In Oracle Configurator Developer, you upload Java archive files into Configurator Extension Archives.

In your Model, you specify the Archives that form the Model’s Archive Path, which is an ordered list of one or more Configurator Extension Archives. Then you create Configurator Extension Rules, which associate Java classes from Archives with Model nodes. In each Rule, you create bindings, which bind together a configuration event, the parameters of a method in the Java class, and arguments related to the Model.

Overview of Configurator Extension Development

the picture is described in the document text

Java Development Tasks

The following tasks are normally performed by the programmer who is developing the Java code for Configurator Extensions. See Implementing Behavior with Java Classes for more details.

  1. Develop Java classes and archives.

    See Developing Java Classes and Archives.

  2. Create Configurator Extension Archives and upload Java archives.

    See the Oracle Configurator Developer User’s Guide for details on this and the following tasks.

  3. Inspect the classes in an Archive.

  4. Add archives to a Model’s Archive Path.

  5. Optionally, modify the Archive Path for a Model.

Configuration Modeling Tasks

The following tasks are normally performed by the model designer who is developing the configuration model and rules. See Incorporating Behavior into Configuration Models for more details.

  1. Create a Configurator Extension Rule.

    See the Oracle Configurator Developer User’s Guide for details on this and the following tasks.

  2. Choose the Java class for a Rule.

  3. Create event bindings for a Rule.

  4. Bind arguments from the Model to parameters of Java methods.

    If you change the type or number of the parameters of a method used in a Configurator Extension Rule, then you must create a new binding that reflects those changes.

  5. Test Configurator Extensions.

Implementing Behavior with Java Classes

Implement the behavior of your Configurator Extension by creating one or more Java classes and methods that use the Oracle Configuration Interface Object (CIO) to access a runtime configuration object. For details on using the CIO, see Part 2.

You can create your Configurator Extension class in any Java development environment. Then you store the compiled Java class in an archive file, using either the JAR or Zip format for your archive. You complete the coding stage of Configurator Extension development by uploading your archive to the Configurator Developer Repository as a Configurator Extension Archive.

Developing Java Classes and Archives provides the detailed procedure for the coding stage of Configurator Extension development.

For an example, see Example of Configurator Extension Coding.

Incorporating Behavior into Configuration Models

The detailed procedure for the modeling stage of Configurator Extension development is provided in the Oracle Configurator Developer User’s Guide. This section provides a simple overview.

In Oracle Configurator Developer, you create a connection between your Java class and your configuration model. To create this connection, you create a Configurator Extension Rule that binds specific parameters of a Java method to specific nodes or Properties of a Model.

Configurator Extension Binding illustrates the relationship of bindings to Configurator Extension Rules. In this relationship:

Configurator Extension Binding

the picture is described in the document text

For an example of the modeling stage of Configurator Extension development, see Example of Configurator Extension Modeling.

Developing Java Classes and Archives

This section describes the basic process for coding Configurator Extensions.

Configurator Extensions depend on the CIO for access to your configuration model. For more background, see Part 2.

  1. Use a Java development environment or text editor to create a .java file in which to define a Java class. See Sample Java Code for Configurator Extension (InstanceNameChange.java) for an example of a very basic Java class that can be used for a Configurator Extension.

  2. Define your class path to include the package oracle.apps.cz.cio.

    See Installation Requirements for Configurator Extensions.

  3. Import the classes from the CIO that your Configurator Extension requires to do its work. See CIO Basics for background. The following example is typical:

    import oracle.apps.cz.cio.Component;

    If you use a class from the collections library, such as List, then for compatibility with the CIO’s package structure you must import the class using this syntax:

    import com.sun.java.util.collections.List;
  4. Define a class in which to determine the behavior of your Configurator Extension.

    public class InstanceNameChange {
      // implement methods here
    }
  5. Create methods that implement the desired behavior for your Configurator Extension. Any methods that you intend to use in a binding in a Configurator Extension Rule must be declared as public.

    Call methods from the CIO that perform required interaction with your configuration model (see The CIO’s Runtime Node Interfaces).

      public void setDefaultName(Component comp, TextFeature tf) {
        // implement CX behavior here
      }

    Names of methods used for Configurator Extensions cannot be longer than 30 characters.

    The Java types of the parameters of your method must agree with the types of Model entities that are eligible for event binding. For a list of the Java classes that you can use in event bindings, see Java Parameter Types for Configurator Extensions.

  6. Compile the .java file into a .class file.

    Use the correct version of the Sun JDK for your platform. See Installation Requirements for Developing Configurator Extensions.

  7. Put the resulting .class file into a Java archive file.

    You can use either the JAR or Zip format for the Java archive. The archive must be valid. This means that the directory structure of the archive must correspond to the package structure of the Java packages in the archive. For example, the following examples refer to the same class in consistent ways. The first line shows an import statement using a package reference to the class, and the second line shows the directory path to the class as stored in an archive file:

    import oracle.apps.cz.cio.Component; 
    
    oracle/apps/cz/cio/Component.class
  8. Now the Java archive file can be incorporated into a Configurator Extension Archive in Configurator Developer. See Incorporating Behavior into Configuration Models.

Example of Configurator Extension Development

This section provides a basic example of the development of a Configurator Extension, which consists of:

Example of Configurator Extension Coding

Sample Java Code for Configurator Extension (InstanceNameChange.java) shows the Java source code for a very simple Configurator Extension.

See Developing Java Classes and Archives for details on how to create this code and prepare it for use in a configuration model. See Example of Configurator Extension Modeling for how this code is used in a Configurator Extension Rule.

Sample Java Code for Configurator Extension (InstanceNameChange.java)

// When bound to the event for addition of a component instance, 
// takes input from the value of a bound Text Feature 
// and changes the instance name to that corresponding text.
 
import oracle.apps.cz.cio.Component;
import oracle.apps.cz.cio.TextFeature;
 
public class InstanceNameChange {
 
    public void setDefaultName(Component comp, TextFeature tf) {
 
        String name = tf.getCurrentValue();
        comp.setInstanceName(name);
    }
}

Example of Configurator Extension Modeling

See the Oracle Configurator Developer User’s Guide for details on how to incorporate a Configurator Extension in a configuration model and test it. See Example of Configurator Extension Coding for how the behavior of this example is coded in Java.

Incorporating Behavior into Configuration Models provides a summary of the tasks for the modeling stage of Configurator Extension development.

The following list summarizes the options specific to this example:

You can also test Configurator Extensions outside Configurator Developer, by creating an HTML test page that substitutes for your host application. (An example is provided in the Oracle Configurator Installation Guide.)

Suggested Development Practices

This section contains an assortment of suggested practices for developing Configurator Extensions more efficiently and conveniently. These practices include:

Observing Project Requirements

Using Configurator Extensions and the CIO allows you to build very powerful applications with Oracle Configurator. There are important requirements that you should fulfill if you want to maximize your success with Configurator Extensions.

Avoiding Common Errors

Observe the following guidelines to avoid common coding errors:

Observing Thread Safety

CIO interfaces are not thread-safe. A single configuration session should only be accessed by a single thread at a time. Whenever a custom application interacts directly with the CIO, you must ensure that it accesses a configuration session by only a single thread at a time. Multithreading problems can occur, for instance, when end users click multiple times in a child window spawned by a locked parent window. You can prevent multithreading problems by locking your User Interface or synchronizing on your servlet. See Sharing a Configuration Session for an example of when this is a consideration.

Even if you follow this practice, multithreading problems can be caused if the end user closes the child window by clicking on the "X" button (in the upper-right-hand corner of the child window's frame). Doing so unlocks the parent window, but does not terminate the thread that was processing the actions in the child window. When control is returned to the UI in the parent window, a new thread is spawned for further processing (such as computing availability, or performing user requests). Consequently, multiple threads exist for the CIO, a situation that can lead to the JVM crashing.

To protect against the potential multithreading effects of end users prematurely closing child windows, developers should trap the "X" button action in their code. The details for this solution are browser-dependent.

Handling Exceptions Properly

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

Do not ignore or swallow exceptions raised by your code. Ignoring exceptions makes it very difficult to determine the cause of some problems. Handling exceptions properly is sound Java coding practice.

Never leave a catch block empty, as is shown in the example Empty Catch Block. The empty catch block causes your code to silently ignore the exception. The program may then fail at some later point that is quite unrelated to the source of the problem, making it very hard to analyze.

Empty Catch Block

...
    try {
        opt1.setState(IState.TRUE);
    }
    catch (LogicalException le) {
   // an empty catch block ignores exceptions
    }
...

This advice applies to both checked exceptions (such as predictable user errors) and unchecked exceptions (unpredictable program failures). Checked exceptions should always be handled, as shown in Catch Block That Handles an Exception. Leaving a catch block empty is worse than not catching an unchecked exception at all, since an unhandled unchecked exception (with no catch block at all) causes the program to fail and preserves some failure information for debugging.

Catch Block That Handles an Exception

...
    try {
        opt1.setState(IState.TRUE);
    }
    catch (LogicalException le) {
    // the exception is handled
        throw new RuntimeException("Error");
    }
...

Avoiding Circularity and Recursion

Avoid coding that results in circularity or recursion. Scenarios that might cause this are described in:

Example of Circularity

You might unintentionally define Configurator Extensions that call each other in a circular chain.

For example, you might bind the postValueChange event to a method that increments the value of a node, and also to some other method that increments the value of the same node. At runtime, the change to the node made by one method triggers the other method, which changes the node again, and triggers the first method. The resulting endless loop of value changes results in a stack overflow. You can determine whether this occurred by checking the stack trace. When the stack overflow occurs in native code, as it often will, the JVM dies with a segmentation violation. On many platforms an hs_err file is not generated. A core dump file is generated (if you have not set coredumpsize to 0), but using gdb on that file to get a backtrace often will not show Java frames, making this problem very difficult to debug.

This kind of scenario can also occur with the onConfigValidate event, which is dispatched during the validation performed after every CIO transaction.

Example of Recursion

You might unintentionally invoke a method that calls itself recursively in an endless loop.

For example, you might bind the method setIntegerValue() in Inadvertent Recursion (RecursionExample.java) to the postValueChange event. (You would also bind its node parameter to an Integer Feature, and its config parameter to the system parameter Configuration, with an event scope of Base Node.)

Inadvertent Recursion (RecursionExample.java)

import oracle.apps.cz.cio.IInteger;
import oracle.apps.cz.cio.Configuration;
import oracle.apps.cz.cio.ConfigTransaction;
import oracle.apps.cz.cio.LogicalException;
 
public class RecursionExample {
    
    public void setIntegerValue(IInteger node, Configuration config) {
        ConfigTransaction tr = config.beginConfigTransaction();
        try {
            int val = node.getIntValue();
            node.setIntValue(val + 1 ); // no limit to setting values
            config.commitConfigTransaction(tr);
        } catch(LogicalException le) {
            le.getExceptionCause(); // handle the returned node
        }
    }
}

The setIntegerValue() method changes the value of the specified node inside a transaction (which is sound practice). However, every time a transaction is committed, the CIO traverses the list of changes to the configuration (as described in Validating Configurations) and detects the change to the node, and this change triggers the postValueChange event, which calls the setIntegerValue() method again, in a loop.

To avoid this recursion, you must place a limit on the setIntegerValue() method, such as the following:

if (val < 100) { node.setIntValue(val + 1 ); } // limit to setting values

At runtime, this method increments the value of the Integer Feature until it reaches 100, and then stop.

Taking Advantage of Argument Binding

Try to make your code simple and reusable by taking advantage of the power of argument binding.

Sharing Class Instances

All the bindings on a single Configurator Extension Rule share an instance of a class. This means that any member variable can be shared.

You can group bindings based on their intended functionality or based on their class usage, and incur less overhead in the creation of objects.

If your Configurator Extension class uses static member variables to communicate between different instances of the class, the variables cannot be shared across configurations of different models. For example, a Configurator Extension Rule whose base node is in Model M1 will not be able to share static member variables with a Configurator Extension Rule whose base node is in the Model M2 even if both Configurator Extensions are bound to the same Configurator Extension class, MyClass.

Disabling Configurator Extensions

When debugging problems with Oracle Configurator, it is sometimes very helpful to disable some or all of your Configurator Extensions. Disabling Configurator Extensions shows whether the likely source of a problem is in your Configurator Extensions or in the Model that they are associated with. If the problem disappears when you disable Configurator Extensions, then the problem is likely to be in your code. If the problem persists, then the problem is likely to be in your model structure or configuration rules.

Testing for a Null User Interface

If a Configurator Extension might be used with both DHTML UIs (created with a previous release of Oracle Configurator Developer) and generated UIs (created with the HTML-based version of Oracle Configurator Developer), then you should always test for the existence of a DHTML UI. This can also be a way to check which type of UI is in use.

To test for the existence of a DHTML user interface, call Configuration.getUserInterface(), as shown in Testing for a Null User Interface. If the test occurs when the runtime configuration is rendered in a generated UI, then it always returns null.

Testing for a Null User Interface

...
mUi = this.getRuntimeNode().getConfiguration().getUserInterface();
if (mUi != null) { // the UI is DHTML } 
else { // the UI is generated }
...

Using Logging to Examine Problems

When debugging problems with Oracle Configurator, it is very helpful to examine the log file entries created by the CIO during a runtime configuration session. You can insert statements in your code to specify how the entries are written. See Logging Through the CIO for details.

Checking for Deleted or Discontinued Nodes

When working with a runtime node that might have been deleted during a configuration session, always call IRuntimeNode.isDeleted() to test whether that node is actually deleted. Attempting to access or set some attribute of a deleted node generates a NodeDeletedException at runtime. Some methods commonly used to work with nodes are getState(), setState(), and so on. If a configured instance of your Model might contain discontinued nodes, then you should also call IRuntimeNode.isDiscontinued() as a condition of working with a node. A discontinued node is one that exists in an installed configuration of a component (as recorded in Oracle Install Base), but has been removed from the instance of the component being reconfigured, either by deletion or by deselection. If a node has been discontinued by deselection, but not by deletion, then calling a method on it will not raise a NodeDeletedException.

For examples of situations in which you might need to test for deleted or discontinued nodes, see the following sections:

Managing JDBC Connections

Both Configurator Extensions and custom applications use JDBC connections to access the database.

Custom applications must create a database context object before using the CIO, as described in Initializing the CIO. If a custom application needs to access the database after the creation of the Configuration, then they can borrow the context associated with the session's Configuration object, by using Configuration.getContext(). When such applications are finished with the connection, they must release it with CZWebAppsContext.releaseJDBCConnection(). They should never call java.sql.Connection.close() to close the connection, because it does not properly return the connection back to the connection pool. When custom applications are finished with the context object, they must call Context.free(), to prevent connection leaks.

Configurator Extensions do not have to create their own context object and JDBC connection simply to access the configuration model and rules; those connections are created when the runtime Oracle Configurator starts a configuration session. But if Configurator Extensions need to access the database for special queries or invocations, they can borrow the context associated with the session's Configuration object, by using Configuration.getContext(). If they borrow the session context, Configurator Extensions should not call context.releaseJDBCConnection() or java.sql.Connection.close(), because the Web Service already being used by the session will properly free the database resources; calling either of those methods causes a connection leak. However, if a Configurator Extension creates its own context and connection instead of borrowing the session's, then it must follow the practice for releasing connections and contexts that is described here for custom applications.

Accessing More Node and Text IDs

The CZ schema was enhanced in Release 12.1.1 to greatly increase the number of Model nodes and translatable text records that can be created over the life of a database instance. Previously, you could create approximately 2 billion total nodes in the structures of all your Models, and approximately 2 billion translatable text strings. Now, these totals have been increased to approximately 999 trillion.

If you have Configurator Extensions or other custom Java code that uses the CIO, then this schema change requires you to take certain actions. For details, see the sections on upgrade considerations and new public APIs, under "Support for More Node and Text IDs", in the Oracle Configurator Release Notes for Release 12.1.1, on the Oracle Support Web site. A brief description follows: