Fusion Middleware Documentation
Advanced Search


Developing Fusion Web Applications with Oracle Application Development Framework
Close Window

Table of Contents

Show All | Collapse

16 Extending Business Components Functionality

This chapter describes techniques techniques that you can use to incorporate custom code with all types of ADF Business Components and to extend the ADF Business Components framework behavior.

This chapter includes the following sections:

16.1 About Extending Business Components Functionality

One of the powerful features of framework-based development is the ability to extend the base framework to change a built-in feature to behave differently or to add a new feature that can be used by all of your applications.

The base classes of the ADF Business Components framework may be extended to incorporate custom code with all types of components in the framework and to extend the ADF Business Components framework behavior.

When used without customization, your business component is completely defined by its XML document and it will be fully functional without custom Java code or even a Java class file for the component. If you have no need to extend the built-in functionality of a component in ADF Business Components, and no need to write any custom code to handle its built-in events, you can use the component in this XML-only fashion. However, when you do extend base classes of the ADF Business Components framework, you can still work with XML documents in JDeveloper.

Once you have created framework extension classes, any new business component you create can be based on your customized framework class instead of the base one. And, you can always update the definitions of existing components to use the new framework extension class as well.

16.1.1 Additional Functionality for Extending Business Components

You may find it helpful to understand other Oracle ADF features before you start working with the ADF Business Components framework. Following are links to other functionality that may be of interest.

16.2 Creating ADF Business Components Extension Classes

An ADF Business Components framework extension class is a Java class you write that extends one of the framework's base classes to:

  • Augment a built-in feature with additional, generic functionality

  • Change how a built-in feature works, or even to

  • Workaround a bug you encounter in a generic way

Before you begin to develop application-specific business components, Oracle recommends that you consider creating a complete layer of framework extension classes and set up your project-level preferences to use that layer by default. You might not have any custom code in mind to put in these framework extension classes initially, but this practice will help when customization becomes practical.

This way, substantial inconvenience can be avoided if you discover mid-project that all of your entity objects, for example, require a new generic feature, augmented built-in feature, or a generic bug workaround.

Note:

To experiment with the examples in this chapter, use the SummitADF_Examples workspace, as described in Section 2.4, "Running the Standalone Samples from the SummitADF_Examples Workspace." For information about how to obtain and install the Summit ADF standalone sample applications, see Section 2.2, "Setting Up the Summit Sample Applications for Oracle ADF."

16.2.1 How To Create a Framework Extension Class

When you need to add custom code to extend the base functionality of the ADF Business Components framework, you can enable a custom Java class for any of the key types of business components you create. You enable the generation of custom classes for a component on the Java page of its respective overview editor in JDeveloper. When you enable this option, JDeveloper creates a Java source file for a custom class related to the component whose name follows a configurable naming standard. This class, whose name is recorded in the component's XML document, provides a place where you can write the custom Java code required by that component.

To create a framework extension class:

  1. Identify a project to contain the framework extension class.

    You can create it in the same project as your business service components if you believe it will only be used by components in that project. Alternatively, if you believe you might like to reuse the framework extension class across multiple Fusion web applications, create a separate model project to contain the framework extension classes.

  2. Ensure that the BC4J Runtime library is in the project's libraries list.

    Use the Libraries and Classpath page of the Project Properties dialog to verify this and to add the library if missing.

  3. In the Applications window, right-click the project in which you want to create the extension class and choose New and then Java Class.

  4. In the Create Java Class dialog, specify the appropriate framework base class from the oracle.jbo.server package in the Extends field.

    Figure 16-1 illustrates what it would look like to create a custom framework extension class named CustomAppModuleImpl in the com.yourcompany.fwkext package to customize the functionality of the base application module component. To quickly find the base class you're looking for, use the Browse button next to the Extends field that launches the JDeveloper Class Browser. Using its Search tab, you can type in part of the class name (including using * as a wildcard) to quickly subset the list of classes to find the one you're looking for.

    Figure 16-1 Creating a Framework Extension Class for an Application Module

    Create Java Class dialog

When you click OK, JDeveloper creates the custom framework extension class for you in the directory of the project's source path corresponding to the package name you've chosen.

Note:

Some ADF Business Component classes exist in both a server-side and a remote-client version. For example, if you use the JDeveloper Class Browser and type ApplicationModuleImpl into the Match Class Name field on the Search tab, the list will show two ApplicationModuleImpl classes: one in the oracle.jbo.server package and the other in the oracle.jbo.client.remote package. When creating framework extension classes, use the base ADF Business Components classes in the oracle.jbo.server package.

16.2.2 What Happens When You Create a Framework Extension Class

After creating a new framework extension class, it will not automatically be used by your application. You must decide which components in your project should make use of it. The following sections describe the available approaches for basing your business components on your own framework extension classes.

16.2.3 What You May Need to Know About Customizing Framework Extension Bases Classes

To make your framework extension layer classes easier to package as a reusable library, create them in a separate project from the projects that use them.

A common set of customized framework base classes in a package name of your own choosing like com.yourcompany.fwkext, each importing the oracle.jbo.server.* package, would consist of the following classes:

  • public class CustomEntityImpl extends EntityImpl

  • public class CustomEntityDefImpl extends EntityDefImpl

  • public class CustomViewObjectImpl extends ViewObjectImpl

  • public class CustomViewRowImpl extends ViewRowImpl

  • public class CustomApplicationModuleImpl extends ApplicationModuleImpl

  • public class CustomDBTransactionImpl extends DBTransactionImpl2

  • public class CustomDatabaseTransactionFactoryImpl extends DatabaseTransactionFactory

For details about using the custom DBTransactionImpl2 and DatabaseTransactionFactory classes, see Section 16.7.5.2, "Configuring an Application Module to Use a Custom Database Transaction Class."

For completeness, you may also want to create customized framework classes for the following classes as well, note however that overriding anything in these classes would be a fairly rare requirement.

  • public class CustomViewDefImpl extends ViewDefImpl

  • public class CustomEntityCache extends EntityCache

  • public class CustomApplicationModuleDefImpl extends ApplicationModuleDefImpl

16.2.4 How to Base a Business Component on a Framework Extension Class

You can set the base classes for any business component using the Java page of any ADF Business Components wizard or editor.

Before you begin:

  • Create the framework extension class, as described in Section 16.2.1, "How To Create a Framework Extension Class."

  • If you created your framework extension classes in a separate project, visit the Dependencies page of the Project Properties dialog for the project containing your business components and select Build Output to add the framework extension project as a project dependency.

  • If you have packaged your framework extension classes in a Java archive (JAR) file, create a named library definition to reference its JAR file and also list that library in the library list of the project containing your business components. To create a library if missing, use the Manage Libraries dialog available from the Tools > Manage Libraries main menu item. To verify or adjust the project's library list, use the Libraries page of the Project Properties dialog.

After you ensure the framework classes are available to reference, you can create the business component. Every ADF Business Components wizard and editor displays the same Class Extends button on the Java page so you can use the technique to choose your desired framework extension base class(es) both for new components or existing ones.

There is no fixed limit on how many levels of framework extension classes you create. For example, after creating a company-level CustomAppModuleImpl to use for all application modules in all Fusion web applications that your company creates, some later project team might encounter the need to further customize that framework extension class. That team could create a SomeProjectCustomAppModuleImpl class that extends the CustomAppModuleImpl and then include the project-specific custom application module code in there as shown in Example 16-1.

Example 16-1 Extending a Custom Class

public class SomeProjectCustomAppModuleImpl
       extends CustomAppModuleImpl {
  /*
   * Custom application module code specific to the
   * "SomeProject" project goes here.
   */
}

Then, any application modules created as part of the implementation of this specific project can use the SomeProjectCustomAppModuleImpl as their base class instead of the CustomAppModuleImpl.

To create a business component based on a framework extension class:

  1. In the Applications window, double-click the desired component.

  2. In the overview editor, click the Java navigation tab and then click the Edit Java options button.

  3. In the Select Java Options dialog, click Classes Extends.

  4. In the Override Base Classes dialog, enter the fully qualified name of the framework base classes you wish to override. You can also use the Browse button to use the JDeveloper Class Browser to find the classes quickly.

    When you use the Class Browser to select a custom base class for the component, the list of available classes is automatically filtered to show only classes that are appropriate. For example, when clicking Browse in Figure 16-2 to select an application module Object base class, the list will only show classes available in the current project's library list which extend the oracle.jbo.server.ApplicationModule class either directly or indirectly. If you don't see the class you're looking for, either you extended the incorrect base class or you have chosen the wrong component class name to override.

    Figure 16-2 Specifying a Custom Base Class for a New Application Module

    Override Base Class dialog with custom base class

16.2.5 How to Define Framework Extension Classes for All New Components

If you decide to use a specific set of framework extension classes as a standard for a given project, you can use the Project Properties dialog to define your preferred base classes for each component type. Setting these preferences for base classes does not affect any existing components in the project, but the component wizards will use the preferences for any new components created.

To define project-level preferences for framework extension classes:

  1. In the Applications window, right-click the project that will contain the extension classes and choose Project Properties.

  2. In the Project Properties dialog, expand ADF Business Components > Base Classes and enter the fully qualified name of the base class in the Application Module Object class name field.

    For example, to indicate that any new application modules created in the project should use the CustomAppModuleImpl class by default, enter the fully qualified name of that class in the componentName Object class name field as shown in Figure 16-3.

    Figure 16-3 Setting Project-Level Preferences for Business Component Base Classes

    Business Components Base Classes dialog

16.2.6 How to Define Framework Extension Classes for All New Projects

When you want to apply the same base class preferences to each new project that you create in JDeveloper, you can define the preferences at a global level using the Preferences dialog. Base classes that you specify at the global level will not alter your existing projects containing business components.

To define global preferences for framework extension classes:

  1. In the main menu, choose Tools and then Preferences.

  2. In the Preferences dialog, expand ADF Business Components > Base Classes in the tree.

  3. On the Business Components page, enter the fully qualified name of that class in componentName Object class name field.

    The page displays the same options for specifying the preferred base classes for each component type as shown in Figure 16-3.

16.2.7 What Happens When You Base a Component on a Framework Extension Class

When a business component you create extends a custom ADF Business Components framework extension class, JDeveloper updates its XML document to reflect the custom class name you've chosen.

16.2.7.1 XML-Only Components

For example, assume you've created the YourService application module in the com.yourcompany.yourapp package, with a custom application module base class of CustomAppModuleImpl. If you have opted to leave the component as an XML-only component with no custom Java file, its XML document (YourService.xml) will look like what you see in Example 16-2. The value of the ComponentClass attribute of the AppModule tag is read at runtime to identify the Java class to use to represent the component.

Example 16-2 Custom Base Class Names Are Recorded in XML Document

<AppModule
   Name="YourService"
   ComponentClass="com.yourcompany.fwkext.CustomAppModuleImpl" >
  <!-- etc. -->
</AppModule>

Figure 16-4 illustrates how the XML-only YourService application module relates to your custom extension class. At runtime, it uses the CustomAppModuleImpl class which inherits its base behavior from the ApplicationModuleImpl class.

Figure 16-4 XML-Only Component Reference an Extended Framework Base Class

Flow of extended framework base class

16.2.7.2 Components with Custom Java Classes

If your component requires a custom Java class, as you've seen in previous chapters you open the Java page of the component editor and check the appropriate checkbox to enable it. For example, when you enable a custom application module class for the YourServer application module, JDeveloper creates the appropriate YourServiceImpl.java class. As shown in Example 16-3, it also updates the component's XML document to reflect the name of the custom component class.

Example 16-3 Custom Component Class Recorded in XML Document

<AppModule
   Name="YourService"
   ComponentClass="com.yourcompany.yourapp.YourServiceImpl" >
  <!-- etc. -->
</AppModule>

JDeveloper also updates the component's custom Java class to modify its extends clause to reflect the new custom framework base class, as shown in Example 16-4.

Example 16-4 Component's Custom Java Class Updates to Reflect New Base Class

package com.yourcompany.yourapp;
import com.yourcompany.fwkext.CustomAppModuleImpl;
// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---    Custom code may be added to this class.
// ---    Warning: Do not modify method signatures of generated methods.
// ---------------------------------------------------------------------
public class YourServiceImpl extends CustomAppModuleImpl {
  /**This is the default constructor (do not remove)   */
  public YourServiceImpl() {}
  // etc.
}

Figure 16-5 illustrates how the YourService application module with its custom YourServiceImpl class is related to your framework extension class. At runtime, it uses the YourServiceImpl class which inherits its base behavior from the CustomAppModuleImpl framework extension class which, in turn, extends the base ApplicationModuleImpl class.

Figure 16-5 Component with Custom Java Extending Customized Framework Base Class

Flow of extended customized framework class

16.2.8 What You May Need to Know About Updating the Extends Clause in Custom Component Java Files

If you have a business component with a custom Java class and later decide to base the component on an ADF Business Components framework extension class, use the Class Extends button in the Select Java Options dialog to change the component's base class. You can open the dialog from the Java page of the component's overview editor. Doing this updates the component's XML document to reflect the new base class, and also modifies the extends clause in the component's custom Java class.

Caution:

If you manually update the extends clause without using the component editor, the component's XML document will not reflect the new inheritance and the next time you open the editor, your manually modified extends clause will be overwritten with what the component editor believes is the correct component base class.

16.2.9 How to Package Your Framework Extension Layer in a JAR File

Use the Create Deployment Profile: JAR File dialog to create a JAR file containing the classes in your framework extension layer. This is available in the New Gallery in the General > Deployment Profiles category.

Give the deployment profile a name like FrameworkExtensions and click OK. By default the JAR file will include all class files in the project. Since this is exactly what you want, when the JAR Deployment Profile Properties dialog appears, you can just click OK to finish.

Note:

Do not use the ADF Library JAR archive type to package your framework extension layer. You create the ADF Library JAR file when you want to package reusable components to share in the JDeveloper Resource Catalog. For details about working with business components and the ADF Library JAR archive type, see Section 44.2, "Packaging a Reusable ADF Component into an ADF Library."

Finally, to create the JAR file, right-click the project node in the Applications window and choose Deploy - YourProfileName - to JAR File on the context menu. A Deployment tab appears in the JDeveloper Log window that should display feedback like:

----  Deployment started.  ----    Feb 14, 2013 1:42:39 PM
Running dependency analysis...
Wrote JAR file to ...\FrameworkExtensions\deploy\FrameworkExtensions.jar
Elapsed time for deployment:  2 seconds
----  Deployment finished.  ----    Reb 14, 2013 1:42:41 PM

16.2.10 How to Create a Library Definition for Your Framework Extension JAR File

JDeveloper uses named libraries as a convenient way to organize the one or more JAR files that comprise reusable component libraries.

To define a library for your framework extensions JAR file:

  1. In the main menu, choose Tools and then Manage Libraries.

  2. In the Manage Libraries dialog, select the Libraries tab and then select User and click New.

  3. In the Create Library dialog that appears, name the library "Framework Extension Layer" and select the Class Path node and click Add Entry.

  4. Use the Select Path Entry dialog to select the JAR file that contains the class files for the framework extension components, then click Select.

  5. Select the Source Path node and click Add Entry.

  6. Use the Select Path Entry dialog that appears to select the directory where the source files for the framework extension classes reside, then click Select.

    For example, select ..\FrameworkExtensions\src for the JAR file FrameworkExtensions.jar.

  7. Click OK.

When finished, you will see your new "Framework Extension Layer" user-defined library, as shown in Figure 16-6. You can then add this library to the library list of any project where you will be building business services, and your custom framework extension classes will be available to reference as the preferred component base classes.

Figure 16-6 New User-Defined Library for Your Framework Extensions Layer

Manage Libraries dialog

16.3 Customizing Framework Behavior with Extension Classes

One of the common tasks you'll perform in your framework extension classes is implementing custom application functionality. Since framework extension code is written to be used by all components of a specific type, the code you write in these classes often needs to work with component attributes in a generic way. To address this need, ADF Business Components provides API's that allow you to access component metadata at runtime. It also provides the ability to associate custom metadata properties with any component or attribute. You can write your generic framework extension code to leverage runtime metadata and custom properties to build generic functionality, which if necessary, only is used in the presence of certain custom properties.

16.3.1 How to Access Runtime Metadata For View Objects and Entity Objects

Figure 16-7 illustrates the three primary interfaces ADF Business Components provides for accessing runtime metadata about view objects and entity objects. The ViewObject interface extends the StructureDef interface. The class representing the entity definition (EntityDefImpl) also implements this interface. As its name implies, the StructureDef defines the structure and the component and provides access to a collection of AttributeDef objects that offer runtime metadata about each attribute in the view object row or entity row. Using an AttributeDef, you can access its companion AttributeHints object to reference hints like the display label, format mask, tooltip, etc.

Figure 16-7 Runtime Metadata Available for View Objects and Entity Objects

Flow of available metadata for objects

16.3.2 How to Implement Generic Functionality Using Runtime Metadata

In Section 8.4.1, "ViewObject Interface Methods for Working with the View Object's Default RowSet" you learned that for read-only view objects the findByKey() method and the setCurrentRowWithKey builtin operation only work if you override the create() method on the view object to call setManageRowsByKey(true). This can be a tedious detail to remember if you create a lot of read-only view objects, so it is a great candidate for automating in a framework extension class for view objects.

Assume a FrameworkExtensions project contains a SummitViewObjectImpl class that is the base class for all view objects in the application. This framework extension class for view objects extends the base ViewObjectImpl class and overrides the create() method as shown in Example 16-5 to automate this task. After calling the super.create() to perform the default framework functionality when a view object instance is created at runtime, the code tests whether the view object is a read-only view object with at least one attribute marked as a key attribute. If this is the case, it invokes setManageRowsByKey(true).

The isReadOnlyNonEntitySQLViewWithAtLeastOneKeyAttribute() helper method determines whether the view object is read-only by testing the combination of the following conditions:

  • isFullSql() is true

    This method returns true if the view object's SQL query is completely specified by the developer, as opposed to having the select list derived automatically based on the participating entity usages.

  • getEntityDefs() is null

    This method returns an array of EntityDefImpl objects representing the view object's entity usages. If it returns null, then the view object has no entity usages.

It goes on to determine whether the view object has any key attributes by looping over the AttributeDef array returned by the getAttributeDefs() method. If the isPrimaryKey() method returns true for any attribute definition in the list, then you know the view object has a key.

Example 16-5 Automating Setting Manage Rows By Key

public class SummitViewObjectImpl extends ViewObjectImpl {
  protected void create() {
    super.create();
    if (isReadOnlyNonEntitySQLViewWithAtLeastOneKeyAttribute()) {
      setManageRowsByKey(true);
    }
  }
  boolean isReadOnlyNonEntitySQLViewWithAtLeastOneKeyAttribute() {
    if (getViewDef().isFullSql() && getEntityDefs() == null) {
      for (AttributeDef attrDef : getAttributeDefs()) {
        if (attrDef.isPrimaryKey()) {
          return true;
        }
      }
    }
    return false;
  }
  // etc.
}

16.3.3 How to Implement Generic Functionality Driven by Custom Properties

When you create application modules, view objects, and entity objects you can select the General navigation tab in the overview editor for these business components and expand the Custom Properties section to define custom metadata properties for any component. These are name/value pairs that you can use to communicate additional declarative information about the component to the generic code that you write in framework extension classes. You can use the getProperty() method in your code to conditionalize generic functionality based on the presence of, or the specific value of, one of these custom metadata properties.

For example, the SummitViewObjectImpl framework extension class overrides the view object's insertRow() method as shown in Example 16-6 to conditionally force a row to be inserted and to appear as the last row in the row set. If any view object extending this framework extension class defines a custom metadata property named InsertNewRowsAtEnd, then this generic code executes to insert new rows at the end. If a view object does not define this property, it will have the default insertRow() behavior.

Example 16-6 Conditionally Inserting New Rows at the End of a View Object's Default RowSet

public class SummitViewObjectImpl extends ViewObjectImpl {
  private static final String INSERT_NEW_ROWS_AT_END = "InsertNewRowsAtEnd";
  public void insertRow(Row row) {
    super.insertRow(row);
    if (getProperty(INSERT_NEW_ROWS_AT_END) != null) {
      row.removeAndRetain();
      last();
      next();
      getDefaultRowSet().insertRow(row);    
    }
  }
  // etc.
}

In addition to defining component-level custom properties, you can also define properties on view object attributes, entity object attributes, and domains. At runtime, you access them using the getProperty() method on the AttributeDef interface for a given attribute.

16.3.4 What You May Need to Know About the Kinds of Attributes

In addition to providing information about an attribute's name, Java type, SQL type, and many other useful pieces of information, the AttributeDef interface contains the getAttributeKind() method that you can use to determine the kind of attribute it represents. This method returns a byte value corresponding to one of the public constants in the AttributeDef interface listed in Table 16-1.

Table 16-1 Entity Object and View Object Attribute Kinds

Public AttributeDef Constant Attribute Kind Description

ATTR_PERSISTENT

Persistent attribute

ATTR_TRANSIENT

Transient attribute

ATTR_ENTITY_DERIVED

View object attribute mapped to an entity-level transient attribute

ATTR_SQL_DERIVED

SQL-Calculated attribute

ATTR_DYNAMIC

Dynamic attribute

ATTR_ASSOCIATED_ROWITERATOR

Accessor attribute returning a RowSet of set of zero or more Rows

ATTR_ASSOCIATED_ROW

Accessor attribute returning a single Row


16.3.5 What You May Need to Know About Custom Properties

You may find it handy to programmatically set custom property values at runtime. While the setProperty() API to perform this function is by design not available to clients on the ViewObject, ApplicationModule, or AttributeDef interfaces in the oracle.jbo package, code that you write inside custom Java classes of your business components can use it.

16.4 Creating Generic Extension Interfaces

In addition to creating framework extension classes, you can create custom interfaces that all of your components can implement by default. The client interface is very useful for exposing methods from your application module that might be invoked by UI clients, for example. This section considers an example for an application module, however, the same functionality is possible for a custom extended view object and view row interface as well. For more information about client interfaces, see also Section 13.9, "Publishing Custom Service Methods to UI Clients" and Section 13.10, "Working Programmatically with an Application Module's Client Interface."

Assume that you have a CustomApplicationModuleImpl class that extends ApplicationModuleImpl and that you want to expose two custom methods like this:

public void doFeatureOne(String arg);
public int anotherFeature(String arg);

Perform the following steps to create a custom extension interface CustomApplicationModule and have your CustomApplicationModuleImpl class implement it.

  1. Create a custom interface that contains the methods you would like to expose globally on your application module components. For this scenario, that interface would look like this:

    package devguide.advanced.customintf.fwkext;
    /**
     * NOTE: This does not extend the
     * ====  oracle.jbo.ApplicationModule interface.
     */
    public interface CustomApplicationModule  {
      public void doFeatureOne(String arg);
      public int anotherFeature(String arg);
    }
    

    Notice that the interface does not extend the oracle.jbo.ApplicationModule interface.

  2. Modify your CustomApplicationModuleImpl application module framework extension class to implement this new CustomApplicationModule interface.

    package devguide.advanced.customintf.fwkext;
    import oracle.jbo.server.ApplicationModuleImpl;
    public class CustomApplicationModuleImpl
           extends ApplicationModuleImpl 
           implements CustomApplicationModule {
      public void doFeatureOne(String arg) {
        System.out.println(arg); 
      }
      public int anotherFeature(String arg) {
        return arg == null ? 0 : arg.length();
      }
    }
    
  3. Rebuild your project.

    The ADF Business Components overview editors will only "see" your interfaces after they have been successfully compiled.

After you have implemented your CustomApplicationModuleImpl class, you can create a new application module which exposes the global extension interface and is based on your custom framework extension class. For this purpose you use the overview editor for application modules.

To create a custom application module interface:

  1. In the Applications window, double-click the application module for which you want to create the custom interface.

    For example, you might create a new ProductModule application module which exposes the global extension interface CustomApplicationModule and is based on the CustomApplicationModuleImpl framework extension class.

  2. In the overview editor, select the Java navigation tab and then click the Edit Java options icon.

    The Java Classes page should show an existing Java class for the application module identified as Application Module Class.

    By default, JDeveloper generates the Java class for application modules you create. However, if you disabled this feature, click the Edit Java options button in the Java Classes section and select Generate Application Module Class. Click OK to add a Java class to the project from which you will create the custom interface.

  3. In the Select Java Options dialog, click Class Extends.

  4. In the Override Base Classes dialog, specify the name of the framework base class you want to override and click OK.

    For example, you might select CustomApplicationModuleImpl as the base class for the application module.

  5. In the Java Classes page of the overview editor, expand the Client Interface section and click the Edit application module client interface button.

  6. In the Edit Client Interface dialog, click the Interfaces button.

  7. In the Select Interfaces to Extend dialog, select the desired custom application module interface from the available list and click OK.

    For example, you might shuttle the CustomApplicationModule interface to the Selected list to be one of the custom interfaces that clients can use with your component.

  8. In the Edit Client Interfaces dialog, ensure that at least one method appears in the Selected list.

    Note:

    You need to select at least one method in the Selected list in the Edit Client Interfaces dialog, even if it means redundantly selecting one of the methods on the global extension interface. Any method will do in order to get JDeveloper to generate the custom interface.

  9. Click OK.

    The Java Classes page displays the new custom interface for the application module identified as Application Module Client Interface.

When you dismiss the Edit Client Interfaces dialog and return to the application module overview editor, JDeveloper generates the application module custom interface. For example, the custom interface ProductModule automatically extends both the base ApplicationModule interface and your CustomApplicationModule extension interface like this:

package devguide.advanced.customintf.common;
import devguide.advanced.customintf.fwkext.CustomApplicationModule;

import oracle.jbo.ApplicationModule;
// ---------------------------------------------------------------------
// ---    File generated by ADF Business Components Design Time.
// ---------------------------------------------------------------------
public interface ProductModule
       extends CustomApplicationModule, ApplicationModule {
  void doSomethingProductRelated();
}

Once you've done this, then client code can cast your ProductModule application module to a CustomApplicationModule interface and invoke the generic extension methods it contains in a strongly typed way.

Note:

The basic steps are the same for exposing methods on a ViewObjectImpl framework extension class, as well as for a ViewRowImpl extension class.

16.5 Invoking Stored Procedures and Functions

You can write code in the custom Java classes for your business components to invoke database stored procedures and functions. Here you'll consider some simple examples based on procedures and functions in a PL/SQL package; however, using the same techniques, you also can invoke procedures and functions that are not part of a package.

Consider the PL/SQL package shown in Example 16-7.

Example 16-7 PL/SQL Package with Example Procedures

create or replace package invokestoredprocpkg as
  procedure proc_with_no_args;
  procedure proc_with_three_args(n number, d date, v varchar2);
  function  func_with_three_args(n number, d date, v varchar2) return varchar2;
  procedure proc_with_out_args(n number, d out date, v in out varchar2);
end invokestoredprocpkg;

The following sections explain how to invoke each of the example procedures and functions in this package.

Note:

The example in this section refers to the oracle.summit.model.invokingstoredprocedure package in the SummitADF_Examples application workspace.

16.5.1 How to Invoke Stored Procedures with No Arguments

If you need to invoke a stored procedure that takes no arguments, you can use the executeCommand() method on the DBTransaction interface (in the oracle.jbo.server package as shown in Example 16-8.

Example 16-8 Executing a Stored Procedure with No Arguments

// In InvokingStoredProcAppModuleImpl.java
public void callProcWithNoArgs() {
  getDBTransaction().executeCommand(
    "begin invokestoredprocpkg.proc_with_no_args; end;");
}

16.5.2 How to Invoke Stored Procedure with Only IN Arguments

Invoking stored procedures that accept only IN-mode arguments — which is the default PL/SQL parameter mode if not specified — requires using a JDBC PreparedStatement object. The DBTransaction interface provides a createPreparedStatement() method to create this object for you in the context of the current database connection. You could use a helper method like the one shown in Example 16-9 to simplify the job of invoking a stored procedure of this kind using a PreparedStatement. Importantly, by using a helper method, you can encapsulate the code that closes the JDBC PreparedStatement after executing it. The code performs the following basic tasks:

  1. Creates a JDBC PreparedStatement for the statement passed in, wrapping it in a PL/SQL begin...end block.

  2. Loops over values for the bind variables passed in, if any.

  3. Sets the value of each bind variable in the statement.

    Notice that since JDBC bind variable API's use one-based numbering, the code adds one to the zero-based for loop index variable to account for this.

  4. Executes the statement.

  5. Closes the statement.

Example 16-9 Helper Method to Simplify Invoking Stored Procedures with Only IN Arguments

protected void callStoredProcedure(String stmt, Object[] bindVars) {
  PreparedStatement st = null;
  try {
    // 1. Create a JDBC PreparedStatement for 
    st = getDBTransaction().createPreparedStatement("begin "+stmt+";end;",0);
    if (bindVars != null) {
      // 2. Loop over values for the bind variables passed in, if any
      for (int z = 0; z < bindVars.length; z++) {
        // 3. Set the value of each bind variable in the statement
        st.setObject(z + 1, bindVars[z]);
      }
    }
    // 4. Execute the statement
    st.executeUpdate();
  }
  catch (SQLException e) {
    throw new JboException(e);
  }
  finally {
    if (st != null) {
      try {
        // 5. Close the statement
        st.close();
      }
      catch (SQLException e) {}
    }
  }
}

With a helper method like this in place, calling the proc_with_three_args procedure shown in Example 16-7 would look like this:

// In StoredProcTestModuleImpl.java
public void callProcWithThreeArgs(Number n, Date d, String v) {
  callStoredProcedure("callStoredProcedure.proc_with_three_args(?,?,?)",
                      new Object[]{n,d,v});
}

Notice the question marks used as JDBC bind variable placeholders for the arguments passed to the function. JDBC also supports using named bind variables, but using these simpler positional bind variables is also fine since the helper method is just setting the bind variable values positionally.

16.5.3 How to Invoke Stored Function with Only IN Arguments

Invoking stored functions that accept only IN-mode arguments requires using a JDBC CallableStatement object in order to access the value of the function result after executing the statement. The DBTransaction interface provides a createCallableStatement() method to create this object for you in the context of the current database connection. You could use a helper method like the one shown in Example 16-10 to simplify the job of invoking a stored function of this kind using a CallableStatement. The helper method encapsulates both the creation and clean up of the JDBC statement being used.

The code performs the following basic tasks:

  1. Creates a JDBC CallableStatement for the statement passed in, wrapping it in a PL/SQL begin...end block.

  2. Registers the first bind variable for the function return value.

  3. Loops over values for the bind variables passed in, if any.

  4. Sets the value of each user-supplied bind variable in the statement.

    Notice that since JDBC bind variable API's use one-based numbering, and since the function return value is already the first bind variable in the statement, the code adds two to the zero-based for loop index variable to account for these.

  5. Executes the statement.

  6. Returns the value of the first bind variable.

  7. Closes the statement.

Example 16-10 Helper Method to Simplify Invoking Stored Functions with Only IN Arguments

// Some constants
public static int NUMBER = Types.NUMERIC;
public static int DATE = Types.DATE;
public static int VARCHAR2 = Types.VARCHAR;

protected Object callStoredFunction(int sqlReturnType, String stmt,
                                    Object[] bindVars) {
  CallableStatement st = null;
  try {
    // 1. Create a JDBC CallabledStatement  
    st = getDBTransaction().createCallableStatement(
           "begin ? := "+stmt+";end;",0);
    // 2. Register the first bind variable for the return value
    st.registerOutParameter(1, sqlReturnType);
    if (bindVars != null) {
      // 3. Loop over values for the bind variables passed in, if any
      for (int z = 0; z < bindVars.length; z++) {
        // 4. Set the value of user-supplied bind vars in the stmt
        st.setObject(z + 2, bindVars[z]);
      }
    }
    // 5. Set the value of user-supplied bind vars in the stmt
    st.executeUpdate();
    // 6. Return the value of the first bind variable
    return st.getObject(1);
  }
  catch (SQLException e) {
    throw new JboException(e);
  }
  finally {
    if (st != null) {
      try {
        // 7. Close the statement
        st.close();
      }
      catch (SQLException e) {}
    }
  }
}

With a helper method like this in place, calling the func_with_three_args procedure shown in Example 16-7 would look like this:

// In InvokingStoredProcAppModuleImpl.java
public String callFuncWithThreeArgs(Number n, Date d, String v) {
  return (String)callStoredFunction(VARCHAR2,
                            "invokestoredprocpkg.func_with_three_args(?,?,?)",
                            new Object[]{n,d,v});
}

Notice the question marks are used as JDBC bind variable placeholders for the arguments passed to the function. JDBC also supports using named bind variables, but using these simpler positional bind variables is also fine since the helper method is just setting the bind variable values positionally.

16.5.4 How to Call Other Types of Stored Procedures

Calling a stored procedure or function like invokestoredprocpkg.proc_with_out_args that includes arguments of OUT or IN OUT mode requires using a CallableStatement as in the previous section, but is a little more challenging to generalize into a helper method. Example 16-11 illustrates the JDBC code necessary to invoke the invokestoredprocpkg.proc_with_out_args procedure.

The code performs the following basic tasks:

  1. Defines a PL/SQL block for the statement to invoke.

  2. Creates the CallableStatement for the PL/SQL block.

  3. Registers the positions and types of the OUT parameters.

  4. Sets the bind values of the IN parameters.

  5. Executes the statement.

  6. Creates a JavaBean to hold the multiple return values

    The DateAndStringBean class contains bean properties named dateVal and stringVal.

  7. Sets the value of its dateVal property using the first OUT param.

  8. Sets value of its stringVal property using second OUT param.

  9. Returns the result.

  10. Closes the JDBC CallableStatement.

Example 16-11 Calling a Stored Procedure with Multiple OUT Arguments

public DateAndStringBean callProcWithOutArgs(Number n, String v) {
  CallableStatement st = null;
  try  {
    // 1. Define the PL/SQL block for the statement to invoke
    String stmt = "begin invokestoredprocpkg.proc_with_out_args(?,?,?); end;";
    // 2. Create the CallableStatement for the PL/SQL block
    st = getDBTransaction().createCallableStatement(stmt,0);
    // 3. Register the positions and types of the OUT parameters
    st.registerOutParameter(2,Types.DATE);
    st.registerOutParameter(3,Types.VARCHAR);
    // 4. Set the bind values of the IN parameters
    st.setObject(1,n);
    st.setObject(3,v);
    // 5. Execute the statement
    st.executeUpdate();
    // 6. Create a bean to hold the multiple return values
    DateAndStringBean result = new DateAndStringBean();
    // 7. Set value of dateValue property using first OUT param
    result.setDateVal(new Date(st.getDate(2)));
    // 8. Set value of stringValue property using 2nd OUT param
    result.setStringVal(st.getString(3));
    // 9. Return the result
    return result;
  } catch (SQLException e)  {
    throw new JboException(e);
  } finally  {
    if (st != null) {
      try {
        // 10. Close the JDBC CallableStatement
        st.close();
      }
      catch (SQLException e) {}
    }    
  }
}   

The DateAndString bean used in Example 16-11 is a simple JavaBean with two bean properties like this:

package oracle.summit.model.invokingstoredprocedure;

import java.io.Serializable;
import oracle.jbo.domain.Date;

public class DateAndStringBean implements Serializable {
  Date dateVal;
  String stringVal;
  public void setDateVal(Date dateVal) {this.dateVal=dateVal;}
  public Date getDateVal() {return dateVal;}
  public void setStringVal(String stringVal) {this.stringVal=stringVal;}
  public String getStringVal() {return stringVal;}
}

Note:

In order to allow the custom method to be a legal candidate for inclusion in an application module's custom service interface (if desired), the bean needs to implement the java.io.Serializable. interface. Since this is a "marker" interface, this involves simply adding the implements Serializable keywords without needing to code the implementation of any interface methods.

16.6 Accessing the Current Database Transaction

Since ADF Business Components abstracts all of the lower-level database programming details for you, you typically won't need direct access to the JDBC Connection object. Unless you use the reserved release mode described in Section 49.2.2.3.3, "About Reserved Release Level," there is no guarantee at runtime that your application will use the exact same application module instance or JDBC Connection instance across different web page requests. Since inadvertently holding a reference to the JDBC Connection object in this type of pooled services environment can cause unpredictable behavior at runtime, by design, ADF Business Components has no direct API to obtain the JDBC Connection. This is an intentional attempt to discourage its direct use and inadvertent abuse.

However, on occasion it may come in handy when you're trying to integrate third-party code with ADF Business Components, so you can use a helper method like the one shown in Example 16-12 to access the connection.

Example 16-12 Helper Method to Access the Current JDBC Connection

/**
 * Put this method in your XXXImpl.java class where you need
 * to access the current JDBC connection
 */
private Connection getCurrentConnection() throws SQLException {
 /* Note that we never execute this statement, so no commit really happens */
 PreparedStatement st = getDBTransaction().createPreparedStatement("commit",1);
 Connection conn = st.getConnection();
 st.close();
 return conn;
}

Caution:

Never cache the JDBC connection obtained using the helper method from Example 16-12 anywhere in your own code. Instead, call the helper method for each connection to avoid inadvertently holding a reference to a JDBC connection that might be used in a later request by another user. Due to the pooled services nature of the Oracle ADF runtime environment, you must not close the reference you are holding; however, do ensure that you close your statement.

16.7 Customizing Business Components Error Messages

You can customize any of the builtin ADF Business Components error messages by providing an alternative message string for the error code in a custom message bundle.

Note:

The example in this section refers to the oracle.summit.model.custommessages package in the SummitADF_Examples application application workspace.

16.7.1 How to Customize Base ADF Business Components Error Messages

Assume you want to customize the builtin error message:

JBO-27014: Attribute OrderFilled in SOrd is required

If you have requested the Oracle Application Development Framework (Oracle ADF) source code from Oracle Worldwide Support, you can look in the CSMessageBundle.java file in the oracle.jbo package to see that this error message is related to the combination of the following lines in that message bundle file:

public class CSMessageBundle extends CheckedListResourceBundle {
  // etc.
  public static final String EXC_VAL_ATTR_MANDATORY = "27014";
  // etc. 
  private static final Object[][] sMessageStrings = {
    // etc.
    {EXC_VAL_ATTR_MANDATORY, "Attribute {2} in {1} is required"},
    // etc.
  }
}

The numbered tokens {2} and {1} are error message placeholders. In this example the {l} is replaced at runtime with the name of the entity object and the {2} with the name of the attribute.

To create a custom message bundle file:

  1. In the Applications window, right-click the project that you want to add the message bundle file to and choose Project Properties.

  2. In the Project Properties dialog, select Business Components > Options and click New.

  3. In the Create MessageBundle Class dialog, enter a name and package for the custom message bundle and click OK.

    Note:

    If the fully qualified name of your custom message bundle file does not appear in the Custom Message Bundles to use in this Project list, click the Remove button, then click the Add button to add the new message bundle file created. When the custom message bundle file is correctly registered, its fully qualified class name should appear in the list, as shown in Figure 16-8.

    Figure 16-8 Project Properties Displays Message Resource Bundles

    Resource bundles in Project Properties dialog
  4. In the Project Properties dialog, click OK to dismiss the Project Properties dialog and open the new custom message bundle class in the source editor.

  5. Edit the two-dimensional String array in the custom message bundle class to contain any customized messages you'd like to use.

    Example 16-13 illustrates a custom message bundle class that overrides the error message string for the JBO-27014 error to inform the user that a value must be provided for the named attribute. A second custom error message displays when a database constraint with the name ORD_ORDER_FILLED_CK is violated to inform the user of the expected Order Filled value. For more details about customizing error messages for constraints, see Section 16.7.4, "How to Customize Error Messages for Database Constraint Violations."

    Example 16-13 Custom ADF Business Components Message Bundle

    package oracle.summit.model.custommessages;
    
    import java.util.ListResourceBundle;
    
    public class CustomMessageBundle extends ListResourceBundle {
      private static final Object[][] sMessageStrings 
        = new String[][] {
           {"27014","You must provide a value for {2}"}
           {"S_ORD_ORDER_FILLED_CK", "The order filled value must be Y or N"}
         }
    
    
      /* Return String Identifiers and corresponding Messages in a 
       * two-dimensional array.
       */
      protected Object[][] getContents() {
        return sMessageStrings;
      }
    }
    

16.7.2 What Happens When You Customize Base ADF Business Components Error Messages

After adding this message to your custom message bundle file, if you test the application using the Oracle ADF Model Tester and try to blank out the value of a mandatory attribute, you'll now see your custom error message instead of the default one:

JBO-27014: You must provide a value for Order Filled

You can add as many messages to the message bundle as you want. Any message whose error code key matches one of the built-in error message codes will be used at runtime instead of the default one in the oracle.jbo.CSMessageBundle message bundle.

16.7.3 How to Display Customize Error Messages as Nested Exceptions

When you customize ADF Business Components error messages, you will also need to customize the display of nested error messages. To accomplish this, you must create and register a custom error handler class.

When your business method throws an error, the ADF Model data binding layer intercepts the error and invokes the registered custom error handler class. In general, the error handler class is responsible for formatting the exception to be readable. During this process, the default error handler DCErrorHandlerImpl normally skips the top-level JboException, as this object is a wrapper over other business exceptions and does not have any business significance.

Although skipping the top-level exception is the desired behavior in the case of ADF Business Components errors, the default behavior will result in skipping the custom message you set for replacing the SQLException. To avoid this situation, while displaying each item in a nested exception, your custom error handler class must override DCErrorHandlerImpl::skipException(Exception ex) to decide whether to display the corresponding exception to the user in the final list or not.

Before you begin:

It may be helpful to have an understanding of application modules. For more information, see Section 16.7, "Customizing Business Components Error Messages."

You may also find it helpful to understand functionality that can be added using other Oracle ADF features. For more information, see Section 16.1.1, "Additional Functionality for Extending Business Components."

You will need to complete this task:

Create the error message in the resource bundle, as described in Section 16.7.1, "How to Customize Base ADF Business Components Error Messages."

To provide custom messages for SQLExceptions in your project:

  1. Create an error handler class that extends the default error handler DCErrorHandlerImpl interface provided by the ADF Model data binding layer.

  2. In the error handler class, override the default error handler behavior for the DCErrorHandlerImpl::skipException(Exception ex) method, as shown in Example 16-14.

    This overridden method is necessary to display each item in a nested exception, such as the ones returned for database-level error messages. You must implement logic to check for specifics exception types and, based on the business scenario, determine whether to display it in the list.

  3. You can then register the custom error handler in your project's DataBindings.cpx file, as described in Section 18.3, "Customizing Error Handling."

Example 16-14 shows a custom implementation of the error handler that skips the SQLIntegrityConstraintViolationException from displaying in the error final list displayed to the user. You can choose to skip other database-level error message resulting from errors, such as unique constraint violations or foreign key constraint violations.

Example 16-14 DDL Statement Specifies Constraint Name

package view;

import java.sql.SQLIntegrityConstraintViolationException;

import oracle.adf.model.BindingContext;
import oracle.adf.model.RegionBinding;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.adf.model.binding.DCErrorHandlerImpl;

import oracle.adf.model.binding.DCErrorMessage;

import oracle.jbo.DMLConstraintException;
import oracle.jbo.JboException;

public class CustomErrorHandler extends DCErrorHandlerImpl {

    public CustomErrorHandler() {
        super();
    }


    /**
     * If an exception is a RowValException or a TxnValException
     * and they have nested exceptions, then do not display
     * it.
     */
    @Override
    protected boolean skipException(Exception ex) {

        if (ex instanceof DMLConstraintException) {
            return false;
        } else if (ex instanceof SQLIntegrityConstraintViolationException) {
            return true;
        }
        return super.skipException(ex);
    }
)

16.7.4 How to Customize Error Messages for Database Constraint Violations

If you enforce constraints in the database, you might want to provide a custom error message in your Fusion web application to display to the end user when one of those constraints is violated. For example, assume a constraint called S_ORD_ORDER_FILLED_CK gets added to the application's S_ORD table using the following DDL statement shown in Example 16-15.

Example 16-15 DDL Statement Specifies Constraint Name

alter table s_ord add (
  constraint S_ORD_ORDER_FILLED_CK
      check (ORDER_FILLED IN ('Y', 'N'))
);

To define a custom error message in your application, you add a message to a custom message bundle with the constraint name as the message key. Example 16-16 shows the CustomMessageBundle.java class when it defines a message with the key S_ORD_ORDER_FILLED_CK which matches the name of the database constraint name defined in Example 16-15.

Example 16-16 Customizing Error Message for Database Constraint Violation

package oracle.summit.model.custommessages;

import java.util.ListResourceBundle;

public class CustomMessageBundle extends ListResourceBundle {
  private static final Object[][] sMessageStrings 
    = new String[][] {
       {"27014","You must provide a value for {2}"},
       {"S_ORD_ORDER_FILLED_CK", "The order filled value must be Y or N"}
     };

  protected Object[][] getContents() {
    return sMessageStrings;
  }
}

16.7.5 How to Implement a Custom Constraint Error Handling Routine

If the default facility for assigning a custom message to a database constraint violation does not meet your needs, you can implement your own custom constraint error handling routine. Doing this requires creating a custom framework extension class for the ADF Business Components transaction class, which you then configure your application module to use at runtime.

16.7.5.1 Creating a Custom Database Transaction Framework Extension Class

To write a custom framework extension class for the ADF Business Components transaction, create a class like the CustomDBTransactionImpl shown in Example 16-17. This example overrides the transaction object's postChanges() method to wrap the call to super.postChanges() with a try/catch block in order to perform custom processing on any DMLConstraintException errors that might be thrown. In this simple example, the only custom processing being performed is a call to ex.setExceptions(null) to clear out any nested detail exceptions that the DMLConstraintException might have. Instead of this, you could perform any other kind of custom exception processing required by your application, including throwing a custom exception, provided your custom exception extends JboException directly or indirectly.

Example 16-17 Custom Database Transaction Framework Extension Class

package oracle.summit.model.custommessages;

import oracle.jbo.DMLConstraintException;
import oracle.jbo.server.DBTransactionImpl2;
import oracle.jbo.server.TransactionEvent;

public class CustomDBTransactionImpl  extends DBTransactionImpl2 {
  public void postChanges(TransactionEvent te) {
    try {
      super.postChanges(te);
    }
    /*
     * Catch the DML constraint exception
     * and perform custom error handling here
     */
    catch (DMLConstraintException ex) { 
      ex.setExceptions(null);
      throw ex;
    }
  }
}

16.7.5.2 Configuring an Application Module to Use a Custom Database Transaction Class

In order for your application module to use a custom database transaction class at runtime, you must:

  1. Provide a custom implementation of the DatabaseTransactionFactory class that overrides the create() method to return an instance of the customized transaction class.

  2. Configure the value of the TransactionFactory property to be the fully qualified name of this custom transaction factory class.

Example 16-18 shows a custom database transaction factory class that does this. It returns a new instance of the CustomDBTransactionImpl class when the framework calls the create() method on the database transaction factory.

Example 16-18 Custom Database Transaction Factory Class

package oracle.summit.model.custommessages;

import oracle.jbo.server.DBTransactionImpl2;
import oracle.jbo.server.DatabaseTransactionFactory;

public class CustomDatabaseTransactionFactory
                     extends DatabaseTransactionFactory {
  public CustomDatabaseTransactionFactory() {
  }
  /**
   * Return an instance of our custom CustomDBTransactionImpl class
   * instead of the default implementation.
   *
   * @return instance of custom CustomDBTransactionImpl implementation.
   */
  public DBTransactionImpl2 create() {
    return new CustomDBTransactionImpl();
  }    
}

To complete the job, use the Properties tab of the Edit Configuration dialog to assign the value oracle.summit.model.custommessages.CustomDatabaseTransactionFactory to the TransactionFactory property, as shown in Figure 16-9. You can open the Edit Configuration dialog from the Configuration page of the overview editor for the application module by clicking the Edit selected configuration object button. When you run the application using this configuration, your custom transaction class will be used.

Figure 16-9 ADF Business Components Can Use Custom Database Transaction Class

Business components can use database transaction class

16.8 Creating Extended Components Using Inheritance

Whenever you create a new business component, if necessary, you can extend an existing one to create a customized version of the original. As shown in Figure 16-10, the ProductViewEx view object extends the ProductView view object to add a bind variable named bv_ProductName and to customize the WHERE clause to reference that bind variable.

Figure 16-10 ADF Business Components Can Extend Another Component

Business components can extend others

While the figure shows a view object example, this component inheritance facility is available for all component types. When one component extends another, the extended component inherits all of the metadata and behavior from the parent it extends. In the extended component, you can add new features or customize existing features of its parent component both through metadata and Java code.

Note:

The example in this section refers to the oracle.summit.model.extend package in the SummitADF_Examples application workspace.

16.8.1 How To Create a Component That Extends Another

To create an extended component, use the component wizard in the New Gallery for the type of component you want to create. For example, to create an extended view object, you use the Create View Object wizard. On the Name page of the wizard — in addition to specifying a name and a package for the new component — provide the fully qualified name of the component that you want to extend in the Extends field. To pick the component name from a list, use the Browse button next to the Extends field. Then, continue to create the extended component in the normal way using the remaining panels of the wizard.

16.8.2 How To Extend a Component After Creation

After you define an extended component, JDeveloper lets you change the parent component from which an extended component inherits. You can use the overview editor for the component to accomplish this.

To change the parent component after creation:

  1. In the Applications window, double-click the component.

  2. In the overview editor, click the General navigation tab and then click the Refactor object extends button next to the Extends field.

  3. In the Select Parent dialog, choose the desired component to extend from the package list and click OK.

To change the extended component to not inherit from any parent, select the None checkbox in the Select Parent dialog. This has the same effect as if you deleted the component and re-created to accomplish this.

16.8.3 What Happens When You Create a Component That Extends Another

Business components that you create in an ADF Business Component data model project are comprised of an XML document and an optional Java class. When you create a component that extends another, JDeveloper reflects this component inheritance in both the XML document and in any generated Java code for the extended component.

16.8.3.1 Attributes Added to the Extended Component's XML Descriptor

JDeveloper notes the name of the parent component in the new component's XML document by adding an Extends attribute to the root component element. Any new declarative features you add or any aspects of the parent component's definition you've overridden appear in the extended component's XML document. In contrast, metadata that is purely inherited from the parent component is not repeated for the extended component.

Example 16-19 shows what the ProductViewEx.xml XML document for the ProductViewEx view object looks like. Notice the Extends attribute on the ViewObject element, the Variable element related to the additional bind variable added in the extended view object, and the overridden value of the Where attribute for the WHERE clause that was modified to reference the theProductName bind variable.

Example 16-19 Extended Component Reflects Parent in Its XML Descriptor

<ViewObject
  xmlns="http://xmlns.oracle.com/bc4j"
  Name="ProductViewEx"
  InheritPersonalization="true"
  BindingStyle="OracleName"
  CustomQuery="false"
  ComponentClass="oracle.summit.model.extend.ProductViewExImpl"
  RowClass="oracle.summit.model.extend.ProductViewExRowImpl"
  RowInterface="oracle.summit.model.extend.common.ProductViewExRow"
  ClientRowProxyName="oracle.summit.model.extend.client.ProductViewExRowClient"
  ComponentInterface="oracle.summit.model.extend.common.ProductViewEx"
  ClientProxyName="oracle.summit.model.extend.client.ProductViewExClient"
  Extends="oracle.summit.model.extend.ProductView"
  Where="UPPER(PRODUCT_NAME) LIKE UPPER(:theProductName)||'%'"
...
  <Variable
     Name="bv_ProductName"
     Kind="where"
     Type="java.lang.String"/>
...
</ViewObject>

16.8.3.2 Java Classes Generated for an Extended Component

If you enable custom Java code for an extended component, JDeveloper automatically generates the Java classes to extend the respective Java classes of its parent component. In this way, the extended component can override any aspect of the parent component's programmatic behavior as necessary. If the parent component is an XML-only component with no custom Java class of its own, the extended component's Java class extends whatever base Java class the parent would use at runtime. This could be the default ADF Business Components framework class in the oracle.jbo.server package, or could be your own framework extension class if you have specified that in the Extends dialog of the parent component.

In addition, if the extended component is an application module or view object and you enable client interfaces on it, JDeveloper automatically generates the extended component's client interfaces to extend the respective client interfaces of the parent component. If the respective client interface of the parent component does not exist, then the extended component's client interface directly extends the appropriate base ADF Business Components interface in the oracle.jbo package.

16.8.4 What You May Need to Know About Extending Components

16.8.4.1 Parent Classes and Interfaces for Extended Components

Since an extended component is a customized version of its parent, code you write that works with the parent component's Java classes or its client interfaces works without incident for either the parent component or any customized version of that parent component.

For example, assume you have a base ProductView view object with custom Java classes and client interfaces like:

  • class ProductViewImpl

  • row class ProductViewRowImpl

  • client interface ProductView

  • row client interface ProductViewRow

If you create a ProductViewEx view object that extends ProductView, then you can use the base component's classes and interface to work both with ProductView and ProductViewEx.

Example 16-20 illustrates a test client program that works with the ProductView, ProductViewRow, ProductViewEx, and ProductViewExRow client interfaces. A few interesting things to note about the example are the following:

  1. You can use parent ProductView interface for working with the ProductViewEx view object that extends it.

  2. Alternatively, you can cast an instance of the ProductViewEx view object to its own more specific ProductViewEx client interface.

  3. You can test if row ProductViewRow is actually an instance of the more specific ProductViewExRow before casting it and invoking a method specific to the ProductViewExRow interface.

Example 16-20 Working with Parent and Extended Components

package oracle.summit.model.extend;

import oracle.jbo.ApplicationModule;
import oracle.jbo.AttributeDef;
import oracle.jbo.Row;
import oracle.jbo.StructureDef;
import oracle.jbo.ValidationException;
import oracle.jbo.ViewObject;
import oracle.jbo.client.Configuration;

import oracle.summit.model.extend.common.ProductView;
import oracle.summit.model.extend.common.ProductViewEx;
import oracle.summit.model.extend.common.ProductViewExRow;
import oracle.summit.model.extend.common.ProductViewRow;


public class TestClient {  
    public static void main(String[] args) {
    String        amDef = "oracle.summit.model.extend.AppModule";
    String        config = "AppModuleLocal";
    ApplicationModule am =
                         Configuration.createRootApplicationModule(amDef,config);
    ProductView products = (ProductView)am.findViewObject("ProductView1");
    products.executeQuery();
    ProductViewRow product = (ProductViewRow)products.first();
    printAllAttributes(products,product);
    testSomethingOnProductsRow(product);
    products = (ProductView)am.findViewObject("ProductViewEx1");
    ProductViewEx productsByName = (ProductViewEx)products;
    productsByName.setbv_ProductName("bunny");
    productsByName.executeQuery();
    product = (ProductViewRow)productsByName.first();
    printAllAttributes(productsByName,product);
    testSomethingOnProductsRow(product);
    am.getTransaction().rollback();
    Configuration.releaseRootApplicationModule(am,true);
  }
  private static void testSomethingOnProductsRow(ProductViewRow product) {
    try {
      if (product instanceof ProductViewExRow) {
        ProductViewExRow productByName = (ProductViewExRow)product;
        productByName.someExtraFeature("Test");        
      }
      product.setName("Q");
      System.out.println("Setting the Name attribute to 'Q' succeeded.");
    }
    catch (ValidationException v) {
      System.out.println(v.getLocalizedMessage());      
    }    
  }
  private static void printAllAttributes(ViewObject vo, Row r) {
    String viewObjName = vo.getName();
    System.out.println("Printing attribute for a row in VO '"+
                       viewObjName+"'");
    StructureDef def = r.getStructureDef();
    StringBuilder sb = new StringBuilder();
    int numAttrs = def.getAttributeCount();
    AttributeDef[] attrDefs = def.getAttributeDefs();
    for (int z = 0; z < numAttrs; z++) {
      Object value = r.getAttribute(z);
      sb.append(z > 0 ? "  " : "")
        .append(attrDefs[z].getName())
        .append("=")
        .append(value == null ? "<null>" : value)
        .append(z < numAttrs - 1 ? "\n" : "");
    }
    System.out.println(sb.toString());
  }
}

Running the test client produces the results shown in Example 16-21.

Example 16-21 Results of Running TestClient.java

Printing attribute for a row in VO 'ProductView1'
Id=10011
  Name=Bunny Boot
  ShortDesc=Beginners ski boot
  LongtextId=518
  ImageId=1001
  SuggestedWhlslPrice=150
  WhlslUnits=<null>
  SomeValue=I am from the Product Impl Class
Setting the Name attribute to 'Q' succeeded.

Note:

In this example, ProductView is an entity-based view object based on the Product entity object. The Product entity object includes a transient SomeValue attribute that returns the string "I am from the Product Impl class". You'll learn more about why this was included in the example in Section 16.9, "Substituting Extended Components in a Delivered Application."

16.8.4.2 Generated Classes for Extended Components

When you create an extended component, the Class Extends button on the Java page of the extended component's wizard is disabled. Additionally, in the application module editor's Java page, when you click Edit java options, the Class Extends button in the Java dialog appears disabled. This is due to the fact that JDeveloper automatically extends the appropriate class of its parent component, so it does not make sense to allow you to select a different class.

16.8.4.3 Business Component Types

Entity Objects

When you create an extended entity object, you can introduce new attributes, new associations, new validators, and new custom code. You can override certain declarative aspects of existing attributes as well as overriding any method from the parent component's class. You may not remove from the extended entity object any attributes that the base class defines from the extended entity object.

View Objects

When you create an extended view object, you can introduce new attributes, new view links, new bind variables, and new custom code. You can override certain declarative aspects of existing attributes as well as overriding any method from the parent component's class. You may not remove from the extended view object any attributes that the base class defines.

Application Modules

When you create an extended application module, you can introduce new view object instances or new nested application module instance and new custom code. You can also override any method from the parent component's class from the extended view object.

16.8.4.4 New Attributes in an Extended Component

If you add new attributes in an extended entity object or view object, the attribute index numbers are computed relative to the parent component. For example, consider the ProductView view object mentioned in Section 16.8.4.1, "Parent Classes and Interfaces for Extended Components." If you enable a custom view row class, it might have attribute index constants defined in the ProductViewRowImpl.java class like this:

public class ProductViewRowImpl extends SummitViewRowImpl
                             implements ProductViewRow {

  /**
   * AttributesEnum: generated enum for identifying attributes and accessors. 
   * Do not modify.
   */
  public enum AttributesEnum {...}

  public static final int ID = AttributesEnum.Id.index();
  public static final int NAME = AttributesEnum.Name.index();
  public static final int SHORTDESC = AttributesEnum.ShortDesc.index();
  public static final int LONGTEXTID = AttributesEnum.LongtextId.index();
  public static final int IMAGEID = AttributesEnum.ImageId.index();
  public static final int SUGGESTEDWHLSLPRICE =
                              AttributesEnum.SuggestedWhlslPrice.index();
  public static final int WHLSLUNITS = AttributesEnum.WhlslUnits.index();
  public static final int SOMEVALUE = AttributesEnum.SomeValue.index();
  //etc.
}

When you create an extended view object like ProductViewEx, if that view object adds an additional attribute like SomeExtraAttr and has a custom view row class enabled, then its attribute constants will be computed relative to the maximum value of the attribute constants in the parent component:

public class ProductViewExRowImpl extends ProductViewRowImpl
                                   implements ProductViewExRow {

  public static final int MAXUSAGECONST = 1;

  public enum AttributesEnum {
      SomeExtraAttr {
          public Object get(ProductViewExRowImpl obj) {
              return obj.getSomeExtraAttr();
          }

          public void put(ProductViewExRowImpl obj, Object value) {
              obj.setAttributeInternal(index(), value);
          }
      }
       
      private static AttributesEnum[] vals = null;
      private static int firstIndex =
          ViewDefImpl.getMaxAttrConst("oracle.summit.model.extend.ProductView");

      public abstract Object get(ProductViewExRowImpl object);
 
      public abstract void put(ProductViewExRowImpl object, Object value);
 
      public int index() {
          return AttributesEnum.firstIndex() + ordinal();
      }
 
      public static int firstIndex() {
          return firstIndex;
      }
 
      public static int count() {
          return AttributesEnum.firstIndex() +
                                 AttributesEnum.staticValues().length;
      }
 
      public static AttributesEnum[] staticValues() {
          if (vals == null) {
              vals = AttributesEnum.values();
          }
          return vals;
      }
  }
  public static final int SOMEEXTRAATTR = AttributesEnum.SomeExtraAttr.index();
...
}

16.9 Substituting Extended Components in a Delivered Application

If you deliver packaged applications that can require on-site customization for each potential client of your solution, ADF Business Components offers a useful feature to simplify that task.

Note:

The example in this section refers to the ExtendedProject project in the SummitADF_Examples application workspace.

All too often, on-site application customization is performed by making direct changes to the source code of the delivered application. This approach demonstrates its weaknesses whenever you deliver patches or new feature releases of your original application to your clients. Any customizations they had been applied to the base application's source code need to be painstakingly reapplied to the patched or updated version of the base application. Not only does this render the application customization a costly, ongoing maintenance expense, it can introduce subtle bugs due to human errors that occur when reapplying previous customizations to new releases.

ADF Business Components offers a superior, component-based approach to support application customization that doesn't require changing — or even having access to — the base application's source code. To customize your delivered application, your customers can:

  1. Import one or more packages of components from the base application into a new project.

  2. Create new components to effect the application customization, extending appropriate parent components from the base application as necessary.

  3. Define a list of global component substitutions, naming their customized components to substitute for your base application's appropriate parent components.

When the customer runs your delivered application with a global component substitution list defined, their customized application components are used by your delivered application without changing any of its code. When you deliver a patched or updated version of the original application, their component customizations apply to the updated version the next time they restart the application without needing to reapply any customizations.

16.9.1 How To Substitute an Extended Component

To define global component substitutions, use the Project Properties dialog in the project where you have created extended components based on the imported components from the base application.

Note:

You can only substitute a component in the base application with an extended component that inherits directly or indirectly from the base one.

To substitute an extended component:

  1. In the Applications window, right-click the project that you want to add the extended component to and choose Project Properties.

  2. In the Project Properties dialog, select ADF Business Components > Substitutions and select the base application's component in the Available list.

  3. In the Substitute list, select the customized, extended component to substitute .

  4. Click Add.

    For example, assume that you have created the view object CustomizedProduct in a package that extends the base view object Products. To substitute the CustomizedProducts view object for the legacy Products view object, you would select these view objects as shown in Figure 16-11 to define the component substitution.

    Figure 16-11 Defining Business Component Substitutions

    ADF Business Components Substitutions dialog

16.9.2 What Happens When You Substitute

When you define a list of global component substitutions in a project named ExtendedProject, the substitution list is saved in the ExtendedProject.jpx in the root directory of the source path.

The file will contain Substitute elements as shown in Example 16-22, one for each component to be substituted.

Example 16-22 Component Substitution List Saved in the Project's JPX File

<JboProject
   Name="ExtendedProject"
   SeparateXMLFiles="true"
   PackageName="oracle.summit.model.extended" >
   <Containee
      Name="custompackage"
      FullName="oracle.summit.model.custompackage"
      ObjectType="JboPackage" >
   </Containee>
   <Containee
      Name="extended"
      FullName="oracle.summit.model.extended"
      ObjectType="JboPackage" >
   </Containee>
   <AppContainee
      Name="Model"
      FullName="oracle.summit.model.Model"
      ObjectType="JboProject">
   <Substitutes>
      <Substitute
        OldName="oracle.summit.model.extended.ProductEx"
        NewName="oracle.summit.model.custompackage.CustomProduct" />
   </Substitutes>
</JboProject>

16.9.3 How to Enable the Substituted Components in the Base Application

To have the original application use the set of substituted components, define the Java system property Factory-Substitution-List and set its value to the name of the project whose *.jpx file contains the substitution list. The value should be just the project name without any *.jpr or *.jpx extension.

For example, consider a simple example that customizes the Product entity object and the ProductView view object described in Section 16.8.4.1, "Parent Classes and Interfaces for Extended Components." To perform the customization, assume you create new project named ExtendedProject that:

  • Defines a library for the JAR file containing the base components

  • Imports the package containing Product and ProductView

  • Creates new extended components in a distinct package name called CustomizeProduct and CustomizeProductView

  • Defines a component substitution list to use the extended components.

When creating the extended components, assume that you:

  • Added an extra view attribute named SomeExtraAttribute to the ProductViewEx view object.

  • Added a new validation rule to the CustomizedProduct entity object to enforce that the product name cannot be the letter "Q".

  • Overrode the getChecksum() method in the CustomizedProduct.java class to return "I am the CustomizedProduct Class".

If you define the Factory-Substitution-List Java system property set to the value ExtendsAndSubstitutes, then when you run the exact same test client class shown in Example 16-20 the output of the sample will change to reflect the use of the substituted components.