Friday, March 9, 2012

FAQ #38 - How to add help to an ADF application

Introduction

Adding help to an ADF Fusion Web application is thoroughly explained in section 19.5 Displaying Help for Components of the Web User Interface Developer’s Guide for Oracle Application Development Framework guide. Yet, as it seems from this post https://forums.oracle.com/forums/message.jspa?messageID=10190922#10190922 in the JDeveloper and ADF forum at OTN, some of us still have questions around this subject. In this post I will attempt to clarify the subject of adding help to your ADF application by presenting pretty much the same information but in a different approach, so I suggest that before continuing with the reading this post you first take a look at the aforementioned link in the guide. Then, if you still have questions, read on.

Main Theme

Help added to an ADF Fusion Web application is categorized based on where the help contents are stored. Currently the help contents can be stored in any of the following mediums:
  • In a resource bundle
  • In a managed bean, or 
  • In an XML Localization Interchange File Format (XLIFF) XML file
So the first step would be to determine where your help contents will be stored. As mentioned, you have the choices listed above. So the first step entails having to make a design decision which one to use. What gets a little bit confusing is that each one of the three choices above allows you to display help using an external URL, which will open the help contents in a separate browser window.

So how do you add help to your ADF Fusion Web application? Simply by adding a help-provider tag to the adf-settings.xml configuration file. The adf-settings.xml file resides in the .adf/META-INF directory and it is accessible in JDeveloper in the Application Resources part of the Application Navigator via the Descriptors | ADF META-INF node. Depending on the help contents location (one of the three choices listed above) you need to provide values for the help-provider-class and property tags under the help-provider tag. Here is an example for the case where the help contents are stored in a resource bundle:

In the table below I have listed the appropriate help provider classes and property names/values depending on the location of the help contents:


You can provide external URL help in each case (resource bundle, managed bean, XLIFF) by providing your own custom help provider class. Just make sure that in each case you extend the appropriate default help provider class (shown in the table above), i.e. oracle.adf.view.rich.help.ResourceBundleHelpProvider for resource bundle based help or oracle.adf.view.rich.help.ELHelpProvider for managed bean or XLIFF based help.

For the remainder of this post, we will see how to add help utilizing the resource bundle method, which is in my opinion the most straightforward case. In this case all you have to do is to create a resource bundle, i.e. a text file with the file extension .properties and add the help contents to it. The help contents are made up of text lines that conform to the following naming convention:

help_topic_id=help_topic_data

where help_topic_id is the help topic identifier and help_topic_data is the actual help shown to the user. Here is an example:

PREFIX_TOPIC1_INSTRUCTIONS=Example for help instructions
PREFIX_TOPIC1_DEFINITION=Example for help definition
A few things to notice about the help topic identifiers:
  • For the same topic (TOPIC1 in the example above), you can define both instructions help and definition help by appending _INSTRUCTIONS and _DEFINITION to the end of the id respectively. Instructions help usually appears as you tab from one UI component to another, while definition help usually appears as a help icon in front of the UI component but this may vary depending the UI component. Again, take a look at section 19.5 Displaying Help for Components of the Web User Interface Developer’s Guide for Oracle Application Development Framework guide for more information on this.
  • Notice the prefix (PREFIX_ in the example above) used by the help topic identifiers. The prefix identifies the help provider and is specified in the help-provider tag in the adf-settings.xml configuration file.

Finally, how do you assign help topic identifiers to UI components? For this you can use the HelpTopicId property under Appearance in the Property Inspector, just don't add the _INSTRUCTIONS or _DEFINITION part of the help topic id:


The result of using PREFIX_TOPIC1 for an af:inputText UI component is shown below:

As soon as you tab into the inputText component the instructions help is shown. The definition help is shown by hovering the mouse on the question mark icon added by the framework in front of the inputText component.

One last thing: the question mark icon could actually become a button that when clicked will display help from an external URL in a separate browser window. As mentioned earlier, you do this by extending the default help provider. But again, this is explained nicely in the documentation.

Conclusion

Hopefully this post clarified any additional questions that you might have had related to adding help to your ADF Fusion Web application.

Until the next post, have fun with JDeveloping!

Thursday, March 8, 2012

FAQ #37 - How to create audit trail records using ADFBC, Pt. 2

Introduction

In this second part of creating historical audit trail records using ADFBC we will see how to integrate, in a generic way, the stored procedures introduced in part 1 of these series. For more information on the database schema and stored procedures that are required to support this technique refer to FAQ #36 - How to create audit trail records using ADFBC, Pt. 1.

Main Theme

As explained in part 1 (FAQ #36 - How to create audit trail records using ADFBC, Pt. 1), historical audit trails are generated by calling the stored procedures AUDIT_TABLE and AUDIT_DETAILS. Both of these procedures are part of the AUDIT_PKG database package.

The AUDIT_TABLE procedure is called to create a historical audit trail of the corresponding table. As explained in part 1, corresponding history tables are created for each table being audited. These history tables conform to the following naming convention: table_name_HISTORY where table_name is the table being audited.  For example the corresponding history REGIONS table is called REGIONS_HISTORY.

To create a generic implementation, we will need to create framework extension classes for both the oracle.jbo.server.EntityImpl and oracle.jbo.server.ApplicationModuleImpl classes. Once these framework extension classes are created ensure that the ADFBC project settings are changed to reflect this fact. You do this by bringing up the Project Properties dialog and selecting ADF Business Components | Base Classes as it is shown in the picture below:

The next step is to override the EntityImpl doDML() method for the EntityImpl extension framework class and add the following code to it:

Note a few things about this overridden doDML() method:

  1. We call getEntityDef().getProperty(DISABLE_AUDIT_PROPERTY) to determine whether auditing for the specific entity object has been disabled. This is controlled by the presence of an entity object property identified by the constant DISABLE_AUDIT_PROPERTY. As long as this property has not been defined in the entity object, auditing is enabled by default.
  2. We call getEntityDef().getSourceType() to determine the source type of the entity object. We will audit entity objects based on database tables only. This is done by comparing the return value of getEntityDef().getSourceType() to DBOBJ_TYPE_TABLE. DBOBJ_TYPE_TABLE is a constant defined in oracle.jbo.server.EntityDefImpl indicating that the entity object is based on a database table.
  3. The first parameter required by the AUDIT_TABLE stored procedure is the user's current session identifier. This can be retrieved by calling SessionImpl.findOrCreateSessionContextManager().getCurrentSession().getId(). Take a look at this post for information on this: Bit #39 - Retrieving the current session id at the ADFBC layer.
  4. The second parameter passed to AUDIT_TABLE stored procedure is the commit identifier. We would like to keep track changes for each commit done in the same user session. We retrieve the commit identifier from a database sequence by calling the helper getCommitId() (see source code below).
  5. We pass the DML operation as a string (INSERT, UPDATE or DELETE) based on the value of the operation parameter passed to doDML() by the ADFBC framework.
  6. We call getEntityDef().getSource() to get the name of the table associated with the entity object.
  7. Finally we call the getEntityId() helper (see code below) to get the entity object's primary key. Note that we convert the primary key to a string before passing as a parameter to the AUDIT_TABLE stored procedure.
Here is the code for the getCommitId() and getEntityId() helpers:

Notice a couple of things about these helper methods:
  1. In getEntityId() we iterate through the entity object attributes in order to determine the primary key attribute. We do this by first calling getStructureDef().getAttributeDefs() to get the attribute definitions and then by calling isPrimaryKey() for each attribute to determine if it is a primary key attribute. If it is, we call getAttribute() to retrieve and return its value. Notice that you will have to expand this implementation if you have an entity object with a composite primary key.
  2. In getCommitId() we add the commit identifier returned from the database sequence to the session user data by calling getDBTransaction().getSession().getUserData(). We do this because we will need the same commit identifier at a later stage when calling the AUDIT_DETAILS stored procedure.
As explained in part 1 of this post, to create the specific details of the audit trail we need to call AUDIT_PKG.AUDIT_DETAILS stored procedure. To do this, override the afterCommit() in the ApplicationModuleImpl framework extension class and add the following code to it:


Notice in this case how we call getSession().getUserData().get(ExtEntityImpl.COMMIT_ID) to get the same commit identifier and pass it as a parameter to the AUDIT_DETAILS stored procedure. Also, notice that we remove the commit identifier from the session user data once we are done with calling the stored procedure.

Conclusion

In this two-part series we've seen a custom implementation of creating a historical audit trail based on a stored procedure implementation in the database. Notice that this technique is fairly generic as it does not require any additional implementation in the specific entity objects used throughout the application. As mentioned above, auditing is enabled by default and can be disabled by the presence of a property defined in the entity object. This can be easily reversed so that auditing is not done by default. If you found this useful, let me know so I can email you or post the source code as well.

Until the next time, have fun JDeveloping!


Wednesday, March 7, 2012

FAQ #36 - How to create audit trail records using ADFBC, Pt. 1

Introduction

ADFBC allows for the creation of historical audit trail records at the entity object level via the use of history columns. By default the framework allows for keeping track of historical information for attributes indicated as Created On, Created By, Modified On, Modified By and Version Number. This is explained in section 4.10.12 How to Track Created and Modified Dates Using the History Column of the Fusion Developer's Guide for Oracle Application Development Framework 11g Release 2 guide. The guide also explains how to create your own custom history types in section 4.16 Creating New History Types. In this post we will detail a generic technique to create historical audit trail records using a PL/SQL stored procedure in the database. For this we will use the HR schema and demonstrate how to create an audit trail for the REGIONS and COUNTRIES tables.

Main Theme

Schema changes

Before you start on implementing an audit trail in your ADF application you need to decide on what specific information you need to create a historical trail on. This might include creating audit trail information for specific tables and/or specific columns in these tables. For the sake of this post, I will demonstrate how to create historical information for the REGIONS and COUNTRIES tables in the HR schema. For this purpose you will need to create corresponding REGIONS_HISTORY and COUNTRIES_HISTORY tables. These tables will hold the historic information. Here is a structure of the REGIONS_HISTORY table used for this implementation:

This table holds the historical data (columns REGION_ID and REGION_NAME) and information related to audit trail such as the user session that created the audit trail (SESSION_ID), the commit number within the session (COMMIT_ID), the type of audit operation, i.e. INSERT, UPDATE, DELETE (OPERATION) and a link to the HISTORY table (HISTORY_ID). The HISTORY table provides the ability to browse for the complete audit trail and its structure is shown below:

The table holds such information as the date/time of the specific audit trail, the user that has created the audit trail and the user session identifier. For each HISTORY record there are a number of history lines that detail the specific changes. This detailed information is stored in a table called HISTORY_LINE. The structure of the HISTORY_LINE table is shown below:

As you can see, this table holds audit trail information specific for each change done to the table being audited. The information is kept in this table on a per session commit basis, i.e. for each commit done in the user session. Some of the columns of interest are the following:
  • HISTORY_TABLE - The name of the table that was audited
  • FIELD_NAME - The name of the column with the HISTORY_TABLE that was audited
  • OPERATION - The operation on the column, i.e. INSERT, UPDATE, DELETE
  • VALUE_OLD - The original column data value
  • VALUE_NEW - The new column data value
Given the information in the HISTORY_LINE table we will be able to link to the corresponding history table (REGIONS_HISTORY, COUNTRIES_HISTORY, etc.) and be able to present a "picture" of what the data in the table looked like prior to the change. We can then compare it with the latest data in the corresponding table (REGIONS, COUNTRIES, etc.). Here an example of what the data in the HISTORY_LINE table might look like:

PL/SQL support

Historical audit trail information is created by calling two separate PL/SQL procedures called AUDIT_TABLE and AUDIT_DETAILS. These procedures have been implemented as part of the AUDIT_PKG in the database. AUDIT_TABLE is called to create the corresponding _HISTORY (REGIONS_HISTORY, COUNTRIES_HISTORY) table row while AUDIT_DETAILS is called to create the HISTORY and HISTORY_LINE rows. This is what the AUDIT_TABLE procedure looks like:


The code uses dynamic SQL to call the specific table audit procedures. The AUDIT_PKG package declares an array called tables_to_audit which defines the specific tables to audit. If the table is to be audited it must be added to this array. Here is its declaration for this example:

Part of this implementation requires that you provide the specific table audit procedures. These procedures are called dynamically by AUDIT_TABLE as mentioned above. The specific audit procedures must conform to the following naming format: AUDIT_table_name, where table_name is the table being audited. For the REGIONS table for instance, the procedure should be called AUDIT_REGIONS. Here is an example implementation of AUDIT_REGIONS:

The AUDIT_PKG.AUDIT_TABLE procedure will be called from the ADFBC layer to create an audit record for the specific table. The procedure is expecting the current session identifier, a commit identifier, the audit operation (INSERT, UPDATE, DELETE), the table name being audited and the table's primary key. Since the table's primary key is specified as a VARCHAR2 type, it will need to converted to this type if it is of different type. In this case also note the limitation of creating an audit trail for a table that uses a composite primary key. We will seen in part 2 of this post how this is done.

As mentioned earlier, the AUDIT_DETAILS procedure creates or locates the HISTORY record for the specific user session and uses dynamic SQL to create the individual HISTORY_LINE rows. It is shown below:


In this case you will also need to implement the specific audit details procedures to generate the HISTORY_LINE rows for the columns that were affected for each specific table. These procedures must conform to the following naming format since they are dynamically called: AUDIT_table_name_DETAILS, where table_name is the table being audited. Here is an example implementation of the AUDIT_REGIONS_DETAILS:

These procedures determine the differences between the original table (i.e. REGIONS) and the _HISTORY table (i.e. REGIONS_HISTORY) that have occurred for the specific commit session and produce the data in the HISTORY_LINE table.

The AUDIT_PKG.AUDIT_DETAILS procedure will be called from the ADFBC layer specifying the user's session identifier, the commit identifier and the user name. We will see this in the second part of this post.

Conclusion

We will conclude in the next part where we will see how to hook up this implementation to the ADFBC framework. We will do this in a generic way that will require minimal changes only to framework extension classes.

Until then, enjoy JDeveloping!
Related Posts Plugin for WordPress, Blogger...