Skip Headers
Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers
10g Release 3 (10.1.3.0)

Part Number B25947-02
Go to Documentation Home
Home
Go to Table of Contents
Contents
Go to Index
Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

6 Creating a Business Domain Layer Using Entity Objects

This chapter describes how to use entity objects to create a reusable layer of business domain objects for use in your J2EE applications.

This chapter includes the following sections:

6.1 Introduction to Entity Objects

An entity object is the ADF Business Components component that represents a row in a database table and simplifies modifying its data. Importantly, it allows you to encapsulate domain business logic for those rows to ensure that your business policies and rules are consistently validated. By the end of this chapter, you'll understand the concepts shown in Figure 6-1:

Figure 6-1 Entity Object Encapsulates Business Logic for a Table

Image of entity object and encapsulated business logic

When your application module creates, modifies, or removes entity objects and commits the transaction, changes are saved automatically. When you need to work together with a ServiceRequest and the User who created it, or the ServiceHistory entries it logically contains, then associations between entities simplify the task. Entity objects support numerous declarative business logic features to enforce the validity of your data as well. As you'll see in more detail in later chapters, you will typically complement declarative validation with additional custom application logic and business rules to cleanly encapsulate a maximum amount of domain business logic into each entity object. Your associated set of entity objects forms a reusable business domain layer that you can exploit in multiple applications.

Note:

To experiment with a working version of the examples in this chapter, download the DevGuideExamples workspace from the Example Downloads page at http://otn.oracle.com/documentation/jdev/b25947_01/ and see the BusinessLayerWithEntityObjects project.

6.2 Creating Entity Objects and Associations

The simplest way to create entity objects and associations is to reverse-engineer them from existing tables. Since often you will already have a database schema to work with, the simplest way is also the most common approach that you'll use in practice. When needed, you can also create an entity object from scratch, and then generate a table for it later as well.

6.2.1 How to Create Entity Objects and Associations from Existing Tables

To create an entity object, use the Business Components from Tables wizard. The wizard is available from the New Gallery in the Business Tier > ADF Business Components category. If it's the first component you're creating in the project, the Initialize Business Components Project dialog appears to allow you to select a database connection before the wizard will appear. These examples assume that you're working with an connection named SRDemo for the SRDEMO schema.

In step 1 on the Entity Objects page, you select a list of tables from the Available list for which you want to create entity objects. The Package field at the top allows you to indicate the package in which all of the entity objects will be created. If the Auto-Query checkbox is checked, then the list of available tables appears immediately, otherwise you need to click the Query button to retrieve the list after optionally providing a name filter. Once you have selected a table from the Available list, the proposed entity object name for that table appears in the Selected list with the related table name in parenthesis. Clicking an entity object name in the Selected list, you can use the Entity Name field below to change the default entity object name. Since each entity object instance represents a single row in a particular table, it is best practice to name the entity objects with a singular noun like User, Product, and ServiceHistory instead of their plural counterparts. Figure 6-2 shows what the wizard page looks like after selecting all five tables in the SRDEMO schema, setting a package name of devguide.model.entities, and renaming each proposed entity object to have a singular name.

Note:

Since an entity object represents a database row, it seems natural to call it an entity row. Alternatively, since at runtime the entity row is an instance of a Java object that encapsulates business logic for that database row, the more object-oriented term entity instance is also appropriate. Therefore, these two terms are interchangeable.

Figure 6-2 Creating Entity Objects for Existing Tables

Step 1 of Create Business Components from Tables wizard

Click Finish to create the entity objects. A progress dialog appears while the components are created, and then you can see the resulting components in the Application Navigator as shown in Figure 6-3. You can experiment with the Flat Level control and the Sort by Type button to see the effect they have on the display.

Figure 6-3 New Entity Objects in Application Navigator Using Flat Level 1 and Sorted By Type

Image shows how Application Navigator sorts entity objects

6.2.2 What Happens When You Create Entity Objects and Associations from Existing Tables

When you create an entity object from an existing table, first JDeveloper interrogates the data dictionary to infer the following information:

  • The Java-friendly entity attribute names from the names of the table's columns (e.g. USER_ID -> UserId)

  • The SQL and Java data types of each attribute based on those of the underlying column

  • The length and precision of each attribute

  • The primary and unique key attributes

  • The mandatory flag on attributes, based on NOT NULL constraints

  • The relationships between the new entity object and other entities based on foreign key constraints

JDeveloper then creates the XML component definition file that represents its declarative settings and saves it in the directory that corresponds to the name of its package. One of the entities created above was named User in the devguide.model.entities package, so the XML file created will be ./devguide/model/entities/User.xml under the project's source path. This XML file contains the name of the table, the names and data types of each entity attribute, and the column name for each attribute. If you're curious to see its contents, you can see the XML file for the entity object by selecting it in the Application Navigator and looking in the corresponding Sources folder in the Structure window. Double-clicking the User.xml node will open the XML in an editor so you can inspect it.

Note:

If your IDE-level Business Components Java generation preferences so indicate, the wizard may also create an optional custom entity object class UserImpl.java.

In addition to the entity objects whose names you decided, the wizard also generates named association components that capture information about the relationships between entity objects. A quick glance at the database diagram in Figure 6-4 confirms that the default association names like SvrAssignedToUsrFkAssoc and SvrCreatedByUsrFkAssoc are derived by converting the foreign key constraint names to a Java-friendly name and adding the Assoc suffix. For each association created, JDeveloper creates an appropriate XML component definition file and saves it in the directory that corresponds to the name of its package. By default the associations reverse-engineered from foreign keys get created in the same package as the entities, so, for example, one of the association XML file will be named ./devguide/model/entities/SvrAssignedToUsrFkAssoc.xml.

Figure 6-4 USERS and SERVICE_REQUESTS Tables Are Related By Foreign Keys

Images shows tables related by foreign keys

6.2.2.1 What Happens When a Table Has No Primary Key

If a table has no primary key constraint, then JDeveloper cannot infer the primary key for the entity object. Since every entity object must have at least one attribute marked as a primary key, the wizard will create an attribute named RowID and use the database ROWID value as the primary key for the entity. If appropriate, you can edit the entity object later to mark a different attribute as a primary key and remove the RowID attribute. If you use the Create Entity Object wizard, you will be prompted to use RowID as the primary key, if you have not set any other attribute as primary key.

6.2.3 Creating Entity Objects Using the Create Entity Wizard

To create a single entity object, you can use the Create Entity Object wizard. The wizard is available from the New Gallery in the Business Tier > ADF Business Components category. By selecting the name of an existing table in step 1, on the Name page, JDeveloper will infer all of the same information for the new entity that it would have done using the Business Components from Tables wizard. If you enter a name of a table that does not exist, you will need to define each attribute one by one on the Attributes page of the wizard. You can later create the table manually, or generate it, as described in Section 6.2.6, "Creating Database Tables from Entity Objects".

6.2.4 Creating an Entity Object for a Synonym or View

When creating an entity object using the Business Components from Tables wizard or the Create Entity Object wizard, an entity object can represent an underlying table, synonym, or view. You've seen in Section 6.2.2.1, "What Happens When a Table Has No Primary Key", that the framework can infer the primary key and related associations by inspecting database primary and foreign key constraints in the data dictionary. However, if the schema object you select is a database view then neither the primary key, nor associations can be inferred since database views do not have database constraints. In this case, if you use the Business Components from Tables wizard, the primary key defaults to RowID. If you use the Create Entity Object wizard, you'll need to specify the primary key manually by marking at least one of its attributes as a primary key.

If the schema object you choose is a synonym, then there are two possible outcomes. If the synonym is a synonym for a table, then the wizard and editor will behave as if you had specified a table. If instead the synonym refers to a database view, then they will behave as if you had specified the a view.

6.2.5 Editing an Existing Entity Object or Association

After you've created a new entity object, you can edit any of its settings by using the Entity Object Editor. Select the Edit menu option on the context menu in the Application Navigator, or double-click on the entity object, to launch the editor. By opening the different panels of the editor, you can adjust the settings that define the entity and govern its runtime behavior. Later sections of this chapter cover many of these settings.

6.2.6 Creating Database Tables from Entity Objects

To create database tables based on entity objects, select the package in the Application Navigator that contains the entity objects and choose Create Database Objects... from the context menu. A dialog appears to let you select the entities whose tables you'd like to create. This tool can be used to generate a table for an entity object you created from scratch, or to drop and re-create an existing table.

Caution:

This feature does not generate a DDL script to run later; it performs its operations directly against the database and will drop existing tables. A dialog appears to confirm that you want to do this before proceeding. For entities based on existing tables, use with prudence.

6.2.6.1 Using Database Key Constraints for an Association

In the Association editor, the Use Database Key Constraints checkbox on the Association Properties page controls whether the related foreign key constraint will be generated when creating the tables for entity objects. Selecting this option does not have any runtime implications.

6.2.7 Synchronizing an Entity with Changes to Its Database Table

Inevitably you (or your DBA) might alter a table for which you've already created an entity object. Your existing entity will not be disturbed by the presence of additional attributes in its underlying table; however, if you want to access the new column in the table in your J2EE application, you'll need to synchronize the entity object with the database table. To perform this synchronization automatically, select the entity object in question and choose Synchronize with Database... from the context menu. For example, suppose you had done the following at the SQL*Plus command prompt to add a new SECURITY_QUESTION column to the USERS table:

ALTER TABLE USERS ADD (security_question VARCHAR2(60));

After you use the synchronize feature on the existing User entity, the Synchronize with Database dialog would appear as shown in Figure 6-5

Figure 6-5 Synchronizing an Entity Object with Its Underlying Table

Image shows Synchronize with Database dialog

The dialog proposes the changes that it can perform for you automatically, and by clicking the desired synchronize button you can carry out the synchronization.

6.2.8 What You May Need to Know About Creating Entities

The Business Components from Tables wizard makes it easy to quickly generate a lot of business components at the same time. In practice, this does not mean that you should use it to immediately create entity objects for every table in your database schema just because you can. If your application will be using all of those tables, that may be appropriate, but since you can use the wizard whenever needed, Oracle recommends creating the entity objects for the tables you know will be involved in the application. Section 8.9, "Deciding on the Granularity of Application Modules" outlines some thoughts on use case-driven design for your business services that can assist you in understanding which entity objects are required to support your application's business logic needs. You can always add more entity objects later as necessary.

6.3 Creating and Configuring Associations

If your database tables have no foreign key constraints defined, JDeveloper won't be able to automatically infer the associations between the entity objects that you create. Since several interesting runtime features that you'll learn about depend on the presence of entity associations, Oracle recommends that you create them manually.

6.3.1 How to Create an Association

To create an association, use the Create New Association wizard.

Assuming the association between the ServiceRequest and the ServiceHistory entities did not already exist, you could create it manually following these steps:

To create an association:

  1. Open the Create New Association wizard from the New Gallery in the Business Tier > ADF Business Components category.

  2. In step 1 on the Name page, provide a name for the association component and a package to contain it.

  3. In step 2, on the Entity Objects page, select a "source" attribute from one of the entity objects that will be involved in the association to act as the master. Figure 6-6 shows the selected SvrId attribute from the ServiceRequest entity object as the source entity attribute.

    Figure 6-6 Manually Defining the Attribute Pairs That Relate Two Entity Objects

    Image shows step 2 of the Create Association wizard
  4. Next, select a corresponding destination attribute from the other entity object involved in the association. Since ServiceHistory rows contain a service request ID that relates them to a specific ServiceRequest row, select this SvrId foreign key attribute in the ServiceHistory entity object as the destination attribute.

  5. Next, click Add to add the matching attribute pair to the table of source and destination attribute pairs below. If there were multiple attribute pairs required to define the association, you could repeat these steps to add additional source/target attribute pairs. For this example, the one (SvrId,SvrId) pair is all that's required.

  6. Finally, ensure that the Cardinality dropdown correctly reflects the cardinality of the association. Since the relationship between a ServiceRequest and its related ServiceHistory rows is one-to-many, you can leave the default setting.

  7. In step 3, on the Association SQL page, you can preview the association SQL predicate that will be used at runtime to access the related ServiceHistory entity objects for a given instance of the ServiceRequest entity object.

  8. In step 4, on the Association Properties page, you control whether the association represents a one-way relationship or a bidirectional one and set other properties that define the runtime behavior of the association. Notice in Figure 6-7 that the Expose Accessor checkbox is checked in both the Source and Destination group boxes. By default, an association is a bi-directional relationship allowing either entity object to access the related entity row(s) on the other side when needed. In this example, it means that if you are working with an instance of a ServiceRequest entity object, you can easily access the collection of its related ServiceHistory rows. You can also easily access the ServiceRequest to which it belongs, with any instance of a ServiceHistory entity object. Bidirectional navigation is more convenient for writing business validation logic, so in practice, you will typically leave these default checkbox settings.

    Figure 6-7 Association Properties Control Runtime Behavior

    Image shows step 4 of the Association Properties page

6.3.1.1 Changing Entity Association Accessor Names

You should consider the default settings for the accessor names on the Association Properties page and decide whether changing the names to something more intuitive is appropriate. These define the names of the accessor attributes you will use at runtime to programmatically access the entities on the other side of the relationship. By default, the accessor names will be the names of the entity object on the other side. Since the accessor names on an entity must be unique among entity object attributes and other accessors, if one entity is related to another entity in multiple ways then the default accessor names are modified with a numeric suffix to make the name unique. For example, the ServiceRequest entity is associated once to the User entity to represent the user who created the request, and a second time to reflect the technician to whom the service request is assigned for resolution. The default accessor names on the ServiceRequest entity would be User and User1. By opening the respective Association Properties page for the associations in question, you can rename these accessors to more intuitive names like CreatedByUser and TechnicianAssigned.

6.3.1.2 Renaming and Moving Associations to a Different Package

Since the names of the default associations are not easy to understand, one of the first tasks you might want to perform after creating entity objects from tables is to rename them to something more meaningful. Furthermore, since associations are a component that you typically configure at the outset of your project and don't really visit frequently thereafter, you might want to move them to a different package so that your entity objects are easier to see. Both renaming components and moving them to a different package is straightforward using JDeveloper's refactoring functionality. To move a set of business components to a different package, select one or more of the components in the Application Navigator and choose Refactor > Move... from the context menu. To rename a component, select it in the navigator and choose Refactor > Rename from the context menu.

When you refactor ADF Business Components, JDeveloper automatically moves any XML and/or Java files related to the components, as well as updating any other components that might reference them. Figure 6-8 shows what the Application Navigator would look like after renaming all of the associations and moving them to the devguide.model.entities.associations package. While you can refactor the associations into any package names you choose, picking a subpackage like this keeps them logically related to the entities but allows collapsing the package of associations to avoid seeing them when you don't need to.

Figure 6-8 Application Navigator After Association Refactoring

Image of Application Navigator after association refactoring

6.3.2 What Happens When You Create an Association

When you create an association, JDeveloper creates an appropriate XML component definition file and saves it in the directory that corresponds to the name of its package. If you created an association named ServiceHistoriesForServiceRequest in the devguide.model.entities.associations package, then the association XML file will be created in the ./devguide/model/entities/associations directory with the name ServiceHistoriesForServiceRequest.xml. At runtime, the entity object uses the association information to automate working with related sets of entities.

6.3.3 What You May Need to Know About Composition Associations

{para}?>When you create composition associations, it is useful to know about the kinds of relationships you can represent, and the various options.

Associations between entity objects can represent two styles of relationships depending on whether the source entity:

  • References the destination entity

  • Contains the destination entity as a logical, nested part

As shown in Figure 6-9, in your SRDemo application business layer you have a ServiceRequest that references a Product, the requesting User, and the assigned User (a technician). These relationships represent the first kind of association, reflecting that a User or a Product entity object can exist independently of a ServiceRequest. In addition, the removal of a ServiceRequest does not imply the cascade removal of the Product to which it was referring or the related Users.

In contrast, the relationship between ServiceRequest and its collection of related ServiceHistory details is stronger than a simple reference. The ServiceHistory entries comprise a logical part of the overall ServiceRequest. In other words, a ServiceRequest is composed of ServiceHistory entries. It does not make sense for a ServiceHistory entity row to exist independently from a ServiceRequest, and when a ServiceRequest is removed — assuming it is allowed — all of its composed parts should be removed as well.

This type of logical containership represents the second kind of association, called a composition. The UML diagram in Figure 6-9 illustrates the stronger composition relationship using the solid diamond shape on the side of the association which composes the other.

Figure 6-9 ServiceRequest is Composed of ServiceHistory Entries and References Both Product and User

Image shows objects that are part of ServiceRequest

The Business Components from Tables wizard creates composition associations by default for any foreign keys that have the ON DELETE CASCADE option. Using the Create Association wizard or the Association Editor, to indicate that an association is a composition association, check the Composition Association checkbox on the Association Properties page. An entity object offers additional runtime behavior in the presence of a composition. You'll learn the specifics and the settings that control it in Section 6.6.3.12, "Understanding and Configuring Composition Behavior".

6.4 Creating an Entity Diagram for Your Business Layer

Since your layer of business domain objects represents a key reusable asset to your team, it is often convenient to visualize it using a UML model. JDeveloper supports easily creating a diagram for your business domain layer that you and your colleagues can use for reference.

6.4.1 How to Create an Entity Diagram

To create a diagram of your entity objects, use the Create Business Components Diagram dialog. You access it from the New Gallery in the Business Tier > ADF Business Components category. The dialog prompts you for a diagram name, and a package name in which the diagram will be created. Enter a diagram name like "Business Domain Objects" and a name for the package like devguide.model.design, and click OK to create the empty diagram.

To add your existing entity objects to the diagram, select them all in the Application Navigator and drop them onto the diagram surface. Use the property inspector to hide the package name, change the font, turn off the grid and page breaks, and display the name of the two associations that might have been otherwise ambiguous. The diagram should now look like what you see in: Figure 6-10:

Figure 6-10 UML Diagram of Business Domain Layer

Image of UML Diagram of Business Domain Layer

6.4.1.1 Publishing the Business Entity Diagram

To publish the diagram to PNG, JPG, SVG, or compressed SVG format, choose Publish Diagram... from the context menu on the diagram surface.

6.4.2 What Happens When You Create an Entity Diagram

When you create a business components diagram, JDeveloper creates an XML file representing the diagram in a subdirectory of the project's model path that matches the package name in which the diagram resides. For the Business Domain Objects diagram in Figure 6-10, it would create a matching *.oxd_bc4j file in the ./devguide/model/design subdirectory of the model path. By default, the Application Navigator unifies the display of the project contents paths so that ADF components and Java files in the source path appear in the same package tree as the UML model artefacts in the project model path. However, as shown in Figure 6-11, using the Toggle Directories toolbar button on the navigator, you can see the distinct project content path root directories when you prefer.

Figure 6-11 Toggling the Display of Separate Content Path Folders

Image of toggling folder sorting in Application Navigator

6.4.3 What You May Need to Know About Creating Entities On a Diagram

6.4.3.1 UML Diagram is Actively Synchronized with Business Components

The UML diagram of business components is not just a static picture that reflects the point in time when you dropped the entity objects onto the diagram. Rather, it is a UML-based rendering of the current component definitions, so it will always reflect the current state of affairs. What's more, the UML diagram is both a visualization aid and a visual navigation and editing tool. You can bring up the Entity Object Editor for any entity object in a diagram by selecting Properties... from the context menu (or double-clicking). You can also perform some entity object editing tasks directly on the diagram like renaming entities and entity attributes, as well as adding or removing attributes.

6.4.3.2 UML Diagram Adds Extra Metadata to XML Component Descriptors

When you include a business component like an entity object a UML diagram, JDeveloper adds extra metadata to a Data section of the component's XML component descriptor as shown in Example 6-1. This is additional information is used at design time only.

Example 6-1 Additional UML Metadata Added to an Entity Object XML Descriptor

<Entity Name="ServiceRequest" ... >
   <Data>
      <Property Name ="COMPLETE_LIBRARY" Value ="FALSE" />
      <Property Name ="ID"
                Value ="ff16fca0-0109-1000-80f2-8d9081ce706f::::EntityObject" />
      <Property Name ="IS_ABSTRACT" Value ="FALSE" />
      <Property Name ="IS_ACTIVE" Value ="FALSE" />
      <Property Name ="IS_LEAF" Value ="FALSE" />
      <Property Name ="IS_ROOT" Value ="FALSE" />
      <Property Name ="VISIBILITY" Value ="PUBLIC" />
   </Data>
   :
</Entity>

6.5 Defining Attribute Control Hints

With your basic business domain layer of entity objects in place, you can immediately add value by defining UI control hints to ensure that your domain data gets displayed consistently to your end users in locale-sensitive way. JDeveloper manages storing the hints in a way that is easy to localize for multilingual applications. This section explores how to define label text, tooltip, and format mask hints for entity object attributes. As you'll see in Chapter 7, "Building an Updatable Data Model With Entity-Based View Objects", the UI hints you define on your business domain layer are automatically inherited by any entity-based view objects.

6.5.1 How to Add Attribute Control Hints

To add attribute control hints to an entity object, open the Entity Object Editor and expand the Attributes node in the left-side panel to reveal the list of the entity's attributes. Figure 6-12 shows what this would look like for the ServiceRequest entity object. Selecting a particular attribute name like RequestDate and selecting the Control Hints tab, you can set its:

  • Label Text hint to "Requested On"

  • Tooltip Text hint to "The date on which the service request was created"

  • Format Type to Simple Date

  • Format mask of MM/dd/yyyy HH:mm

You can select the other attributes in turn to define appropriate control hints for them as well.

Note:

Java defines a standard set of format masks for numbers and dates that are different from those used by the Oracle database's SQL and PL/SQL languages. For reference, see the Javadoc for the java.text.DecimalFormat and java.text.SimpleDateFormat classes.

Figure 6-12 Setting UI Control Hints for Label for Format Mask for Entity Object Attributes

Image of Setting UI Control Hints in Entity Object Editor

6.5.2 What Happens When You Add Attribute Control Hints

When you define attribute control hints for an entity object, JDeveloper creates a standard Java message bundle file in which to store them. The file is specific to the entity object component to which its related, and it is named accordingly. For the ServiceRequest entity in the devguide.model.entities package, the message bundle file created will be named ServiceRequestImplMsgBundle.java and it will be created in the devguide.model.entities.common subpackage. By selecting the ServiceRequest component in the Application Navigator, you'll see that this new file gets added to the Sources folder in the Structure window that shows the group of implementation files for each component. Example 6-2 shows how the control hint information appears in the message bundle file. The first entry in each String array is a message key, the second entry is the locale-specific String value corresponding to that key.

Example 6-2 Entity Object Component Message Bundle Class Stores Locale-Sensitive Control Hints

package devguide.model.entities.common;
import oracle.jbo.common.JboResourceBundle;
// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---------------------------------------------------------------------
public class ServiceRequestImplMsgBundle extends JboResourceBundle {
  static final Object[][] sMessageStrings = {
  { "AssignedDate_FMT_FORMAT", "MM/dd/yyyy HH:mm" },
  { "AssignedDate_FMT_FORMATTER", "oracle.jbo.format.DefaultDateFormatter" },
  { "AssignedDate_LABEL", "Assigned On" },
  { "AssignedTo_LABEL", "Assigned To" },
  { "CreatedBy_LABEL", "Requested By" },
  { "ProblemDescription_DISPLAYWIDTH", "60" },
  { "ProblemDescription_LABEL", "Problem" },
  { "RequestDate_FMT_FORMAT", "MM/dd/yyyy HH:mm" },
  { "RequestDate_FMT_FORMATTER", "oracle.jbo.format.DefaultDateFormatter" },
  { "RequestDate_LABEL", "Requested On" },
  { "RequestDate_TOOLTIP", 
    "The date on which the service request was created" },
  { "Status_LABEL", "Status" },
  { "SvrId_LABEL", "Request" }
  };
// etc.

6.5.3 Internationalizing the Date Format

Internationalizing the model layer of an application built using ADF Business Components entails producing translated versions of each component message bundle file. For example, the Italian version of the ServiceRequestImplMsgBundle message bundle would be a class named ServiceRequestImplMsgBundle_it and a more specific Swiss Italian version would have the name ServiceRequestImplMsgBundle_it_ch. These classes typically extend the base message bundle class, and contain entries for the message keys that need to be localized, together with their localized translation.

For example, the Italian version of the ServiceRequest entity object message bundle would look like what you see in Example 6-3. Notice that in the Italian translation, the format masks for the RequestDate and AssignedDate have been changed to dd/MM/yyyy HH:mm. This ensures that an Italian user will see a date value like May 3rd, 2006, as 03/05/2006 15:55, instead of 05/03/2006 15:55, which the format mask in the default message bundle would produce. Notice the overridden getContents() method. It returns an array of messages with the more specific translated strings merged together with those that are not overridden from the superclass bundle. At runtime, the appropriate message bundles are used automatically, based on the current user's locale settings.

Example 6-3 Localized Entity Object Component Message Bundle for Italian

package devguide.model.entities.common;
import oracle.jbo.common.JboResourceBundle;
public class ServiceRequestImplMsgBundle_it 
       extends ServiceRequestImplMsgBundle {
  static final Object[][] sMessageStrings = {
    { "AssignedDate_FMT_FORMAT", "dd/MM/yyyy HH:mm" }, 
    { "AssignedDate_LABEL", "Assegnato il" }, 
    { "AssignedTo_LABEL", "Assegnato a" }, 
    { "CreatedBy_LABEL", "Aperto da" }, 
    { "ProblemDescription_LABEL", "Problema" }, 
    { "RequestDate_FMT_FORMAT", "dd/MM/yyyy HH:mm" },
    { "RequestDate_LABEL", "Aperto il" }, 
    { "RequestDate_TOOLTIP", "La data in cui il ticket è stato aperto" },
    { "Status_LABEL", "Stato" }, 
    { "SvrId_LABEL", "Ticket" }
  };
  public Object[][] getContents() {    return super.getMergedArray(sMessageStrings, super.getContents());  }
}

6.6 Configuring Declarative Runtime Behavior

The entity object offers numerous declarative features to simplify implementing typical enterprise business applications. Depending on your task, sometimes the declarative facilities alone may satisfy your needs. However, when you need to go beyond the declarative behavior to implement more complex business logic or validation rules for your business domain layer, that is possible as well. This chapter focuses on giving you a solid understanding of the declarative features. In Chapter 9, "Implementing Programmatic Business Rules in Entity Objects", you'll study some of the most typical ways that you extend entity objects with custom code.

6.6.1 How To Configure Declarative Runtime Behavior

To configure the declarative runtime behavior of an entity object, use the Entity Object Editor. You access the editor by selecting an entity in the Application Navigator and choosing Edit from the context menu. Figure 6-13 shows what the editor looks like for the ServiceRequest entity object.

On the Name page, you can see the entity object's name and configure the database table to which it relates. On the Attributes page, you create or delete the attributes that represent the data relevant to an entity object. By expanding this node, you can access the properties of each of the entity object's attributes.

On the Tuning page, you set options to make database operations more efficient when you create, modify, or delete multiple entities of the same type in a single transaction. On the Publish page, you define events that your entity object can use to notify others of interesting changes in its state, optionally including some or all of the entity object's attributes in the delivered event. On the Subscribe page, you enroll your entity object to be notified when selected events of other entity objects fire. On the Authorization page, you define role-based updatability permissions for any or all attributes. And finally, on the Custom Properties page, you can define custom metadata you can access at runtime on the entity.

Figure 6-13 Use the Entity Object Editor to Configure Its Declarative Features

Image of Entity Object Editor setting declarative features

Note:

If your entity has a long list of attribute names, there's a quick way to find the one you're looking for. With the Attributes node in the tree expanded, you can begin to type the letters of the attribute name and JDeveloper performs an incremental search to take you to its name in the tree.

6.6.2 What Happens When You Configure Declarative Runtime Behavior

All of the declarative settings that describe and control an entity object's runtime behavior are stored in its XML component definition file. When you modify settings of your entity using the editor, pressing OK updates the component's XML definition file and optional custom java files. If you need to immediately apply changes and continue working in the editor, use the Apply button. Applying changes while editing is typically useful only when you enable the generation of a custom Java file for the component for the first time and you want JDeveloper to generate those files before you open another page in the editor.

6.6.3 About the Declarative Entity Object Features

Since much of the entity object's declarative functionality is related to the settings of its attributes, this section covers the important options shown in Figure 6-13 in detail.

6.6.3.1 Legal Database and Java Data types for an Entity Object Attribute

The Persistent property controls whether the attribute value corresponds to a column in the underlying table, or whether it is just a transient value. If the attribute is persistent, the Database Column area lets you change the name of the underlying column that corresponds to the attribute and indicate its column type with precision and scale information (e.g. VARCHAR2(40) or NUMBER(4,2)). Based on this information, at runtime the entity object enforces the maximum length and precision/scale of the attribute value, throwing an exception if a value does not meet the requirements.

Both the Business Components from Tables wizard and the Create Entity Object wizard automatically infer the Java type of each entity object attribute from the SQL type of the database column type of the column to which it is related. The Attribute Type field allows you to change the Java type of the entity attribute to any type you might need. The Database Column Type field reflects the SQL type of the underlying database column to which the attribute is mapped. The value of the Database Column Name field controls the column to which the attribute is mapped.

Your entity object can handle tables with column types, as listed in Table 6-1. With the exception of the java.lang.String class, the default Java attribute types are all in the oracle.jbo.domain and oracle.ord.im packages and support efficiently working with Oracle database data of the corresponding type. The dropdown list for the Java Type field includes a number of other common types that are also supported.

Table 6-1 Default Entity Object Attribute Type Mappings

Oracle Column Type Entity Column Type Entity Java Type

NVARCHAR2(n), VARCHAR2(n), NCHAR VARYING(n), VARCHAR(n)

VARCHAR2

String

NUMBER

NUMBER

Number

DATE

DATE

Date

TIMESTAMP(n), TIMESTAMP(n) WITH TIME ZONE, TIMESTAMP(n) WITH LOCAL TIME ZONE

TIMESTAMP

Date

LONG

LONG

String

RAW(n)

RAW

Raw

LONG RAW

LONG RAW

Raw

ROWID

ROWID

RowID

NCHAR, CHAR

CHAR

String

NCLOB, CLOB

CLOB

ClobDomain

BLOB

BLOB

BlobDomain

BFILE

BFILE

BFileDomain

ORDSYS.ORDIMAGE

ORDSYS.ORDIMAGE

OrdImageDomain

ORDSYS.ORDVIDEO

ORDSYS.ORDVIDEO

OrdVideoDomain

ORDSYS.ORDAUDIO

ORDSYS.ORDAUDIO

OrdAudioDomain

ORDSYS.ORDDOC

ORDSYS.ORDDOC

OrdDocDomain


Note:

In addition to the types mentioned here, you can use any Java object type as an entity object attribute's type, provided it implements the java.io.Serializable interface.

6.6.3.2 Indicating Datatype Length, Precision, and Scale

When working with types that support defining a maximum length like VARCHAR2(n), the Database Column Type field includes the maximum attribute length as part of the value. So, for example, an attribute based on a VARCHAR2(10) column in the database will initially reflect the maximum length of 10 characters by showing VARCHAR2(10) as the Database Column Type. If for some reason you want to restrict the maximum length of the String-valued attribute to fewer characters than the underlying column will allow, just change the maximum length of the Database Column Type value. For example, the EMAIL column in the USERS table is VARCHAR2(50), so by default the Email attribute in the Users entity object defaults to the same. If you know that the actual email addresses are always 8 characters or less, you can update the database column type for the Email attribute to be VARCHAR2(8) to enforce a maximum length of 8 characters at the entity object level.

The same holds for attributes related to database column types that support defining a precision and scale like NUMBER(p[,s]). So, for example, to restrict an attribute based on a NUMBER(7,2) column in the database to have a precision of 5 and a scale of 1 instead, just update the Database Column Type to be NUMBER(5,1).

6.6.3.3 Controlling the Updatability of an Attribute

The Updatable setting controls when the value of a given attribute can be updated. If set to:

  • Always, the attribute is always updatable

  • Never, the attribute is read-only

  • While New, the attribute can be set during the transaction that creates the entity row for the first time, but after being successfully committed to the database the attribute is read-only

6.6.3.4 Making an Attribute Mandatory

The Mandatory property controls whether the field is required.

6.6.3.5 Defining the Primary Key for the Entity

The Primary Key property indicates whether the attribute is part of the key that uniquely identifies the entity. Typically, you will use a single attribute for the primary key, but multiattribute primary keys are fully supported.

At runtime, when you access the related Key object for any entity row using the getKey() method, this Key object contains the value(s) of the primary key attribute(s) for the entity object. If your entity object has multiple primary key attributes, the Key object contains each of their values. It is important to understand that these values appear in the same relative sequential order as the corresponding primary key attributes in the entity object definition.

For example, the ServiceHistory entity object has multiple primary key attributes SvrId and LineNo. On the Attributes page of the Entity Object Editor, SvrId is first, and LineNo is second; an array of values encapsulated by the Key object for a entity row of type ServiceHistory will have these two attribute values in exactly this order. The reason why it is crucial to understand this point is that if you try to use findByPrimaryKey() to find an entity with a multiattribute primary key, and the Key object you construct has these multiple primary key attributes in the wrong order, the entity row will not be found as expected.

6.6.3.6 Defining a Static Default Value

The Default field specifies a static default value for the attribute. For example, you might set the default value of the ServiceRequest entity object's Status attribute to Open, or set the default value of the User entity object's UserRole attribute to user.

6.6.3.7 Synchronization with Trigger-Assigned Values

If you know that the underlying column value will be updated by a database trigger during insert or update operations, you can check the respective Refresh After Insert or Refresh After Update checkboxes to have the framework automatically retrieve the modified value to keep the entity object and database row in sync. The entity object uses the Oracle SQL RETURNING INTO feature, while performing the INSERT or UPDATE to return the modified column back to your application in a single database round-trip.

Note:

If you create an entity object for a synonym that resolves to a remote table over a DBLINK, use of this feature will give an error at runtime like:
JBO-26041: Failed to post data to database during "Update"
## Detail 0 ##
ORA-22816: unsupported feature with RETURNING clause

Section 26.5, "Basing an Entity Object on a Join View or Remote DBLink" describes a technique to circumvent this database limitation.

6.6.3.8 Trigger-Assigned Primary Key Values from a Database Sequence

One common case where Refresh After Insert comes into play is a primary key attribute whose value is assigned by a BEFORE INSERT FOR EACH ROW trigger. Often the trigger assigns the primary key from a database sequence using PL/SQL logic similar to this:

CREATE OR REPLACE TRIGGER ASSIGN_SVR_ID
BEFORE INSERT ON SERVICE_REQUESTS FOR EACH ROW
BEGIN
 IF :NEW.SVR_ID IS NULL OR :NEW.SVR_ID < 0 THEN
   SELECT SERVICE_REQUESTS_SEQ.NEXTVAL
     INTO :NEW.SVR_ID
     FROM DUAL;
   END IF;
END;

Set the Attribute Type to the built-in datatype named DBSequence, as shown in Figure 6-14, and the primary key will be assigned automatically by the database sequence. Setting this datatype automatically enables the Refresh After Insert property.

When you create a new entity row whose primary key is a DBSequence, a unique negative number gets assigned as its temporary value. This value acts as the primary key for the duration of the transaction in which it is created. If you are creating a set of interrelated entities in the same transaction, you can assign this temporary value as a foreign key value on other new, related entity rows. At transaction commit time, the entity object issues its INSERT operation using the RETURNING INTO clause to retrieve the actual database trigger-assigned primary key value. Any related new entities that previously used the temporary negative value as a foreign key will get that value updated to reflect the actual new primary key of the master.

Note:

As shown in Figure 6-14, you will typically also set the Updatable property of a DBSequence-valued primary key to Never. The entity object assigns the temporary ID, and then refreshes it with the actual ID value after the INSERT option. The end user never needs to update this value.

Figure 6-14 Setting Primary Key Attribute to DBSequence Type Automates Trigger-Assigned Key Handling

Image shows setting DBsequence type

Note:

The sequence name shown on the Sequence tab only comes into play at design time when you use the Create Database Tables... feature described in Section 6.2.6, "Creating Database Tables from Entity Objects". The sequence indicated here will be created along with the table on which the entity object is based.

6.6.3.9 Lost Update Protection

At runtime the framework provides automatic "lost update" detection for entity objects to ensure that a user cannot unknowingly modify data that another user has updated and committed in the meantime. Typically this check is performed by comparing the original values of each persistent entity attribute against the corresponding current column values in the database at the time the underlying row is locked. If an entity object detects that it would be updating a row that is now inconsistent with the current state of the database, it raises the RowInconsistentException.

You can make the lost update detection more efficient by identifying an attribute of your entity whose value you know will get updated whenever the entity gets modified. Typical candidates include a version number column or an updated date column in the row. The change indicator attribute value might be assigned by a database trigger you've written and refreshed in the entity object using the Refresh After Insert and Refresh After Update properties. Alternatively, you can indicate that the entity object should manage updating the change indicate attribute's value using the history attribute feature described in the next section. To detect whether the row has been modified since the user queried it in the most efficient way, select the Change Indicator to compare only the change indicator attribute values.

6.6.3.10 History Attributes

Frequently, you'll need to keep track of historical information in your entity object like:

  • Who created this entity?

  • When did they create it?

  • Who last modified this entity?

  • When did they modify it?

  • How many times has this row been modified?

Entity objects store this information in a History Column attribute, as shown in Figure 6-15.

If an attribute's datatype is Number, String, or Date, and it is not part of the primary key, then you can enable this property to have your entity automatically maintain the attribute's value for historical auditing. How the attribute gets handled depends on the history attribute type that you indicate. If you choose the Version Number type of history column, ADF will automatically increment the value of the numeric attribute every time the object is updated. If you choose Created By, Created On, Modified By, or Modified On, the value will be updated with the current user's username or the current date, respectively, when the object is created or modified.

Figure 6-15 Defaulting a Date tor the Current Database Time Using a History Attribute

Image shows selecting value from History column

6.6.3.11 Setting the Discriminator Attribute for Entity Object Inheritance Hierarchies

Sometimes a single database table stores information about several different kinds of logically related objects. For example, a payroll application might work with hourly, salaried, and contract employees all stored in a single EMPLOYEES table with an EMPLOYEE_TYPE column. The value of the EMPLOYEE_TYPE column indicates with a value like H, S, or C, whether a given row represents an hourly, salaried, or contract employee respectively. While many employee attributes and behavior are the same for all employees, certain properties and business logic depends on the type of employee. In this situation it can be convenient to represent these different types of employees using an inheritance hierarchy. Attributes and methods common to all employees can be part of a base Employee entity object, while subtype entity objects like HourlyEmployee, SalariedEmployee, and ContractEmployee extend the base Employee object and add additional properties and behavior. The Discriminator attribute property is used to indicate which attribute's value distinguishes the type of row. Section 26.6, "Using Inheritance in Your Business Domain Layer" explains how to set up and use inheritance.

6.6.3.12 Understanding and Configuring Composition Behavior

When an entity object composes other entities, it exhibits additional runtime behavior to correctly play its role as a logical container of other nested entity object parts. The following features are always enabled for composing entity objects:

6.6.3.12.1 Orphan-row Protection for New Composed Entities

When a composed entity object is created, it performs an existence check on the value of its foreign key attribute to ensure that it identifies an existing entity as its owning parent entity. Failure to provide a value for the foreign key at create time, or providing a value that does not identify an existing entity object, throws an InvalidOwnerException instead of allowing an "orphaned" child row to be created with no well-identified parent entity.

Note:

The existence check performed finds new pending entities in the current transaction, as well as existing ones in the database if necessary.
6.6.3.12.2 Ordering of Changes Saved to the Database

This feature ensures that the sequence of DML operations performed in a transaction involving both composing and composed entity objects is performed in the correct order. For example, an INSERT statement for a new composing parent entity object will be performed before the DML operations related to any composed children.

6.6.3.12.3 Cascade Update of Composed Details from Refresh-On-Insert Primary Keys

When a new entity row having a Refresh On Insert primary key is saved, after its trigger-assigned primary value is retrieved, any composed entities will automatically have their foreign key attribute values updated to reflect the new primary key value.

There are a number of additional composition related features that you can control through settings on the Association Properties page of the Create Association wizard or the Association Editor. Figure 6-16 shows this page for the ServiceHistoriesForServiceRequest association between the ServiceRequest and ServiceHistory entity objects. These settings are the defaults that result from reverse-engineering the composition from an ON DELETE CASCADE foreign key constraint in the database.

Figure 6-16 Composition Settings for ServiceHistoriesForServiceRequest Association

Image shows Association Properties page

The additional features, and the properties that affect their behavior, include the following:

6.6.3.12.4 Cascade Delete Support

You can either enable or prevent the deletion of a composing parent while composed children entities exist. When the Implement Cascade Delete is unchecked, the removal of the composing entity object is prevented if it contains any composed children. When checked, this option allows the composing entity object to be removed unconditionally and composed children entities are also removed. If the related Optimize for Database Cascade Delete option is unchecked, then the composed entity objects perform their normal DELETE statement at transaction commit time to make the changes permanent. If the option is checked, then the composed entities do not perform the DELETE statement on the assumption that the database ON DELETE CASCADE constraint will handle the deletion of the corresponding rows.

6.6.3.12.5 Cascade Update of Foreign Key Attributes When Primary Key Changes

By checking the Cascade Update Key Attributes option, you can enable the automatic update of the foreign key attribute values in composed entities when the primary key value of the composing entity is changed.

6.6.3.12.6 Locking of Composite Parent Entities

Using the Lock Top-Level Container option, you can control whether adding, removing, or modifying a composed detail entity row should attempt to lock the composing entity before allowing the changes to be saved.

6.6.3.12.7 Updating of Composing Parent History Attributes

Using the Update Top-Level History Columns option, you can control whether adding, removing, or modifying a composed detail entity object should update the Modified By and Modified On history attributes of the composing parent entity.

6.7 Using Declarative Validation Rules

One page of the Entity Object Editor worthy of special attention is the Validation page, where you can see and manage the declarative validation rules for the entity or any of its attributes. The framework enforces entity-level validation rules when a user tries to commit pending changes or simply navigates between rows. Attribute-level validation rules are enforced when the user changes the value of the related attribute. When you add a validation rule, you supply an appropriate error message and can later translate it easily into other languages if needed. Oracle ADF ships with a number of built-in declarative validation rules that you'll see in this section. Section 9.3, "Using Method Validators" explains how to use the Method Validator to invoke custom validation code and in Section 26.9, "Implementing Custom Validation Rules" you'll learn how to extend the basic set of declarative rules with custom rules of your own.

6.7.1 How to Add a Validation Rule

To add a validation rule to an entity object, use the Validation page of the Entity Object Editor, as shown in Figure 6-17. To add an attribute-level validation rule, select the attribute in the Declared Validation Rules tree, and click New.... Defining an entity-level validation rule is similar, except that you select the root entity object node in the tree before clicking New....

Figure 6-17 Validation Page of the Entity Object Editor

Image shows Validation Page of the Entity Object Editor

When you add a new validation rule, the Add Validation Rule dialog appears. Use the Rule dropdown list to select the kind of validation rule you want, and configure its declarative settings using the other controls in the page. The controls will change depending on the kind of validation rule you select. Figure 6-18 illustrates what the Add Validation Rule dialog would look like when defining a range validation rule for the ProdId attribute of the ServiceRequest entity object. This validation rule has been selected to enforce that the value lie between 100 and 999 inclusive. When you add a validation rule, you also can enter an error message that will be shown to the user if the validation rule fails.

Figure 6-18 Adding a New Range Validation Rule for the ProdId Attribute

Image shows Add Validation Rule page for ProdId Attribute

6.7.2 What Happens When You Add a Validation Rule

When you add a validation rule to an entity object, JDeveloper updates its XML component definition to include an entry describing what rule you've used and what rule properties you've entered. For example, if you add the range validation rule above to the ProdId attribute, this results in a RangeValidationBean entry in the XML file:

<Entity Name="ServiceRequest"
   <!-- : -->
   <Attribute Name="ProdId" IsNotNull="true" Precision="8" Scale="0"
      ColumnName="PROD_ID" Type="oracle.jbo.domain.Number" 
      ColumnType="NUMBER" SQLType="NUMERIC" TableName="SERVICE_REQUESTS" >
      <RangeValidationBean
         xmlns="http://xmlns.oracle.com/adfm/validation"
         ResId="ProdId_Rule_0" 
         OnAttribute="ProdId"
         OperandType="LITERAL"
         MinValue="100"
         MaxValue="999" >
      </RangeValidationBean>
   </Attribute>
   <!-- : -->
</Entity>

At runtime, the rule is automatically enforced by the entity object based on this declarative information. The error message is a translatable string and is managed in the same way as translatable UI control hints in an entity object message bundle class. The ResId property in the XML component definition entry for the validator corresponds to the String key in the message bundle. Example 6-4 shows the relevant bit of the ServiceRequest entity object's message bundle, where the ProdId_Rule_0 key appears with the error message for the default locale. The validation errors messages get translated using the same mechanism described above for UI control hints.

Example 6-4 Entity Object Message Bundle Contains Validation Error Messages

package devguide.model.entities.common;
import oracle.jbo.common.JboResourceBundle;
// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---------------------------------------------------------------------
public class ServiceRequestImplMsgBundle extends JboResourceBundle {
  static final Object[][] sMessageStrings = {
  // other strings here
  { "ProdId_Rule_0", "Valid product codes are between 100 and 999" },
  // other strings here
  };
  // etc.
}

6.7.3 What You May Need to Know About Validation Rules

{para}?>It is important to know that some validators can be used at the entity level, and some are used on the attribute level. Also, you should be aware the List Validator is designed for working with a relatively small set.

6.7.3.1 Understanding the Built-in Entity-Level Validators

You can use the following built-in validators at the entity object level:

Unique Key Validator

Validates that the primary key for an entity is unique.

Method Validator

Invokes a method in an entity object's custom Java class to evaluate a programmatic validation.

6.7.3.2 Understanding the Built-in Attribute-Level Validators

You can use the following built-in validators at the entity object attribute level:

Compare Validator

Validates an attribute value against:

  • A literal value,

  • A selected attribute of the first row of a view object query result, or

  • The first column of the first row of a SQL query result

List Validator

Validates that an attribute exists in an in-memory set of values from a:

  • Static list,

  • A selected attribute in the rows of view object's default row set, or

  • The first column value in the rows of a SQL query result.

Range Validator

Validates that an attribute lies between a minimum and maximum value, inclusive.

Length Validator

Validates whether the string length of an attribute's value is less than, equal to, or greater than a fixed number of characters.

Regular Expression Validator

Validates that an attribute's value matches a regular expression.

Method Validator

Invokes a method in an entity object's custom Java class to evaluate a programmatic validation.

6.7.3.3 Caveat About the List Validator

The List Validator is designed for validating an attribute against a relatively small set of values. As shown in Figure 6-19, if you select the Query Result or View Object Attribute style of list validation, keep in mind the validator will retrieve all of the rows from the query before performing an in-memory scan to validate whether the attribute value in question matches an attribute in the list. The query performed by the Validator's SQL or view object query does not reference the value being validated in the WHERE clause of the query.

In other words, this is not the feature to use if you want to validate that a user-entered product code exists in a table of a million products. Section 9.6, "Using View Objects for Validation", explains the technique you can use to efficiently perform SQL-based validations by using a view object to perform a targeted validation query against the database.

Figure 6-19 List Validator is Designed for Relatively Small Lists of Values

Image shows selecting a list in the List Validator

6.8 Working Programmatically with Entity Objects and Associations

While external client programs can access an application module and work with any view object in its data model, by design neither UI-based nor programmatic clients work directly with entity objects. In Chapter 7, "Building an Updatable Data Model With Entity-Based View Objects", you'll learn how to easily combine the flexible SQL-querying of view objects with the business logic enforcement and automatic database interaction of entity objects for an incredibly powerful application-building combination. The combination enables a fully updatable application module data model, designed to the needs of the current end-user tasks at hand, that shares the centralized business logic in your reusable domain business object layer.

However, it is important first to understand how view objects and entity objects can be used on their own before learning to harness their combined power. By learning about these objects in greater detail, you will have a better understanding of when you should use them alone and when to combine them in your own applications.

Since clients don't work directly with entity objects, any code you write that works programmatically with entity objects will typically be custom code in a custom application module class or in the custom class of another entity object. This section illustrates examples of working programmatically with entity objects and associations from within custom methods of an application module named SRService in the devguide.model package, manipulating the SRDemo entities you learned how to create earlier in the chapter.

6.8.1 Finding an Entity Object by Primary Key

To access an entity row, you use a related object called the entity definition. At runtime, each entity object has a corresponding entity definition object that describes the structure of the entity and manages the instances of the entity object it describes. After creating an SRService application module in the devguide.model package and enabling a custom Java class for it, imagine you wanted to write a method to return a specific service request's current status. It might look like the retrieveServiceRequestStatus() method like in the SRServiceImpl.java file shown in Example 6-5.

The example breaks down into these basic steps:

  1. Find the entity definition.

    You obtain the entity definition object for the devguide.model.entities.ServiceRequest entity by passing its fully qualified name to the static findDefObject() method on the EntityDefImpl class. The EntityDefImpl class in the oracle.jbo.server package implements the entity definition for each entity object.

  2. Construct a key.

    You build a Key object containing the primary key attribute that you want to look up. In this case, you're creating a key containing the single requestId value passed into the method as an argument.

  3. Find the entity object using the key.

    You use the entity definition's findByPrimaryKey() method to find the entity object by key, passing in the current transaction object, which you can obtain from the application module using its getDBTransaction() method. The concrete class that represents an entity object row is the oracle.jbo.server.EntityImpl class.

  4. Return some of its data to the caller.

    You use the getAttribute() method of EntityImpl to return the value of the Status attribute to the caller.

Example 6-5 Finding a ServiceRequest Entity Object by Key

// Custom method in SRServiceImpl.java
public String findServiceRequestStatus(long requestId) {
  String entityName = "devguide.model.entities.ServiceRequest";
  // 1. Find the entity definition for the ServiceRequest entity
  EntityDefImpl svcReqDef = EntityDefImpl.findDefObject(entityName);
  // 2. Create the key
  Key svcReqKey = new Key(new Object[]{requestId});
  // 3. Find the entity object instance using the key
  EntityImpl svcReq = svcReqDef.findByPrimaryKey(getDBTransaction(),svcReqKey);
  if (svcReq != null) {
    // 4. Return the Status attribute of the ServiceRequest
    return (String)svcReq.getAttribute("Status");
  }
  else {
    return null;
  }
}

Note:

The oracle.jbo.Key object constructor takes an Object array to support creating multiattribute keys, in addition to the more typical single-attribute value keys.

6.8.2 Accessing an Associated Entity Using the Accessor Attribute

In Section 6.3, "Creating and Configuring Associations", you learned that associations enable easy access from one entity object to another. Here's a simple method that helps illustrate what that means in practice. You can add a findServiceRequestTechnician() method that finds a service request, then accesses the associated User entity object representing the technician assigned to the request.

However, since this is the second method in the application module that will be finding a ServiceRequest entity object by ID, you might first want to refactor this functionality into the following retrieveServiceRequestById() helper method that you can then reuse anywhere in the application module that requires finding a service request by ID:

// Helper method in SRServiceImpl.java
private EntityImpl retrieveServiceRequestById(long requestId) {
  String entityName = "devguide.model.entities.ServiceRequest";
  EntityDefImpl svcReqDef = EntityDefImpl.findDefObject(entityName);
  Key svcReqKey = new Key(new Object[]{requestId});
  return svcReqDef.findByPrimaryKey(getDBTransaction(),svcReqKey);
}

Example 6-6 shows the code for findServiceRequestTechnician(). The example follows three basic steps:

  1. Find the ServiceRequest by ID.

    Using the retrieveServiceRequestById() helper method, it retrieves the ServiceRequest entity object by ID.

  2. Access the associated entity using the accessor attribute.

    In Section 6.3.1.1, "Changing Entity Association Accessor Names" above, you renamed the association accessor for the ServiceRequestsAssignedToUser association so that a ServiceRequest entity could access one of its two related User entity objects with the accessor name of TechnicianAssigned. Using the same getAttribute() method used to retrieve any entity attribute value, you can pass in the name of an association accessor and get back the entity object on the other side of the relationship.

  3. Return some of its data to the caller.

    Using the getAttribute() method on the returned User entity, it returns the assigned technician's name by concatenation his first and last names.

Notice that you did not need to write any SQL to access the related User entity. The relationship information captured in the ADF association between the ServiceRequest and User entity objects is enough to allow the common task of data navigation to be automated.

Example 6-6 Accessing an Associated Entity Using the Accessor Attribute

// Custom method in SRServiceImpl.java
public String findServiceRequestTechnician(long requestId) {
  // 1. Find the service request entity
  EntityImpl svcReq = retrieveServiceRequestById(requestId);
  if (svcReq != null) {
    // 2. Access the User entity object using the association accessor attribute
    EntityImpl tech  = (EntityImpl)svcReq.getAttribute("TechnicianAssigned");
    if (tech != null) {
      // 3. Return some of the User entity object's attributes to the caller
      return tech.getAttribute("FirstName")+" "+tech.getAttribute("LastName");
    }
    else {
      return "Unassigned";
    }
  }
  else {
    return null;
  }
} 

6.8.3 Updating or Removing an Existing Entity Row

Once you've got an entity row in hand, it's simple to update it or remove it. You could add a method like the updateRequestStatus() shown in Example 6-7 to handle the job. The example has three simple steps:

  1. Find the ServiceRequest by ID

    Use the retrieveServiceRequestById() helper method to retrieve the ServiceRequest entity object by ID.

  2. Set one or more attributes to new values.

    Use the EntityImpl class' setAttribute() method to update the value of the Status attribute to the new value passed in.

  3. Commit the transaction.

    Use the application module's getDBTransaction() method to accesses the current transaction object and call its commit() method to commit the transaction.

Example 6-7 Updating an Existing Entity Row

// Custom method in SRServiceImpl.java
public void updateRequestStatus(long requestId, String newStatus) {
  // 1. Find the service request entity 
  EntityImpl svcReq = retrieveServiceRequestById(requestId);
  if (svcReq != null) {
    // 2. Set its Status attribute to a new value
    svcReq.setAttribute("Status",newStatus);
    try {
      // 3. Commit the transaction
      getDBTransaction().commit();
    }
    catch (JboException ex) {
      getDBTransaction().rollback();
      throw ex;
    }
  }
}

The example for removing an entity row would be the same as this, except that after finding the existing entity, you would use the following line instead to remove the entity before committing the transaction:

// Remove the entity instead!
svcReq.remove();

6.8.4 Creating a New Entity Row

In addition to using the entity definition for finding existing entity rows, you can also use it to create new ones. Changing focus from service requests to products for a moment, you could write a createProduct()method like the one shown in Example 6-8 to accept the name and description of a new product, and return the new product ID assigned to it. Assume that the ProdId attribute of the Product entity object has been updated to have the DBSequence type discussed in Section 6.6.3.8, "Trigger-Assigned Primary Key Values from a Database Sequence", so that its value is automatically refreshed to reflect the value the ASSIGN_PRODUCT_ID trigger on the PRODUCTS table will assign to it from the PRODUCTS_SEQ sequence in the SRDemo application schema.

The example follows these steps:

  1. Find the entity definition.

    Use EntityDefImpl.findDefObject() to find the entity definition for the Product entity.

  2. Create a new instance.

    Use the createInstance2() method on the entity definition to create a new instance of the entity object.

    Note:

    The method name really has a 2 at the end. The regular createInstance() method has protected access and is designed to be customized by developers as described Section D.2.5, "EntityDefImpl Class" of Appendix D, "Most Commonly Used ADF Business Components Methods". The second argument of type AttributeList is used to supply attribute values that must be supplied at create time; it is not used to initialize the values of all attributes found in the list. For example, when creating a new instance of a composed child entity row using this API, you must supply the value of a composing parent entity's foreign key attribute in the AttributeList object passed as the second argument. Failure to do so results in an InvalidOwnerException.
  3. Set attribute values.

    Use the setAttribute() method on the entity object to assign values for the Name and Description attributes in the new entity row.

  4. Commit the transaction

    Call commit() on the current transaction object to commit the transaction.

  5. Return the trigger-assigned product ID to the caller

    Use getAttribute() to retrieve the ProdId attribute as a DBSequence, then call getSequenceNumber().longValue() to return the sequence number as a long value to the caller.

Example 6-8 Creating a New Entity Row

// Custom method in SRServiceImpl.java
public long createProduct(String name, String description) {
  String entityName = "devguide.model.entities.Product";
  // 1. Find the entity definition for the Product entity
  EntityDefImpl productDef = EntityDefImpl.findDefObject(entityName);
  // 2. Create a new instance of a Product entity
  EntityImpl newProduct = productDef.createInstance2(getDBTransaction(),null);
  // 3. Set attribute values
  newProduct.setAttribute("Name",name);
  newProduct.setAttribute("Description",description);
  try {
      // 4. Commit the transaction
      getDBTransaction().commit(); 
  }
  catch (JboException ex) {
    getDBTransaction().rollback();
    throw ex;
  }
  // 5. Access the database trigger assigned ProdId value and return it
  DBSequence newIdAssigned = (DBSequence)newProduct.getAttribute("ProdId");
  return newIdAssigned.getSequenceNumber().longValue();
}

6.8.5 Testing Using a Static Main Method

At this point, you are ready to test your custom application module methods. One common technique to build testing code into an object is to include that code in the static main() method. Example 6-9 shows a sample main() method you could add to your SRServiceImpl.java custom application module class to test the sample methods you wrote above. You'll make use of the same Configuration object you used in Section 5.7, "How to Create a Command-Line Java Test Client", to instantiate and work with the application module for testing.

Note:

The fact that this Configuration object resides in the oracle.jbo.client package suggests its use for accessing an application module as an application client, and a main() method is a kind of programmatic, command-line client, so this OK. Furthermore, even though it is not best practice to cast the return value of createRootApplicationModule() directly to an application module's implementation class, it's legal to do in this one situation since despite its being a client to the application module, the main() method's code resides right inside the application module implementation class itself.

A quick glance through the code shows that it's exercising the four methods created above to:

  1. Retrieve the status of service request 101

  2. Retrieve the name of the technician assigned to service request 101

  3. Set the status of service request 101 to illegal value "Reopened"

  4. Create a new product supplying a null product name

  5. Create a new product and display its newly assigned product ID

Example 6-9 Sample Main Method to Test SRService Application Module from the Inside

// Main method in SRServiceImpl.java
   public static void main(String[] args) {
     String        amDef = "devguide.model.SRService";
     String        config = "SRServiceLocal";
     ApplicationModule am = 
       Configuration.createRootApplicationModule(amDef,config);
     /* 
      * NOTE: This cast to use the SRServiceImpl class is OK since this
      * ----  code is inside a business tier *Impl.java file and not in a
      *       client class that is accessing the business tier from "outside".
      */
     SRServiceImpl service = (SRServiceImpl)am;
     // 1. Retrieve the status of service request 101
     String status = service.findServiceRequestStatus(101);
     System.out.println("Status of SR# 101 = " + status);
     // 2. Retrieve the name of the technician assigned to service request 101
     String techName = service.findServiceRequestTechnician(101);
     System.out.println("Technician for SR# 101 = " + techName);
     try {
       // 3. Set the status of service request 101 to illegal value "Reopened"
       service.updateRequestStatus(101,"Reopened");
     }
     catch (JboException ex) {
       System.out.println("ERROR: "+ex.getMessage());
     }
     long id = 0;
     try {
       // 4. Create a new product supplying a null product name
       id = service.createProduct(null,"Makes Blended Fruit Drinks");
     }
     catch (JboException ex) {
       System.out.println("ERROR: "+ex.getMessage());
     }
     // 5. Create a new product and display its newly assigned product id
     id = service.createProduct("Smoothie Maker","Makes Blended Fruit Drinks");
     System.out.println("New product created successfully with id = "+id);
     Configuration.releaseRootApplicationModule(am,true);
   }

Running the SRServiceImpl.java class calls the main() method in Example 6-9, and shows the following output:

Status of SR# 101 = Closed
Technician for SR# 101 = Bruce Ernst
ERROR: The status must be Open, Pending, or Closed
ERROR: JBO-27014: Attribute Name in Product is required
New product created successfully with id = 209

Notice that the attempt to set the service request status to "Reopened" failed due to the List Validator failing on the ServiceRequest entity object's Status attribute, shown in Figure 6-17. That validator was configured to allow only values from the static list Open, Pending, or Closed. Also notice that the first attempt to call createProduct() with a null for the product name raises an exception due to the built-in mandatory validation on the Name attribute of the Product entity object.

Note:

You may be asking yourself, "How would a client application invoke the custom service methods I've created in my SRService application module, instead of being called by a main() method in the same class?" You'll learn the simple steps to enable this in Section 8.4, "Publishing Custom Service Methods to Clients". You'll see that it's a straightforward configuration option involving the Client Interface page of the Application Module Editor

6.9 Generating Custom Java Classes for an Entity Object

As you've seen so far in this chapter, all of the database interaction and a large amount of declarative runtime functionality of an entity object can be achieved without using custom Java code. When you need to go beyond the declarative features to implement custom business logic for your entities, you'll need to enable custom Java generation for the entities that require custom code. Appendix D, "Most Commonly Used ADF Business Components Methods", provides a quick reference to the most common code that you will typically write, use, and override in your custom entity object and entity definition classes. Later chapters discuss specific examples of how the SRDemo application uses custom code in its entity classes as well.

6.9.1 How To Generate Custom Classes

To enable the generation of custom Java classes for an entity object, use the Java page of the Entity Object Editor. As shown in Figure 6-20, there are three optional Java classes that can be related to an entity object. While the Entity Collection Class is rarely customized in practice, the Entity Object Class is the most frequently customized, with the Entity Definition Class getting customized less frequently:

  • Entity collection class — rarely customized.

  • Entity object class — the most frequently customized, it represents each row in the underlying database table.

  • Entity definition class — less frequently customized, it represents the related class that manages entity rows and defines their structure.

Figure 6-20 Entity Object Custom Java Generation Options

Image shows Entity Object custom Java generation options

6.9.1.1 Choosing to Generate Entity Attribute Accessors

When you enable the generation of a custom entity object class, if you also select the Accessors checkbox, then JDeveloper generates getter and setter methods for each attribute in the entity object. For the ServiceRequest entity object, the corresponding custom ServiceRequestImpl.java class would have methods like this generated in it:

public Number getSvrId() {...}
public void setSvrId(Number value) {...}
public String getStatus() {...}
public void setStatus(String value) {...}
public Date getRequestDate() {...}
public void setRequestDate(Date value) {...}
public String getProblemDescription() {...}
public void setProblemDescription(String value) {...}
public Number getProdId() {...}
public void setProdId(Number value) {...}
public Number getCreatedBy() {...}
public void setCreatedBy(Number value) {...}
public Number getAssignedTo() {...}
public void setAssignedTo(Number value) {...}
public Date   getAssignedDate() {...}
public void setAssignedDate(Date value) {...}
public ProductImpl getProduct() {...}
public void setProduct(ProductImpl value) {...}
public RowIterator getServiceHistories() {...}
public UserImpl getTechnicianAssigned() {...}
public void setTechnicianAssigned(UserImpl value) {...}
public UserImpl getCreatedByUser() {...}
public void setCreatedByUser(UserImpl value) {...}

These methods allow you to work with the row data with compile-time checking of the correct datatype usage. That is, instead of writing a line like this to get the value of the ProdId attribute:

Number prodId = (Number)svcReq.getAttribute("ProdId");

you can write the code like:

Number prodId = svcReq.getProdId();

You can see that with the latter approach, the Java compiler would catch a typographical error had you accidentally typed ProductCode instead of ProdId:

// spelling name wrong gives compile error
Number prodId = svcReq.getProductCode();

Without the generated entity object accessor methods, an incorrect line of code like the following cannot be caught by the compiler:

// Both attribute name and type cast are wrong, but compiler cannot catch it
String prodId = (String)svcReq.getAttribute("ProductCode");

It contains both an incorrectly spelled attribute name, as well as an incorrectly typed cast of the getAttribute() return value. When you use the generic APIs on the Row interface, which the base EntityImpl class implements, errors of this kind will raise exceptions at runtime instead of being caught at compile time.

6.9.2 What Happens When You Generate Custom Classes

When you select one or more custom Java classes to generate, JDeveloper creates the Java file(s) you've indicated. For an entity object named devguide.model.entities.ServiceRequest, the default names for its custom Java files will be ServiceRequestImpl.java for the entity object class and ServiceRequestDefImpl.java for the entity definition class. Both files get created in the same ./devguide/model/entities directory as the component's XML component definition file.

The Java generation options for the entity object continue to be reflected on the Java page on subsequent visits to the View Object Editor. Just as with the XML definition file, JDeveloper keeps the generated code in your custom Java classes up to date with any changes you make in the editor. If later you decide you didn't require a custom Java file for any reason, unchecking the relevant options in the Java page will cause the custom Java files to be removed.

6.9.3 Seeing and Navigating to Custom Java Files

As with all ADF components, when you select an entity object in the Application Navigator, the Structure window displays all of its related implementation files. The only required file is the XML component definition file. You saw above that when translatable UI control hints are defined for a component, it will have a component message bundle file as well. As shown in Figure 6-21, when you've enabled generation of custom Java classes, they also appear under the Sources folder for the entity object. When you need to see or work with the source code for a custom Java file, there are two ways to open the file in the source code editor:

  • You can choose the relevant Go to option in the context menu, as shown in Figure 6-21

  • You can double-click on a file in the Sources folder in the Structure window

Figure 6-21 Seeing and Navigating to Custom Java Classes for an Entity Object

Image shows context menu in Application Navigator

6.9.4 What You May Need to Know About Custom Java Classes

See the following sections for additional information about custom Java classes.

6.9.4.1 About the Framework Base Classes for an Entity Object

When you use an "XML-only" entity object, at runtime its functionality is provided by the default ADF Business Components implementation classes. Each custom Java class that gets generated will automatically extend the appropriate ADF Business Components base class so your code inherits the default behavior and can easily add or customize it. An entity object class will extend EntityImpl, while the entity definition class will extend EntityDefImpl (both in the oracle.jbo.server package).

6.9.4.2 You Can Safely Add Code to the Custom Component File

Based perhaps on previous negative experiences, some developers are hesitant to add their own code to generated Java source files. Each custom Java source code file that JDeveloper creates and maintains for you includes the following comment at the top of the file to clarify that it is safe to add your own custom code to this file.

// ---------------------------------------------------------------------
// ---    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.
// ---------------------------------------------------------------------

JDeveloper does not blindly regenerate the file when you click the OK or Apply button in the component editor. Instead, it performs a smart update to the methods that it needs to maintain, leaving your own custom code intact.

6.9.4.3 Configuring Default Java Generation Preferences

You've seen how to generate custom Java classes for your view objects when you need to customize their runtime behavior or simply prefer to have strongly typed access to bind variables or view row attributes.

To configure the default settings for ADF Business Components custom Java generation, you can select the Tools | Preferences... menu and open the Business Components page to set your preferences to be used for business components created in the future. Oracle recommends that developers getting started with ADF Business Components set their preference to generate no custom Java classes by default. As you run into a specific need for custom Java code, as you've learned in this section, you can enable just the bit of custom Java you need for that one component. Over time, you'll discover which set of defaults works best for you.

6.9.4.4 Attribute Indexes and InvokeAccessor Generated Code

As you've seen, the entity object is designed to function either in an XML-only mode or using a combination of an XML component definition and a custom Java class. Due to this feature, attribute values are not stored in private member fields of an entity's class since such a class is not present in the XML-only situation. Instead, in addition to a name, attributes are also assigned a numerical index in the entity's XML component definition based on the zero-based, sequential order of the Attribute and association-related AccessorAttribute tags in that file. At runtime attribute values in an entity row are stored in a sparse array structure managed by the base EntityImpl class, indexed by the attribute's numerical position in the entity's attribute list.

For the most part this private implementation detail is unimportant, since as a developer using entity objects you are shielded from having to understand this. However, when you enable a custom Java class for your entity object, this implementation detail is related to some of the generated code that JDeveloper automatically maintains in your entity object class. It is sensible to understand what that code is used for. For example, in the custom Java class for the ServiceRequest entity object, Example 6-10 shows that each attribute or accessor attribute has a corresponding generated integer constant. JDeveloper ensures that the values of these constants correctly reflect the ordering of the attributes in the XML component definition.

Example 6-10 Attribute Constants are Automatically Maintained in the Custom Entity Java Class

public class ServiceRequestImpl extends EntityImpl {
  public static final int SVRID = 0;
  public static final int STATUS = 1;
  public static final int REQUESTDATE = 2;
  public static final int PROBLEMDESCRIPTION = 3;
  public static final int PRODID = 4;
  public static final int CREATEDBY = 5;
  public static final int ASSIGNEDTO = 6;
  public static final int ASSIGNEDDATE = 7;
  public static final int TECHNICIANASSIGNED = 8;
  public static final int CREATEDBYUSER = 9;
  public static final int PRODUCT = 10;
  public static final int SERVICEHISTORIES = 11;
  // etc.

You'll also notice that the automatically maintained, strongly typed getter and setter methods in the entity object class use these attribute constants like this:

// In devguide.model.entities.ServiceRequestImpl class
public Number getAssignedTo() {
  return (Number)getAttributeInternal(ASSIGNEDTO); // <-- Attribute constant
}
public void setAssignedTo(Number value) {
  setAttributeInternal(ASSIGNEDTO, value); // <-- Attribute constant
}

That last aspect of the automatically maintained code related to entity attribute constants are the getAttrInvokeAccessor() and setAttrInvokeAccessor() methods. These methods optimize the performance of attribute access by numerical index, which is how generic code in the EntityImpl base class typically accesses attribute values when performing generic processing. An example of the getAttrInvokeAccessor() method looks like the following from the ServiceRequestImpl.java class. The companion setAttrInvokeAccessor() method looks similar.

// In devguide.model.entities.ServiceRequestImpl class
/** getAttrInvokeAccessor: generated method. Do not modify. */
protected Object getAttrInvokeAccessor(int index,AttributeDefImpl attrDef)
throws Exception {
  switch (index) {
  case SVRID:              return getSvrId();
  case STATUS:             return getStatus();
  case REQUESTDATE:        return getRequestDate();
  case PROBLEMDESCRIPTION: return getProblemDescription();
  case PRODID:             return getProdId();
  case CREATEDBY:          return getCreatedBy();
  case ASSIGNEDTO:         return getAssignedTo();
  case ASSIGNEDDATE:       return getAssignedDate();
  case SERVICEHISTORIES:   return getServiceHistories();
  case TECHNICIANASSIGNED: return getTechnicianAssigned();
  case CREATEDBYUSER:      return getCreatedByUser();
  case PRODUCT:            return getProduct();
  default:
    return super.getAttrInvokeAccessor(index, attrDef);
  }
}

The rules of thumb to remember about this generated attribute-index related code are the following.

The Do's
  • Add custom code if needed inside the strongly typed attribute getter and setter methods.

  • Use the Entity Object Editor to change the order or type of entity object attributes.

    JDeveloper will change the Java signature of getter and setter methods, as well as the related XML component definition for you.

The Don'ts
  • Don't modify the getAttrInvokeAccessor() and setAttrInvokeAccessor() methods.

  • Don't change the values of the attribute index numbers by hand.

Note:

If you need to manually edit the generated attribute constants because of source control merge conflicts or other reasons, you must ensure that the zero-based ordering reflects the sequential ordering of the <Attribute> and <AccessorAttribute> tags in the corresponding entity object XML component definition.

6.9.5 Programmatic Example for Comparison Using Custom Entity Classes

In order to better evaluate the difference of using custom generated entity classes versus working with the generic EntityImpl class, Example 6-11 shows a version of the SRServiceImpl.java methods that you implemented above in a second SRService2Impl.java application module class. A few of the interesting differences to notice are:

  • Attribute access is performed using strongly typed attribute accessors.

  • Association accessor attributes return the strongly typed entity class on the other side of the association.

  • Using the getDefinitionObject() method in your custom entity class avoids working with fully qualified entity definition names as strings.

  • The createPrimaryKey() method in your custom entity class simplifies creating the Key object for an entity.

Example 6-11 Programmatic Entity Examples Using Strongly Typed Custom Entity Object Classes

package devguide.model;
import devguide.model.entities.ProductImpl;
import devguide.model.entities.ServiceRequestImpl;
import devguide.model.entities.UserImpl;

import oracle.jbo.ApplicationModule;
import oracle.jbo.JboException;
import oracle.jbo.Key;
import oracle.jbo.client.Configuration;
import oracle.jbo.domain.DBSequence;
import oracle.jbo.domain.Number;
import oracle.jbo.server.ApplicationModuleImpl;
import oracle.jbo.server.EntityDefImpl;
import oracle.jbo.server.EntityImpl;
// ---------------------------------------------------------------------
// ---    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.
// ---------------------------------------------------------------------
/**
 * This custom application module class illustrates the same
 * example methods as SRServiceImpl.java, except that here
 * we're using the strongly typed custom Entity Java classes
 * ServiceRequestImpl, UserImpl, and ProductImpl instead of working
 * with all the entity objects using the base EntityImpl class.
 */
public class SRService2Impl extends ApplicationModuleImpl {
  /**This is the default constructor (do not remove)
   */
  public SRService2Impl() {
  }
  /*
   * Helper method to return a ServiceRequest by Id
   */
  private ServiceRequestImpl retrieveServiceRequestById(long requestId) {
    EntityDefImpl svcReqDef = ServiceRequestImpl.getDefinitionObject();
    Key svcReqKey = 
      ServiceRequestImpl.createPrimaryKey(new DBSequence(requestId));
    return (ServiceRequestImpl)svcReqDef.findByPrimaryKey(getDBTransaction(),
                                                          svcReqKey);
  }

  /*
   * Find a ServiceRequest by Id
   */
  public String findServiceRequestStatus(long requestId) {
    ServiceRequestImpl svcReq = retrieveServiceRequestById(requestId);
    if (svcReq != null) {
      return svcReq.getStatus();
    }
    return null;
  }
  
  /*
   * Create a new Product and Return its new id
   */
  public long createProduct(String name, String description) {
    EntityDefImpl productDef = ProductImpl.getDefinitionObject();
    ProductImpl newProduct = (ProductImpl)productDef.createInstance2(
                                           getDBTransaction(),null);
    newProduct.setName(name);
    newProduct.setDescription(description);
    try {
      getDBTransaction().commit();
    }
    catch (JboException ex) {
      getDBTransaction().rollback();
      throw ex;
    }
    DBSequence newIdAssigned = newProduct.getProdId();
    return newIdAssigned.getSequenceNumber().longValue();
  }
  /*
   * Update the status of an existing service request
   */  
  public void updateRequestStatus(long requestId, String newStatus) {
    ServiceRequestImpl svcReq = retrieveServiceRequestById(requestId);
    if (svcReq != null) {
      svcReq.setStatus(newStatus);
      try {
        getDBTransaction().commit();
      }
      catch (JboException ex) {
        getDBTransaction().rollback();
        throw ex;
      }
    }
  }
  
  /*
   * Access an associated Used entity from the ServiceRequest entity
   */  
  public String findServiceRequestTechnician(long requestId) {
    ServiceRequestImpl svcReq = retrieveServiceRequestById(requestId);
    if (svcReq != null) {
      UserImpl tech  = (UserImpl)svcReq.getTechnicianAssigned();
      if (tech != null) {
        return tech.getFirstName()+" "+tech.getLastName();
      }
      else {
        return "Unassigned";
      }
    }
    else {
      return null;
    }
  }  
  // Original main() method generated by the application module editor
  //
  //  /**Sample main for debugging Business Components code using the tester.
  //   */
  //  public static void main(String[] args) {
  //    launchTester("devguide.model", /* package name */
  //      "SRServiceLocal" /* Configuration Name */);
  //  }
   /*
    * Testing method
    */
    public static void main(String[] args) {
      String        amDef = "devguide.model.SRService";
      String        config = "SRServiceLocal";
      ApplicationModule am =
        Configuration.createRootApplicationModule(amDef,config);
      /* 
       * NOTE: This cast to use the SRServiceImpl class is OK since this
       * ----  code is inside a business tier *Impl.java file and not in a
       *       client class that is accessing the business tier from "outside".
       */
      SRServiceImpl service = (SRServiceImpl)am;
      String status = service.findServiceRequestStatus(101);
      System.out.println("Status of SR# 101 = " + status);
      String techName = service.findServiceRequestTechnician(101);
      System.out.println("Technician for SR# 101 = " + techName);
      try {
        service.updateRequestStatus(101,"Reopened");
      }
      catch (JboException ex) {
        System.out.println("ERROR: "+ex.getMessage());
      }
      long id = 0;
      try {
        id = service.createProduct(null,"Makes Blended Fruit Drinks");
      }
      catch (JboException ex) {
        System.out.println("ERROR: "+ex.getMessage());
      }
      id = service.createProduct("Smoothie Maker","Makes Blended Fruit Drinks");
      System.out.println("New product created successfully with id = "+id);
      Configuration.releaseRootApplicationModule(am,true);
    }
}

6.10 Adding Transient and Calculated Attributes to an Entity Object

In addition to having attributes that map to columns in an underlying table, your entity objects can include transient attributes that are value holders or that display values calculated in Java. This section explores a simple example of adding a FullName transient attribute to the Users entity object that calculates its value by concatenating the values of the FirstName and LastName attributes.

6.10.1 How to Add a Transient Attribute

To add a transient attribute to an entity object:

  1. Open the Attributes page in the Entity Object Editor and click the New... button. As shown in Figure 6-22:

  2. Enter a name for the attribute like FullName,

  3. Set the Java Attribute Type like String, and

  4. Deselect the Persistent checkbox

  5. If the value will be calculated, set Updateable to Never

Then click OK to create the attribute.

Figure 6-22 Adding a New Transient Attribute

Adding new transient attribute in New Entity Attribute

6.10.2 What Happens When You Add Transient Attribute

When you add a transient attribute and finish the Entity Object Editor, JDeveloper updates the XML component definition for the entity object to reflect the new attribute. Whereas a persistent entity association looks like this in the XML:

<Attribute
   Name="FirstName"
   IsNotNull="true"
   Precision="30"
   ColumnName="FIRST_NAME"
   Type="java.lang.String"
   ColumnType="VARCHAR2"
   SQLType="VARCHAR"
   TableName="USERS" >
</Attribute>

a transient attribute's Attribute tag looks like this, with no TableName and a ColumnName of $none$:

<Attribute
   Name="FullName"
   IsUpdateable="false"
   IsQueriable="false"
   IsPersistent="false"
   ColumnName="$none$"
   Type="java.lang.String"
   ColumnType="$none$"
   SQLType="VARCHAR" >
</Attribute>

6.10.3 Adding Java Code in the Entity Class to Perform Calculation

A transient attribute is a placeholder for a data value. If you change the Updatable property of the transient attribute to While New or Always, then the end user can enter a value for the attribute. If you want the transient attribute to display a calculated value, then you'll typically leave the Updatable property set to Never and write custom Java code that calculates the value.

After adding a transient attribute to the entity object, to make it a calculated attribute you need to:

  • Enable a custom entity object class on the Java page of the Entity Object Editor, choosing to generate accessor methods

  • Write Java code inside the accessor method for the transient attribute to return the calculated value

For example, after generating the UserImpl.java view row class, the Java code to return its calculated value would reside in the getFullName() method like this:

// Getter method for FullName calculated attribute in UserImpl.java 
public String getFullName() {
  // Commented out original line since we'll always calculate the value
  // return (String)getAttributeInternal(FULLNAME);
      return getFirstName()+" "+getLastName();
}

To ensure that the FullName calculated attribute is reevaluated whenever the LastName or FirstName attributes might be changed by the end user, you can add one line to their respective setter methods to mark FullName as "dirty" whenever either's value is set.

// Setting method for FirstName attribute in UserImpl.java
public void setFirstName(String value) {
  setAttributeInternal(FIRSTNAME, value);
    // Notify any clients that the FullName attribute has changed
    populateAttribute(FULLNAME,null,true, /* send notification */
                                    false, /* markAsChanged     */
                                    false);/* saveCopy          */   
}

and

public void setLastName(String value) {
    setAttributeInternal(LASTNAME, value);
    // Notify any clients that the FullName attribute has changed
    populateAttribute(FULLNAME,null,true, /* send notification */
                                    false, /* markAsChanged     */
                                    false);/* saveCopy          */  
  }