Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers 10g Release 3 (10.1.3.0) Part Number B25947-02 |
|
|
View PDF |
This chapter describes advanced techniques that apply to all types of ADF Business Components.
This chapter includes the following sections:
Section 25.1, "Globally Extending ADF Business Components Functionality"
Section 25.3, "Customizing Framework Behavior with Extension Classes"
Section 25.7, "Working with Libraries of Reusable Business Components"
Section 25.8, "Customizing Business Components Error Messages"
Section 25.9, "Creating Extended Components Using Inheritance"
Section 25.10, "Substituting Extended Components In a Delivered Application"
Note:
To experiment with a working version of the examples in this chapter, download theAdvancedExamples
workspace from the Example Downloads page at http://otn.oracle.com/documentation/jdev/b25947_01
.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. This section describes:
What framework extension classes are
How to create an extension class and base ADF components you create on it
How to adopt the best practice of using a whole custom layer of framework extension classes for your component or specific project
An ADF Business Components framework extension class is Java class you write that extends one of the framework's base classes to:
Augment a built-in feature works with additional, generic functionality
Change how a built-in feature works, or even to
Workaround a bug you encounter in a generic way
Once you've created a framework extension class, any new ADF components you create can be based on your customized framework class instead of the base one. Of course, you can also update the definitions of existing components to use the new framework extension class as well.
To create a framework extension class, follow these steps:
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 ADF applications, create a separate FrameworkExtensions
project (as shown in the SRDemo application) to contain the framework extension classes.
Ensure the BC4J Runtime library is in the project's libraries list.
Use the Libraries page of the Project Properties dialog to verify this and to add the library if missing.
Create the new class using the Create Java Class dialog.
This dialog is available in the New Gallery in the General category.
Specify the appropriate framework base class from the oracle.jbo.server
package in the Extends field.
Figure 25-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.
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 Components component classes exist in both a server-side and a remote-client version. For example, if you use the JDeveloper Class Browser and typeApplicationModuleImpl
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 classes in the oracle.jbo.server
package.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 ADF components on your own framework extension classes.
You can set the base classes for any ADF component using the Java page of any ADF Business Components wizard or editor. Before doing so, review the following checklist:
If you have decided to create your framework extension classes in a separate project, ensure that you have visited the Dependencies page of the Project Properties dialog for the project containing your business components in order to mark the FrameworkExtension
project as a project dependency.
If you have packaged your framework extension classes in a Java archive (JAR) file, ensure that you have created a named library definition to reference its JAR file and also listed 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, Figure 25-2 shows how you would use the CustomAppModuleImpl
class as the base class for a new application module. By clicking the Class Extends button of the Java page of the wizard, the Extends dialog displays to let you enter the fully-qualified name of your framework extension class (or use the Browse button to use the JDeveloper Class Browser to find it quickly).
The same Class Extends button appears on the Java page of every ADF Business Components wizard and editor, so you can use this technique to choose your desired framework extension base class(es) both for new components or existing ones.
Note:
When using the JDeveloper Class Browser in the Extends dialog of an ADF Business Components wizard or editor 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 25-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 theoracle.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.When an ADF component you create extends a custom framework extension class, JDeveloper updates its XML component definition to reflect the custom class name you've chosen.
For example, assume you've created the YourService
application module above 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 component definition (YourService.xml) will look like what you see in Example 25-1. 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 25-1 Custom Base Class Names Are Recorded in XML Component Definition
<AppModule Name="YourService" ComponentClass="com.yourcompany.fwkext.CustomAppModuleImpl" > <!-- etc. --> </AppModule>
Figure 25-3 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.
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 25-2, it also updates the component's XML component definition to reflect the name of the custom component class.
Example 25-2 Custom Component Class Recorded in XML Component Definition
<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 25-3.
Example 25-3 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 25-4 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.
{para}?>
If you have an ADF component with a custom Java class and later decide to base the component on a framework extension class, use the Class Extends button on the Java page of the component editor to change the component's base class. Doing this updates the component's XML component definition to reflect the new base class, and also modifies the extends
clause in the component's custom Java class. If you manually update the extends
clause without using the component editor, the component's XML component definition 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.
In the examples above, you've seen a single CustomAppModuleImpl
class that extends the base ApplicationModuleImpl
class. However, there is no fixed limit on how many levels of framework extension classes you create. After creating a company-level CustomAppModuleImpl
to use for all application modules in all ADF applications 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:
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
.
If you decide to use a specific set of framework extension classes as a standard for a given project, you can open the Business Components > Base Classes page in the Project Properties dialog, as shown in Figure 25-5, to define your preferred base classes for each component type. 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 Application Module Object class name field as shown. 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.
By choosing the Tools | Preferences main menu item in JDeveloper, you can open the Business Components > Base Classes page to set framework base classes preferences at a global level. This setting does not affect any existing projects containing ADF components, but JDeveloper will use these as the default values for the project-level business components base classes preferences for any new projects created.
Before you begin to develop application-specific business components, Oracle recommends that you consider creating a complete layer of framework extension classes and setting up your project-level preferences to use that layer by default. You might not have any custom code in mind to put in some (or any!) of these framework extension classes yet, but the first time you encounter a need to:
Add a generic feature that all your company's application modules require
Augment a built-in feature with some custom, generic processing
Workaround a bug you encounter in a generic way
You will be glad you heeded this recommendation. Failure to set up these preferences at the outset can present your team with a substantial inconvenience 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. Putting a complete layer of framework classes in place to be automatically used by JDeveloper at the start of your project is an insurance policy against this inconvenience and the wasted time related to dealing with it later in the project.
A common set of customized framework base classes in a package name of your own choosing like com.yourcompany.adfextensions
, 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 CustomDatabaseTransactionFactory extends DatabaseTransactionFactory
To make your framework extension layer classes easier to package as a reusable library, Oracle recommends creating them in a separate project from the projects that use them. In the SRDemo application, the FrameworkExtensions
project contains all of the framework extension classes used by the application.
Note:
For your convenience, theFrameworkExtensions
project in the AdvancedExamples
workspace contains a set of these classes. You can select the com.yourcompany.adfextensions
package in the Application Navigator and choose the Refactor > Rename option from the context menu to change the package name of all the classes to a name you prefer.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
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 Files category.
Note:
If you don't see the Deployment Profiles category in the New Gallery, set the Filter By dropdown list at the top of the dialog to the All Technologies choice to make it visible.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.
Finally, to create the JAR file, select the FrameworkExtensions.deploy
node in the Application Navigator under the Resources folder, and choose Deploy to JAR File on the context menu. A Deployment tab appears in the JDeveloper Log window that should display feedback like:
---- Deployment started. ---- Apr 28, 2006 1:42:39 PM Running dependency analysis... Wrote JAR file to ...\FrameworkExtensions\deploy\FrameworkExtensions.jar Elapsed time for deployment: less than one second ---- Deployment finished. ---- Apr 28, 2006 1:42:39 PM
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, do the following:
Choose Tools > Manage Libraries from the JDeveloper main menu.
In the Manage Libraries dialog, select the Libraries tab.
Select the User folder in the tree and click the New button.
In the Create Library dialog that appears, name the library "Framework Extension Layer" and select the Class Path node and click Add Entry.
Use the Select Path Entry dialog that appears to select the FrameworkExtensions.jar
file that contains the class files for the framework extension components, then click Select.
Select the Source Path node and click Add Entry.
Use the Select Path Entry dialog that appears to select the ..\FrameworkExtensions\src
directory where the source files for the framework extension classes reside, then click Select.
Click OK to dismiss the Create Library dialog and define the new library.
When finished, you will see your new "Framework Extension Layer" user-defined library, as shown in Figure 25-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.
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 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.
Figure 25-7 illustrates the three primary interfaces ADF 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.
In Section 7.9.3, "What You May Need to Know About Enabling View Object Key Management for Read-Only View Objects" you learned that for read-only view objects the findByKey()
method and the setCurrentRowWithKey
built-in 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.
The SRDemo application contains an SRViewObjectImpl
class in the FrameworkExtensions
project that is the base class for all view objects in the application. That framework extension class for view objects extends the base ViewObjectImpl
class and overrides the create()
method as shown in Example 25-4 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 25-4 Automating Setting Manage Rows By Key
public class SRViewObjectImpl 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. }
In JDeveloper, when you create application modules, view objects, and entity objects you can open the Custom Properties tab of the editor 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 SRViewObjectImpl
framework extension class in the SRDemo application overrides the view object's insertRow()
method 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. In the SRDemo application, the ServiceHistories
view object defines this custom metadata property so any new rows added to it get inserted at the bottom.
Example 25-5 Conditionally Inserting New Rows at the End of a View Object's Default RowSet
public class SRViewObjectImpl 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.
{para}?>
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 25-1.
Table 25-1 Entity Object and View Object Attribute Kinds
Public AttributeDef Constant |
Attribute Kind Description |
---|---|
|
Persistent attribute |
|
Transient attribute |
|
View object attribute mapped to an entity-level transient attribute |
|
SQL-Calculated attribute |
|
Dynamic attribute |
|
Accessor attribute returning a |
|
Accessor attribute returning a single |
Once you have written framework extension classes that depend on custom properties, you can set a JDeveloper preference so that your custom property names show in the dropdown list on the Custom Properties tab of the appropriate component editor. To set up these pre-defined custom property names, choose Tools | Preferences from the JDeveloper main menu and open the Business Components > Property Names tab in the Preferences dialog.
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 you write inside your ADF components' custom Java classes can use it.
Note:
You can experiment with an example of this technique by using theProgrammaticallySetProperties
project in the AdvancedExamples
workspace. See the note at the beginning of this chapter for download instructions.In addition to creating framework extension classes, you can create custom interfaces that all of your components can implement by default. 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.
Note:
The examples in this section refer to theCustomizedExtensionInterface
project in the AdvancedExamples
workspace. See the note at the beginning of this chapter for download instructions.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.
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.
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(); } }
Rebuild your project.
The ADF wizards will only "see" your interfaces after they have been successfully compiled.
Then, to create a new ProductModule
application module which exposes the global extension interface CustomApplicationModule
and is based on the CustomApplicationModuleImpl
framework extension class, do the following:
Ensure that your application module has a custom Java class.
If it doesn't, you can enable one on the Java page of the Application Module Editor and clicking the Apply button.
Select CustomApplicationModuleImpl
as the base class for the application module.
To do this, again use the Java tab of the Application Module Editor and open the Extends dialog by clicking the Class Extends button.
Indicate that you want the CustomApplicationModule
interface to be one of the custom interfaces that clients can use with your component.
To do this, open the Client Interface page. Click on the Interfaces button. Shuttle the CustomApplicationModule
interface from the Available to the Selected list, then click OK.
Insure that at least one method appears in the Selected list on the Client Interface page.
Note:
You need to select at least one method in the Selected list on the Client Interface page, 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 customProductModule
interface.When you dismiss the Application Module editor, JDeveloper generates the application module custom interface ProductModule
. It 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 Oracle 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 aViewObjectImpl
framework extension class, as well as for a ViewRowImpl
extension class.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 following PL/SQL package:
create or replace package devguidepkg 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 devguidepkg;
The following sections explain how to invoke each of the example procedures and functions in this package.
Note:
The examples in this section refer to theStoredProcedureInvocation
project in the AdvancedExamples
workspace. See the note at the beginning of this chapter for download instructions.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 25-6.
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 25-7 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:
Creates a JDBC PreparedStatement
for the statement passed in, wrapping it in a PL/SQL begin
...end
block.
Loops over values for the bind variables passed in, if any.
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.
Executes the statement.
Closes the statement.
Example 25-7 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 above would look like this:
// In StoredProcTestModuleImpl.java public void callProcWithThreeArgs(Number n, Date d, String v) { callStoredProcedure("devguidepkg.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.
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 25-8 to simplify the job of invoking a stored function of this kind using a CallableStatement
. As above, the helper method encapsulates both the creation and clean up of the JDBC statement being used.
The code performs the following basic tasks:
Creates a JDBC CallableStatement
for the statement passed in, wrapping it in a PL/SQL begin
...end
block.
Registers the first bind variable for the function return value.
Loops over values for the bind variables passed in, if any.
Sets the value of each bind 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.
Executes the statement.
Returns the value of the first bind variable.
Closes the statement.
Example 25-8 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 above would look like this:
// In StoredProcTestModuleImpl.java public String callFuncWithThreeArgs(Number n, Date d, String v) { return (String)callStoredFunction(VARCHAR2, "devguidepkg.func_with_three_args(?,?,?)", new Object[]{n,d,v}); }
Notice the question marks as above that 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.
Calling a stored procedure or function like devguidepkg.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 25-9 illustrates the JDBC code necessary to invoke the devguidepkg.proc_with_out_args
procedure.
The code performs the following basic tasks:
Defines a PL/SQL block for the statement to invoke.
Creates the CallableStatement
for the PL/SQL block.
Registers the positions and types of the OUT
parameters.
Sets the bind values of the IN
parameters.
Executes the statement.
Creates a JavaBean to hold the multiple return values
The DateAndStringBean
class contains bean properties named dateVal
and stringVal
.
Sets the value of its dateVal
property using the first OUT
param.
Sets value of its stringVal
property using second OUT
param.
Returns the result.
Closes the JDBC CallableStatement
.
Example 25-9 Calling a Stored Procedure with Multiple OUT Arguments
public Date callProcWithOutArgs(Number n, String v) { CallableStatement st = null; try { // 1. Define the PL/SQL block for the statement to invoke String stmt = "begin devguidepkg.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 25-9 is a simple JavaBean with two bean properties like this:
package devguide.advanced.storedproc; 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 thejava.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.Since the ADF Business Components components abstract 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 28.3.1, "Supported Release Levels", 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, the ADF Business Components layer 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 25-10 to access the connection.
Example 25-10 Helper Method to Access the Current JDBC Connection
/** * Put this method in your XXXXImpl.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:
Oracle recommends that you never cache the JDBC connection obtained using the helper method above in your own code anywhere. Instead, call the helper method each time you need it to avoid inadvertently holding a reference to a JDBC Connection that might be used in another request by another user at a later time do to the pooled services nature of the ADF runtime environment.As with other Java components, you can create a JAR file containing one of more packages of reusable ADF components. Then, in other projects you can import one or more packages of components from this component library to reference those in a new application.
Note:
The examples in this section refer to theReusableComponents
, ProjectImportingReusableComponents
, and OtherProjectWithComponents
projects in the AdvancedExamples
workspace. See the note at the beginning of this chapter for download instructions.Use the Create Business Components Archive Profile dialog to create a JAR file containing the Java classes and XML component definitions that comprise your business components library. This is available in the New Gallery in the General > Deployment Files category.
Note:
If you don't see the Deployment Profiles category in the New Gallery, set the Filter By dropdown list at the top of the dialog to the All Technologies choice to make it visible.Give the deployment profile a name like ReusableComponents.bcdeploy
and click OK. As shown in Figure 25-8, the ReusableComponents.bcdeploy
business components deployment archive profile contains two nested JAR deployment profiles:
ReusableComponentsMiddleTier.deploy
ReusableComponentsCommon.deploy
These two nested profiles are standard JAR deployment profiles that are pre-configured to bundle:
All of the business components custom java classes and XML component definitions into a ReusableComponentsCSMT.jar
archive
All of the client interfaces, message bundle classes, and custom domains into a ReusableComponentsCSCommon.jar
They are partitioned this way in order to simplify deployment of ADF Business Components-based applications. The *CSMT.jar
is an archive of components designed to be deployed only on the middle tier application server. The *CSCommon.jar
is common both to the application server and to the remote client tier in the deployment scenario when the client interacting with the application module is running in a different physical server from the application module with which it is working.
To create the JAR files, select the ReusableComponents.bcdeploy
node in the Application Navigator under the Resources folder, and choose Deploy on the context menu. A Deployment tab appears in the JDeveloper Log window that should display feedback like:
---- Deployment started. ---- Apr 28, 2006 7:04:02 PM Running dependency analysis... Wrote JAR file to ...\ReuseableComponents\deploy\ReuseableComponentsCSMT.jar Running dependency analysis... Wrote JAR file to ...\ReuseableComponents\deploy\ReuseableComponentsCSCommon.jar Elapsed time for deployment: less than one second ---- Deployment finished. ---- Apr 28, 2006 7:04:02 PM
Once you have created a reusable library of business components, you can import one or more packages of components from that library in other projects to reference them. When you import a package of business components from a library, the components in that package are available in the various Available lists of the ADF Business Components component wizards and editor, however they do not display in the Application Navigator. The only components that appear in the Application Navigator are the ones in the source path for the current project.
To import a package of business components from a library, do the following:
Define a library for your JAR file on the Libraries tab of the Project Properties dialog of the importing project.
You can define the library as a project-level library or a user-level library. Be sure to include both the *CSMT.jar
and the *CSCommon.jar
in the class path of the library definition.
Include the new library in your importing project's library list.
With the importing project selected in the Application Navigator, choose File | Import from the JDeveloper main menu.
In the Import dialog that appears, select Business Components from the list.
Use the file open dialog to navigate into your library's *CSMT.jar
file — as if it were a directory — and select the XML component definition file from any components in the package whose components you want to import.
Acknowledge the alert that confirms the successful importing of the package.
Repeat steps 3-6 again for each package of components you want to import.
Assuming that there was an entity object like Product
in the package(s) of components you imported, you could then create a new view object in the importing project using the imported Product
component as its entity usage. This is just one example. You can reference any of the imported components as if they were in the source path of your project. The only difference is that you cannot edit the imported components. In fact, the reusable component library JAR file might only contain the XML component definition files and the Java *.class
files for the components without any source code.
When you import a package of components into a project named YourImportingProjectName
, JDeveloper adds a reference to that package in the YourImportingProjectName.jpx
file in the root directory of your importing project's source path. As part of this entry, it includes a design time project named _LocationURL
whose value points to the JAR file in which the imported components reside.
{para}?>
The Application Navigator displays all business components in the source path of your project. If you want to add additional business components from a directory that is not currently part of your project's source path, then open the Project Content page of the Project Properties dialog and add the parent directory for these other components as one of the directories in the Java Content list. In contrast to imported packages of components, these additional components added to your project's source path will be fully editable and will appear in the Application Navigator.
If you make changes to your imported components and update the JAR file that contains them, you need to close and reopen any importing projects in order to pickup the changes. This does not require exiting out of JDeveloper. You can select your importing project in the Application Navigator, choose File | Close from the main menu, and then re-expand the project's nodes to close and reopen the project. When you do this, JDeveloper will reread the components from the updated version of the imported JAR file.
If you mistakenly import a package of components, or wish to remove an imported package of components that you are not using, at this time, JDeveloper provides no interactive way to do this. To unimport a package, you need to follow these steps:
Remove the workspace in question from the Application Navigator.
Use a text editor to edit the YourImportingProjectName.jpx
file.
Remove the Containee element in that file the represents the imported package you want to remove.
This means removing every line in the file between (and including) the appropriate Containee tag for that package and its matching /Containee tag.
Reopen the workspace in JDeveloper.
Caution:
Do not remove an imported package if your project still has components that reference it. If you do, JDeveloper will throw exceptions when the project is opened, or your application may have unpredictable behavior. Ensure that there are no references to any of the components in the imported package before manually removing the package entry from the*.jpx
file.Note:
The examples in this section refer to theCustomizedErrorMessages
project in the AdvancedExamples
workspace. See the note at the beginning of this chapter for download instructions.You can customize any of the built-in ADF Business Components error messages by providing an alternative message string for the error code in a custom message bundle. Assume you do not like the built-in error message:
JBO-27014: Attribute Name is Product is required
If you have requested the 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, do the following:
Open the Business Components > Options page in the Project Properties dialog for the project containing your business components.
Notice the Custom Message Bundles to use in this Project list at the bottom of the dialog.
Click New.
Enter a name and package for the custom message bundle in the Create MessageBundle class dialog 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, it's fully-qualified class name should appear in the list.Click OK to dismiss the Project Properties dialog and open the new custom message bundle class in the source editor.
Edit the two-dimensional String
array in the custom message bundle class to contain any customized messages you'd like to use.
Example 25-11 illustrates a custom message bundle class that overrides the error message string for the JBO-27014
error considered above.
Example 25-11 Custom ADF Business Components Message Bundle
package devguide.advanced.customerrs; import java.util.ListResourceBundle; public class CustomMessageBundle extends ListResourceBundle { private static final Object[][] sMessageStrings = new String[][] { {"27014","You must provide a value for {2}"} }; protected Object[][] getContents() { return sMessageStrings; } }
After adding this message to your custom message bundle file, if you test the application using the Business Components Browser 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 Name
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.
If you enforce constraints in the database, you might want to provide a custom error message in your ADF application to display to the end user when one of those constraints is violated. For example, imagine that you added a constraint called NAME_CANNOT_BEGIN_WITH_X
to the SRDemo application's PRODUCTS
table using the following DDL statement:
alter table products add ( constraint name_cannot_begin_with_x check (upper(substr(name,1,1)) != 'X') );
To define a custom error message in your application, just add a message to a custom message bundle with the constraint name as the message key. For example, assuming that you use the same CustomMessageBundle.java
class created in the previous section, Example 25-12 shows what it would look like to define a message with the key NAME_CANNOT_BEGIN_WITH_X
which matches the name of the database constraint name defined above.
Example 25-12 Customizing Error Message for Database Constraint Violation
package devguide.advanced.customerrs; import java.util.ListResourceBundle; public class CustomMessageBundle extends ListResourceBundle { private static final Object[][] sMessageStrings = new String[][] { {"27014","You must provide a value for {2}"}, {"NAME_CANNOT_BEGIN_WITH_X", "The name cannot begin with the letter x!"} }; protected Object[][] getContents() { return sMessageStrings; } }
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 transaction class, which you then configure your application module to use at runtime.
To write a custom framework extension class for the ADF transaction, create a class like the CustomDBTransactionImpl
shown in Example 25-13. 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 25-13 Custom Database Transaction Framework Extension Class
package devguide.advanced.customerrs; import oracle.jbo.DMLConstraintException; import oracle.jbo.JboException; import oracle.jbo.common.StringManager; 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; } } }
In order for your application module to use a custom database transaction class at runtime, you must:
Provide a custom implementation of the DatatabaseTransactionFactory
class that overrides the create()
method to return an instance of the customized transaction class.
Configure the value of the TransactionFactory
property to be the fully-qualified name of this custom transaction factory class.
Example 25-14 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 25-14 Custom Database Transaction Factory Class
package devguide.advanced.customerrs; import oracle.jbo.server.DBTransactionImpl2; import oracle.jbo.server.DatabaseTransactionFactory; public class CustomDatabaseTransactionFactory extends DatabaseTransactionFactory { public CustomDatabaseTransactionFactory() { } /** * Return an instance of our custom ToyStoreDBTransactionImpl 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 Configuration Editor to assign the value devguide.advanced.customerrs.CustomDatabaseTransactionFactory
to the TransactionFactory
property. When you run the application using this configuration, your custom transaction class will be used.
Whenever you create a new business component, if necessary, you can extend an existing one to create a customized version of the original. For example, in the SRDemo application, as shown in Figure 25-9, the ServiceRequestsByStatus
view object extends the ServiceRequests
view object to add a named bind variable named TheStatus
and to customize the WHERE
clause to reference that bind variable.
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 examples in this section refer to theBaseProject
project in the AdvancedExamples
workspace. See the note at the beginning of this chapter for download instructions.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.
As you've learned, the ADF business components you create are comprised of an XML component definition and an optional Java class. When you create a component that extends another, JDeveloper reflects this component inheritance in both the XML component definition and in any generated Java code for the extended component.
JDeveloper notes the name of the parent component in the new component's XML component definition 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 component definition. In contrast, metadata that is purely inherited from the parent component is not repeated for the extended component.
Example 25-15 shows what the ServiceRequstsByStatus.xml
XML component definition for the ServiceRequstsByStatus
view object looks like. Notice the Extends
attribute on the ViewObject element, 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 StatusCode
bind variable.
Example 25-15 Extended Component Reflects Parent in Its XML Descriptor
<ViewObject Name="ServiceRequestsByStatus" Extends="oracle.srdemo.model.queries.ServiceRequests" Where="((ServiceRequest.CREATED_BY = CreatedByUser.USER_ID) AND (ServiceRequest.ASSIGNED_TO = AssignedToUser.USER_ID(+))) AND (ServiceRequest.PROD_ID = Product.PROD_ID) AND STATUS LIKE NVL(:StatusCode,'%')" OrderBy="REQUEST_DATE DESC" BindingStyle="OracleName" CustomQuery="false" ComponentClass="oracle.srdemo.model.queries.ServiceRequestsByStatusImpl" FetchMode="FETCH_AS_NEEDED" UseGlueCode="false" > <Variable Name="StatusCode" Kind="where" Type="java.lang.String" DefaultValue="%" > </Variable> </ViewObject>
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.
{para}?>
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 Products
view object with custom Java classes and client interfaces like:
class ProductsImpl
row class ProductsRowImpl
interface Products
row interface ProductsRow
If you create a ProductsByName
view object that extends Products
, then you can use the base component's classes and interface to work both with Products
and ProductsByName
.
Example 25-16 illustrates a test client program that works with the Products
, ProductsRow
, ProductsByName
, and ProductsByNameRow
client interfaces. A few interesting things to note about the example are the following:
You can use parent Products
interface for working with the ProductsByName
view object that extends it.
Alternatively, you can cast an instance of the ProductsByName
view object to its own more specific ProductsByName
client interface.
You can test if row ProductsRow
is actually an instance of the more specific ProductsByNameRow
before casting it and invoking a method specific to the ProductsByNameRow
interface.
Example 25-16 Working with Parent and Extended Components
package devguide.advanced.extsub; /* imports omitted */ public class TestClient { public static void main(String[] args) { String amDef = "devguide.advanced.extsub.ProductModule"; String config = "ProductModuleLocal"; ApplicationModule am = Configuration.createRootApplicationModule(amDef,config); Products products = (Products)am.findViewObject("Products"); products.executeQuery(); ProductsRow product = (ProductsRow)products.first(); printAllAttributes(products,product); testSomethingOnProductsRow(product); // 1. You can use parent Products interface for ProductsByName products = (Products)am.findViewObject("ProductsById"); // 2. Or cast it to its more specific ProductsByName interface ProductsByName productsById = (ProductsByName)products; productsById.setProductName("Ice"); productsById.executeQuery(); product = (ProductsRow)productsById.first(); printAllAttributes(productsById,product); testSomethingOnProductsRow(product); am.getTransaction().rollback(); Configuration.releaseRootApplicationModule(am,true); } private static void testSomethingOnProductsRow(ProductsRow product) { try { // 3. Test if row is a ProductsByNameRow before casting if (product instanceof ProductsByNameRow) { ProductsByNameRow productByName = (ProductsByNameRow)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 above produces the following results:
Printing attribute for a row in VO 'Products' ProdId=100 Name=Washing Machine W001 Checksum=I am the Product Class Setting the Name attribute to 'Q' succeeded. Printing attribute for a row in VO 'ProductsById' ProdId=119 Name=Ice Maker I012 Checksum=I am the Product Class SomeExtraAttr=SomeExtraAttrValue ## Called someExtraFeature of ProductsByNameRowImpl Setting the Name attribute to 'Q' succeeded.
Note:
In this example, Products is an entity-based view object based on theProduct
entity object. The Product
entity object includes a transient Checksum
attribute that returns the string "I am the Product class". You'll learn more about why this was included in the example in Section 25.10, "Substituting Extended Components In a Delivered Application".When you create an extended component, the Class Extends button on the Java page of the extended component is 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.
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.
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.
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.
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 Products
view object mentioned above. If you enable a custom view row class, it might have attribute index constants defined in the ProductsRowImpl.java class like this:
public class ProductsRowImpl extends ViewRowImpl implements ProductsRow { public static final int PRODID = 0; public static final int NAME = 1; public static final int CHECKSUM = 2; //etc. }
When you create an extended view object like ProductsByName, if that view object adds an addition 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 ProductsByNameRowImpl extends ProductsRowImpl implements ProductsByNameRow { public static final int MAXATTRCONST = ViewDefImpl.getMaxAttrConst("devguide.advanced.extsub.Products"); public static final int SOMEEXTRAATTR = MAXATTRCONST;
Additional attributes would have index values of MAXATTRCONST+1
, MAXATTRCONST+2
, etc.
After defining an extended component, JDeveloper allows you to change the parent component from which an extended component inherits. You can accomplish by:
Selecting the extended component in the Application Navigator
Using the Property Inspector to change the Extends
property
However, you cannot currently use this technique to change the extended component to not inherit from any parent. The one exception to this limitation is the entity object, whose component editor offers an Extends field on the Name page that you can blank out if necessary. For all other extended components, to make them no longer extend from a parent component you need to delete and recreate them to accomplish this.
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 examples in this section refer to theBaseProject
and ExtendAndSubstitute
projects in the AdvancedExamples
workspace. See the note at the beginning of this chapter for download instructions.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 re-applied 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 customzations 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:
Import one or more packages of components from the base application into a new project.
Create new components to effect the application customization, extending appropriate parent components from the base application as necessary.
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 re-apply any customizations.
To define global component substitutions, use the Business Components > Substitutions page of the Project Properties dialog in the project where you've created extended components based on the imported components from the base application. As shown in Figure 25-10, to define each component substitution:
Select the base application's component in the Available list.
Select the customized, extended component to substitute in the Substitute list.
Click Add.
Note:
You can only substitute a component in the base application with an extended component that inherits directly or indirectly from the base one.When you define a list of global component substitutions in a project named YourExtendsAndSubstitutesProject
, the substitution list is saved in the YourExtendsAndSubstitutesProject
.jpx
in the root directory of the source path.
The file will contain Substitute elements as shown in Example 25-17, one for each component to be substituted.
Example 25-17 Component Substitution List Saved in the Project's JPX File
<JboProject Name="ExtendAndSubstitute" SeparateXMLFiles="true" PackageName="" > <Containee Name="anotherpkg" FullName="devguide.advanced.anotherpkg.anotherpkg" ObjectType="JboPackage" > </Containee> <Containee Name="extsub" FullName="devguide.advanced.extsub.extsub" ObjectType="JboPackage" > <DesignTime> <Attr Name="_LocationURL" Value="../../BaseProject/deploy/BaseProjectCSMT.jar" /> </DesignTime> </Containee> <Substitutes> <Substitute OldName="devguide.advanced.extsub.Product" NewName="devguide.advanced.anotherpkg.CustomizedProduct" /> <Substitute OldName="devguide.advanced.extsub.Products" NewName="devguide.advanced.anotherpkg.CustomizedProducts" /> </Substitutes> </JboProject>
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.
Consider a simple example that customizes the Product
entity object and the Products
view object described in Section 25.9.3.1, "You Can Use Parent Classes and Interfaces to Work with Extended Components". To perform the customization, assume you create new project named ExtendsAndSubstitutes
that:
Defines a library for the JAR file containing the base components
Imports the package containing Product
and Products
Creates new extended components in a distinct package name called CustomizedProduct
and CustomizedProducts
Defines a component substitution list to use the extended components.
When creating the extended components, assume that you:
Added an extra view attribute named ExtraViewAttribute
to the CustomizedProducts 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 above in Example 25-16 the output of the sample will change to reflect the use of the substituted components:
Printing attribute for a row in VO 'Products' ProdId=100 Name=Washing Machine W001 Checksum=I am the CustomizedProduct Class ExtraViewAttribute=Extra Attr Value The name cannot be Q! Printing attribute for a row in VO 'ProductsById' ProdId=119 Name=Ice Maker I012 Checksum=I am the CustomizedProduct Class SomeExtraAttr=SomeExtraAttrValue ## Called someExtraFeature of ProductsByNameRowImpl The name cannot be Q!
Compared to the output from Example 25-16, notice that in the presence of the factory substitution list, the Products
view object in the original test program now has the additional ExtraViewAttribute
, now reports a Checksum
attribute value of "I am the CustomizedProduct Class", and now disallows the assignment of the product name to have the value "Q". These component behavior changes were performed without needing to modify the original Java or XML source code of the delivered components.