Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers 10g (10.1.3.1.0) Part Number B25947-01 |
|
|
View PDF |
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.
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:
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.
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.
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.
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: Theoracle.jbo.Key object constructor takes an Object array to support creating multiattribute keys, in addition to the more typical single-attribute value keys. |
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:
Find the ServiceRequest by ID.
Using the retrieveServiceRequestById()
helper method, it retrieves the ServiceRequest
entity object by ID.
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.
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; } }
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:
Find the ServiceRequest by ID
Use the retrieveServiceRequestById()
helper method to retrieve the ServiceRequest
entity object by ID.
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.
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();
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:
Find the entity definition.
Use EntityDefImpl.findDefObject()
to find the entity definition for the Product
entity.
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 a2 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 . |
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.
Commit the transaction
Call commit()
on the current transaction object to commit the transaction.
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(); }
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 thisConfiguration 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:
Retrieve the status of service request 101
Retrieve the name of the technician assigned to service request 101
Set the status of service request 101 to illegal value "Reopened"
Create a new product supplying a null product name
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 mySRService 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 |