Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers 10g (10.1.3.1.0) Part Number B25947-01 |
|
|
View PDF |
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.
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.
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.
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.
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
See the following sections for additional information about custom Java classes.
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).
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.
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.
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.
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.
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. |
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); } }