HOWTO: Implementing a View Object with Multiple Updateable Dependent Entity Objects

How To Implement a View Object with Multiple Updateable Dependent Entity Objects

An Oracle JDeveloper How To Document
November 2002

Product Versions

This document was written for Oracle9i JDeveloper versions 9.0.2 and 9.0.3.

Contents

        Overview
        Setting Up the Tables for the Sample Project
        Setting Up Components to Represent the Business Domain Objects
        Setting Up Components for the Application Data Model
        Overriding a Few Framework Methods to Finish the Job
                Controlling of the Posting Order of Employee and Department Entity Instances
                Pre-Populating the Foreign Key Attribute in New Employee Instances
                Refreshing Foreign Key Values in New Employees Once Final Department Id is Assigned
                Testing and Debugging the Application Module With Diagnostics On
                Dealing with a Final Issue For the Create Case
                Deciding How Update and Delete Should Work
        Conclusion

NOTE:

This is one of many BC4J-related HowTo articles that you can find at /products/jdev/howtos/index.html


Overview

BC4J makes most common application-building use cases easy without writing any "plumbing" code by implementing many helpful J2EE design patterns for you. In particular, BC4J view objects easily handle updateable join queries involving data from multiple entity objects, and using declarative properties you can indicate how each entity object involved in the view object's query should behave. For typical cases, BC4J's automatic coordination of view object rows and underlying entity object instances handles the job just fine, but when your application requires a customized treatment for how view object rows and entity objects collaborate, this article illustrates an example of a common case that can be implemented by strategically overriding a few framework methods.

Setting Up the Tables for the Sample Project

Let's assume we need to build a user interface that allows the user to provide the data for creating both a new department and the first new employee in that department. To simplify this task, we can build a single view object that joins the relevant employee and department data to be created.

We'll work with the standard DEPT table, created using this familiar DDL:

CREATE TABLE DEPT (
DEPTNO NUMBER(2),
DNAME VARCHAR2(14),
LOC VARCHAR2(13),
CONSTRAINT DEPT_PK PRIMARY KEY (DEPTNO));

As well as the EMP table, created like this:

CREATE TABLE EMP (
EMPNO NUMBER(4) NOT NULL,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7, 2),
COMM NUMBER(7, 2),
DEPTNO NUMBER(2),
CONSTRAINT EMPLOYEE_PK PRIMARY KEY (EMPNO),
CONSTRAINT EMPLOYEE_WORKS_IN FOREIGN KEY (DEPTNO) REFERENCES DEPT);

To make the example more interesting, let's create the following two sequences:

CREATE SEQUENCE dept_table_Seq START WITH 41;

and

CREATE SEQUENCE emp_table_seq START WITH 1000;

and add the following two triggers to assign the primary keys for the  DEPT and  EMP tables from their respsective sequence created above. The trigger for the DEPTNO sequence assignment looks like this:

CREATE TRIGGER dept_table_befins
  BEFORE INSERT
  ON dept
  FOR EACH ROW
BEGIN
  -- When using a DBSequence domain, BC4J supplies a temporary
  -- negative id for the DETPNO column during insert
  IF (:new.deptno IS NULL OR :new.deptno < 0) THEN
    SELECT dept_table_seq.nextval
      INTO :new.deptno
      FROM dual;
  END IF;
END;

while the trigger for the  EMP table looks like this:

CREATE OR REPLACE TRIGGER emp_table_befins
  BEFORE INSERT
  ON emp
  FOR EACH ROW
BEGIN
  -- When using a DBSequence domain, BC4J supplies a temporary
  -- negative id for the EMPNO column during insert
  IF (:new.empno IS NULL OR :new.empno < 0) THEN
    SELECT emp_table_seq.nextval
      INTO :new.empno
      FROM dual;
  END IF;
END;

With the tables setup, let's move on to create the sample project.

Setting Up Components to Represent the Business Domain Objects


NOTE:

The steps described in this paper correspond to the JDeveloper 9.0.3 Production release of BC4J and its design time wizards/editors.


After creating a new empty project in a workspace, use the New Business Component Package... menu choice on the context menu for the project to create a new BC4J package named  otn.howto.multiple.

By using the right-mouse menu on this newly created package, use the BC4J entity object wizards to create an  Employee entity object mapped to the  EMP table. In the "Attribute Settings" panel, change the datatype of the  Empno attribute from  Number to  DBSequence. Using this special domain tells BC4J that you will be assigning your primary key from a database sequence.

Repeat the same steps to create a  Department entity object mapped to the  DEPT table, and as you did with the  Employee entity, change the datatype of the  Deptno attribute from  Number to  DBSequence.

Since employees can exist independently of departments, the BC4J entity objects that model these two business domain concepts should be associated, but not composed. So use the BC4J association wizard to create a new  EmployeeWorksInAssoc association that captures the relationship between the source  Department.Deptno attribute and the destination  Employee.Deptno attribute. On the "Association Properties" panel, edit the name of the destination association accessor from  Employee to the plural  Employees to better reflect that calling the accessor will return a collection of employees. By creating this association, we enable the ability to programmatically navigate from any  Department entity instance to its related collection of  Employee entity instances, and vice versa.

Setting Up Components for the Application Data Model

Next,use the View Object wizard to create a new view object.

  1. On the "Name" panel, give the new view object the name  CreateDepartmentAndFirstEmployee.
  2. On the "Entity Objects" panel of the wizard, select the  Department entity and "shuffle" it into the selected list.
  3. Repeat to add the  Employee entity.

    With the focus still on the selected  Employee entity in the "Selected" list, notice the two checkboxes in the bottom right of the dialog. For the second and subsequent entities that participate in a view object's result "row", these checkboxes are defaulted to be "Read-Only" and for "Reference". This is based on the most frequent usage pattern in application building, but enabling the ability to have multiple, updateable entities in a view row is as simple as unchecking these two boxes.

  4. Since we are building a view object to create both department and its first employee, uncheck the "Read-Only" checkbox as well as the "Reference" checkbox. This will make both the Department-related attributes and the Employee-related attributes in the new view row updateable.
  5. Continue on to the "Attributes" panel and select all of the attributes from both entities and "shuffle" them into the select list of attributes for the view object.
  6. On the "Attribute Settings" panel, select the  Deptno1 attribute (that was automatically renamed to avoid colliding with the  Deptno attribute from the Department entity) and edit its current name to change it to  DeptnoForEmp for better readability.
  7. We're done, so click (Finish) to create the new view object.

Finally, use the Application Module Wizard to create a new application module named  TestModule, and add an instance (or usage) of the  CreateDepartmentAndFirstEmployee view object component to the data model. Use the "Instance Name" field at the bottom right to change the name of the this view object instance from " CreateDepartmentAndFirstEmployee1" to convenient (and shorter!) name like " test" for the test we're about to do. Just like we have freedom to name the member variables in a class anything we want, we can name the "member instances" of view objects in an application module any convenient names we want. Again just like class member variable names, these view object instance names give us a handy local name, in the context of a particular application module, to refer to an instance of our view object (definition) that we want to work with at runtime.

Overriding a Few Framework Methods to Finish the Job

If  Department and  Employee entities were related by a composition association, and if we were creating the new  Department entity through a  DepartmentView that was viewlinked to a separate  EmployeeView, we would be done here since in that case BC4J automatically handles all of the details of:

  1. Coordinating the posting order of composed entities

    to insure that a containing parent is inserted before its contained children to avoid any database-level constraint violations,

  2. Populating contained child entities foreign key attributes on creation

    to avoid creating "orphaned" child entity instances, and

  3. Refreshing the foreign key values of contained child entities for DBSequence-valued primary keys

    to synchronize the transition from temporary in-memory key values to the final database-assigned key values.

However our  CreateDepartmentAndFirstEmployee example requires some customization of the default framework behavior because we have two differences from the situation that BC4J can handle automatically:

  1. our  Department and  Employee entities are not composed, but just associated, and
  2. we're creating both parent and related child entities in a single view object instead of in two separate view objects.

Controlling of the Posting Order of Employee and Department Entity Instances

The first issue we will tackle is controlling the posting order. We want to make sure that any new  Employee entities being created are posted to the database only after their associated  Department entity instance gets inserted. This will guarantee that we won't ever receive a database constraint violation that would be raised if a new row in the EMP table were to be inserted before its "parent" row in the DEPT table. To insure that this happens, we override the  postChanges() framework method in the  EmployeeImpl.java class for the  Employee entity object like this:

  /** [In file: EmployeeImpl.java]
   *
   * Force new DepartmentImpl instance to post before new EmployeeImpl
   * instance if Employee has a related Department
   */
  public void postChanges(TransactionEvent e) {
    // If this entity instance is NEW (meaning it's going to get inserted)
    if (getPostState() == STATUS_NEW) {
      // Get the associated Dept instance
      DepartmentImpl departmentForThisEmployee = getDepartment();
      // If the associated Dept instance is still NEW, post it first
      if (departmentForThisEmployee != null &&
          departmentForThisEmployee.getPostState() == STATUS_NEW) {
        departmentForThisEmployee.postChanges(e);
      }
    }
    super.postChanges(e);
  }

The code is straightforward. It checks to see if the current Employee instance being posted has the status of  STATUS_NEW, which means it was newly created in the current transaction and is about to be inserted into the underlying table. If we're inserting a new entity, we access its related Department entity instance (if it has one) using the  getDepartment() association accessor. If this returns a non-null value, it will be an instance of the  DepartmentImpl class, which we can then use to check whether its post-state is also  STATUS_NEW. If it is, then we explicitly call the  postChanges() method on this  Department instance to post it first. Then, we call  super.postChanges() to post the current  Employee entity being processed by the BC4J framework's post-cycle coordination.

Pre-Populating the Foreign Key Attribute in New Employee Instances

When a view object's query result "row" includes data from multiple entity objects, the act of creating a new row in the view object automatically results in creating new instances of any of the updateable entity objects participating in that row. If we want to customize anything about how a view object row is created, we just need to:

  • Choose to use a View Row class for our view object (which by default won't get created), and
  • Override the  create() method in that View Row class to modify the row creation behavior as desired.

Start by editing the  CreateDepartmentAndFirstEmployee view object, and visiting the Java panel. Check the "View Row Class" checkbox, and press (Finish). This will add a skeleton  CreateDepartmentAndFirstEmployeeRowImpl.java file to your project, nested beneath the  CreateDepartmentAndFirstEmployee view object component in the navigator.

We override the framework  create() method as shown in the following code:

  /** [In file: CreateDepartmentAndFirstEmployeeRowImpl.java]
   *
   * By default, the framework will automatically create the new
   * underlying entity object instances that are related to this
   * view object row being created.
   *
   * We override this default view object row creation to explicitly
   * pre-populate the new (detail) EmployeeImpl instance using the
   * new (master) DepartmentImpl instance. Since all entity objects
   * implement the AttributeList interface, we can directly pass the
   * new DepartmentImpl instance to the EmployeeImpl create() method
   * that accepts an AttributeList.
   */
  protected void create(AttributeList attributeList) {
    // The BC4J framework will already have created "blank" entity instances
    DepartmentImpl newDepartment = getDepartment();
    EmployeeImpl     newEmployee = getEmployee();
     try {
        // Let department "blank" entity instance to do programmatic defaulting
        newDepartment.create(attributeList);
        // Let employee "blank" entity instance to do programmatic defaulting
        // passing in new DepartmentImpl instance so its attributes are
        // available to the EmployeeImpl's create method.
        newEmployee.create(newDepartment);
     }
     catch (JboException ex) {
       newDepartment.revert();
       newEmployee.revert();      
       throw ex;
     }
     catch (Exception otherEx) {
       newDepartment.revert();
       newEmployee.revert();      
       throw new RowCreateException(true         /* EO Row? */,
                                    "Department" /* EO Name */,
                                    otherEx      /* Details */);
     }
  }

However, the above code won't compile until we consciously expose both the  Department entity's  create() method and the  Employee entity's  create() method. The BC4J framework base class for entity objects,  oracle.jbo.server.EntityImpl, has the  create() method with  protected member access. This means that any subclass of  EntityImpl has access to the method, but not other classes by default.

By overriding this base class  create() method in our  otn.howto.multiple.DepartmentImpl subclass, maintaining the  protected access, we enable any other class in our  otn.howto.multiple package access to it. If our view object were in another package, we'd need to "widen" the access of the  create() method to  public.

The overridden  create() method in  DepartmentImpl class looks like this:

  /** [In file: DepartmentImpl.java]
   *
   * Overide create method to extend protected access to
   * other components in this package.
   */
  protected void create(AttributeList attributeList) {
    super.create(attributeList);
  }

For the  Employee entity, our overridden  create() method will be a little more interesting. Since the overridden  create() method at the view object level has been coded to pass in the new  DepartmentImpl instance as a parameter to the  EmployeeImpl's  create() method, we can refer to any attributes in the  DepartmentImpl instance as part of our programmatic defaulting logic for a new  Employee.

Our overridden  create() method below for the  Employee retrieves the  DepartmentImpl's  Deptno attribute as a  DBSequence, and then retrieves the sequence number from that using  getSequenceNumber(). It uses that value to set the foreign key Deptno attribute on the current  EmployeeImpl entity instance.

  /** [In file: EmployeeImpl.java]
   *
   * Retrieve the DBSequence-valued "Deptno" Attribute from the new
   * DepartmentImpl instance passed-in, and pre-populate the employee's
   * "Deptno" foreign key attribute value with the (initially
   * temporary) value of the DepartmentImpl's Deptno attribute value.
   *
   * The overridden refreshFKInNewContainees() method in DepartmentImpl
   * combined with our overridden postChanges() method here will cooperate
   * to force the new department to post first, and proactively refresh
   * this temporary foreign key value to the final (database-trigger-assigned)
   * sequence number.
   */
  protected void create(AttributeList attributeList) {
    super.create(attributeList);
    DBSequence deptno = (DBSequence)attributeList.getAttribute("Deptno");
    if (deptno != null) {
      setDeptno(deptno.getSequenceNumber());
    }
  }

This takes care of the automatic population of the foreign key attribute. The last step is handling the refreshing of the foreign key attribute on  Employee when the database-assigned Deptno value on Department gets refreshed from the temporary value to the definitive sequence number.

Refreshing Foreign Key Values in New Employees Once Final Department Id is Assigned

Here is an overview of our strategy to implement the refreshing of foreign key values in new  Employee entity instances when a new  Department entity instance gets its temporary primary key refreshed by the database-sequence-assigned value:

  1. For any new Department, just before posting itself we will call the  getEmployees() to hold onto the collection of related  EmployeeImpl instances.

    At this point in time, the  EmployeeImpl instances will be related to the  Department based on the temporary primary key that the BC4J framework assigned to the  DBSequence-valued  Department.Deptno attribute at entity creation time. We'll implement this by overriding the framework method  postChanges() and then calling  super.postChanges() to have the framework do its default behavior after we cache a copy of an iterator to the children employees.

  2. For any related employees, update their temporary foreign key value to the final value of the database-assigned  Department primary key.

    To accomplish this, we'll override the framework method  refreshFKInNewContainees() that gets called automatically by the framework to allow a new entity to refresh the foreign key values in its related children. By default, the framework only refreshes children entities that are related by a composition, so since  Employee is just associated but not composed with  Department, we write a little code in this method to do the job ourselves.

The code to accomplish these two steps is shown below:

  /** [In file: DepartmentImpl.java]
   *
   * Used to temporarily cache an iterator to the set of employees in
   * the department before it primary key gets updated
   */
  private RowIterator newEmpsBeforePost = null;
  
  /**
   * Hold onto iterator to existing employees in the Department
   * before calling super.postChanges() that will cause the deptno to change
   * to the database-trigger-assigned sequence number. We hold onto the
   * iterator at this point because if we wait until the
   * refreshFKInNewContainees() method below, calling the getEmployees()
   * method will return no entities since they still have the temporary
   * new department id as their foreign key value.
   */
  public void postChanges(TransactionEvent e) {
    // If this entity instance is NEW (meaning it's going to get inserted)
    if (getPostState() == STATUS_NEW) {
      newEmpsBeforePost = getEmployees();
    }
    super.postChanges(e);
  }
  
  /**
   * BC4J framework invokes this method to allow a newly inserted master entity
   * to update its new detail entities. Typically you do not have to override
   * this method because the base implementation in oracle.jbo.server.EntityImpl
   * will automatically handle this refreshing when detail entity instances
   * are related to new "master" entity instances by composition. In this
   * example, Department and Employee are related by association but not
   * by the stronger composition kind of "strong" containment relationship
   * (it's an optional property of an association to be containership)
   *
   * Iterate over the employees in the new department and refresh their
   * foreign key value to be the database-refreshed sequence value.
   */
   protected void refreshFKInNewContainees() {
     Number newDeptno = getDeptno().getSequenceNumber();
     if (newEmpsBeforePost != null) {
       while (newEmpsBeforePost.hasNext()) {
         EmployeeImpl curEmp = (EmployeeImpl)newEmpsBeforePost.next();
         curEmp.setDeptno(newDeptno);
       }
       newEmpsBeforePost = null;
     }
   }

With this code, we should be able to use the BC4J Tester to test our application module.

Testing and Debugging the Application Module With Diagnostics On

To test our application module, let's turn on BC4J diagnostic information so that we can observe all of the DML operations that BC4J is doing. To accomplish this:

  • Double-click on the project node to bring up the Project Properties dialog.
  • Click on the "Runner" node at the left to show the "Runner" panel
  • In the "Java Options" text box, add the Java VM command-line argument:  -Djbo.debugoutput=console

    This tells the Java VM to set the  jbo.debugoutput System parameter to the value console. The BC4J framework will notice this setting and dump diagnostic information to  System.out, which will conveniently appear in the JDeveloper log window.

Let's also stop the action at a breakpoint so we can observe what's happening under the covers at runtime. Set a breakpoint on the following line in the overridden  refreshFKInNewContainees() method in the  DepartmentImpl.java source file by clicking on its line number in the left margin of the code editor:

     if (newEmpsBeforePost != null) {

A small red "ball" icon should appear to show that you've set the breakpoint.

To run the BC4J Tester in debug mode so that it picks up our Java VM parameters to enable diagnostics and will stop at our breakpoint, do the following:

  1. Expand the node in the System Navigator for the  TestModule application module, to reveal its implementation files.
  2. Right-mouse on the  TestModuleImpl.java file and select Debug TestModuleImpl... from the context menu.

You should see the initial diagnostics information like this in the Log window:

Debugger connected to local process.
Diagnostics: (BC4J Bootstrap) Routing diagnostics to standard output (use -Djbo.debugoutput=silent to remove)
[00] Diagnostic Properties: Timing:false Functions:false Linecount:true Threshold:6
[01] CommonMessageBundle (language base) being initialized
[02] Stringmanager using default locale: 'null'
  :
[158] Successfully logged in
[159] JDBCDriverVersion: 9.0.1.4.0
[160] DatabaseProductName: Oracle
[161] DatabaseProductVersion: Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production

When the BC4J Tester appears, select the view object instance named  test, and double-click it to show the first row in its query result.

Click the (+) button in the toolbar to create a new row in this view object. You should immediately see the effect of the DBSequence-valued primary key attributes for the Deparment and Employee entity objects by observing the temporary primary key values in the  Deptno and  Empno fields. You should also be able to see the result of our overridden  create() method on the  CreateDepartmentAndFirstEmployeeRowImpl view object row class by noticing that the foreign key attribute named  DeptnoForEmp is in sync with the temporary  Dept.Deptno value. BC4J intentionally assigns negative numbers as temporary primary key values since Oracle database sequences cannot produce negative numbers. This avoids any possible conflict between temporary keys and their final, database-sequence-assigned values.

Enter a new value for the  Dname attribute and the  Ename attribute, and press the Commit button in the toolbar. It's the button with the clockwise, green arrow on it.

The debugger should stop at your breakpoint, and you can step though the code that updates the foreign key attribute in the new employee entity instance to the refreshed database-sequence-assigned value. After you let the code run to completion, you can observe the INSERT statements in the Log window that the BC4J diagnostics print out for your inspection.

Dealing with a Final Issue For the Create Case

To witness one problem that's left to deal with, repeat the above steps in the tester of creating a new row, but before setting any value for Dname or Ename (or any other attribute other than the automatically assigned keys), immediately press the Commit button. You will get a DMLException raised because the framework has tried to insert the new  Employee entity instance without trying to insert the new  Department instance. Let's understand why...

One subtle issue to understand is the difference between the  STATUS_INITIALIZED and the  STATUS_NEW for newly created entity object instances. In a nutshell, in JDeveloper 9.0.3, the BC4J framework introduced the ability to distinguish between a newly created entity which had only defaulted values for its attributes, and an entity which has had at least one of its attributes modified by a client application. These two states are reflected by the two entity states  STATUS_INITIALIZED and  STATUS_NEW, respectively. Only when an entity instance has the status  STATUS_NEW does the framework add it to the list of entities that need to be inserted at the next database posting cycle that gets initiated.

When an entity instance is created, it will automatically have any of its design-time default values assigned to the attributes, if any were specified. In addition, the framework method  create() in an entity object provides a "hook" for doing programmatic defaulting of attributes for newly created entity instances as well. In the second testing example above, the new  Department instance still had the  STATUS_INITIALIZED since we never set any of its attributes other than the framework-assigned temporary key. This means the framework didn't consider the new  Department instance as ready for inserting yet. In the first testing example, we did provide a value for the  Dname attribute, so the entity was transitioned to  STATUS_NEW and everything worked correctly.

We have a few possible choices to solve the problem:

  1. We could mark the  Dname attribute of the  Department entity as mandatory by editing the entity and setting this attribute property.

    In practice, a department without a name is probably not what we want, so this is the easiest solution. This would force the user to provide a value for this required attribute before it would be allowed to be posted successfully. Since they would have to set a value, the new  Department entity's status would be  STATUS_NEW, and the insert would work fine.

  2. We can force the new  Department entity instance to have  STATUS_NEW in our overridden  create() method at the entity object level.

    This would be appropriate if we wanted new  Department entities to always be inserted even if only defaulted attributes are provided. This behavior would be consistent in every view object in which this entity participates.

    To implement this, we would augment the  DepartmentImpl class'  create() method to look like this:

      /** [In file: DepartmentImpl.java]
       *
       * Overide create method to extend protected access to
       * other components in this package.
       */
      protected void create(AttributeList attributeList) {
        super.create(attributeList);
        // Setting Deptno to itself forces entity to be STATUS_NEW instead of STATUS_INITIALIZED
        setDeptno(getDeptno());    
      }
  3. We can force the new  Department entity instance to have  STATUS_NEW in our overridden  create() method at the view object level.

    This would be appropriate if we wanted to force the  STATUS_NEW behavior on the  Department entity instance only when created through this particular view object. We've already overridden this method to implement the rest of the behavior we're studying in this example, so doing this would be one additional line of code that, for example, set the value of the  Deptno to itself.

    To implement this approach, we would augment the overridden  create() method on the  CreateDepartmentAndFirstEmployeeRowImpl class to look like this:

         try {
            // Let department "blank" entity instance to do programmatic defaulting
            newDepartment.create(attributeList);

            // Setting Deptno to itself force entity to be STATUS_NEW instead of STATUS_INITIALIZED
            newDepartment.setDeptno(newDepartment.getDeptno());    

            // Let employee "blank" entity instance to do programmatic defaulting
            // passing in new DepartmentImpl instance so its attributes are
            // available to the EmployeeImpl's create method.
            newEmployee.create(newDepartment);
         }

With this last subtlety of the "create" case taken care of, we should really investigate two final issues:

  • How should this view object work for deletes?
  • How should this view object work for updates?

Deciding How Update and Delete Should Work

By default, if multiple entity usages in the view object are marked as updateable (that is, not Read-Only and not Reference) then all of the typical CRUD operations are still supported: create, update, and delete. We've seen above that the create case required some extra care when the multiple updateable entities are dependent and require foreign-key coordination. But what about the update and delete cases?

The delete case is the easiest to deal with. If you want to prevent deletion in this view object, simply override the remove() method at the view object row level and throw an exception like this:

  /** [In file: CreateDepartmentAndFirstEmployeeRowImpl.java]
   *
   * Deletes are not allowed in this view
   */
  public void remove() {
    throw new JboException("Deletion is not allowed in this view");
  }

The update case also does not present any particular problems, unless you don't want to allow updates at all. Using the BC4J tester with the example we have so far, you should be able to query up a row and modify both  Dname and  Ename values in a row of this view object, and commit the changes. BC4J's normal framework behavior will update underlying entities and post the updates appropriately to the necessary database tables.

However, if we want to prevent updates completely, we can override the  setAttributeInternal() framework method at the view object row level to accomplish this. This method is the framework "pinchpoint" for any attribute settings at the view object row level, so its the perfect place to put some customized logic if we need that at the view object level. Of course, we can also choose to override the  setAttributeInternal() method at the entity object object level, too, if what we want to accomplish is applicable to all uses of a particular entity object in any view object. That would be a little draconian for our example here, so let's customize the attribute settability at the view object row level to implement desired behavior.

The first thing that might come to mind might be to repeat the logic we did above to just throw an exception like this:

  /** [In file: CreateDepartmentAndFirstEmployeeRowImpl.java]
   *
   * WRONG
   * ~~~~~
   *
   * Does coding an override like this prevent updates?
   */
  protected void setAttributeInternal(int index, Object val) {
    throw new JboException("Update is not allowed in this view");
  }

However, since all attribute setting goes through this method, doing the above would completely prevent all attribute setting, even for new rows! So this is a little too "strong" an implementation. We need to allow setting attributes when the row in the view object is in the STATUS_NEW or STATUS_INITIALIZED state.

So far we've looked at the status of entity instances, but haven't directly referenced the status of a view object row. This is because BC4J consciously only tracks the status of entity objects in the cache, and not view object rows. This is due to the inherent complexity in establishing a definition for view object row state when your view object involves multiple underlying updateable entity object "parts". For example, if a view object involves three updateable entity usages, a given row of the view object might have Entity1 with STATUS_NEW, Entity2 with STATUS_MODIFIED, and Entity3 with STATUS_UNMODIFIED. In this case, what is the status of this "hybrid" view object row? Not clear in general.

Of course, for any specific scenario that you implement, as the developer "in charge" you can easily decide how you want to handle the meaning of view row status by turning around and looking at the status of the underlying entity instances. In any view object row class, BC4J auto-generates convenience methods that allow you to access the individual, underlying entity object instances that comprise the row. We used this ability above in overriding the view object row's  create() method, and we'll use it here to add a custom method  isNewOrInitRow() to our  CreateDepartmentAndFirstEmployeeRowImpl class. The method looks like this:

  /** [In file: CreateDepartmentAndFirstEmployeeRowImpl.java]
   *
   * Return true if both underlying Department entity instance and underlying
   * Employee entity instance are in either the NEW or INITIALIZED status.
   */
  private boolean isNewOrInitRow() {
    return isNewOrInit(getDepartment()) && isNewOrInit(getEmployee());
  }

Which uses  getDepartment() and  getEmployee() to access the underlying entity instances for this view object row, and for simplicity makes use of the following helper method to determine whether each is in either the New or Initialized state:

  /** [In file: CreateDepartmentAndFirstEmployeeRowImpl.java]
   *
   * Return true if the status of the entity passed in is either
   * NEW or INITIALIZED.
   */
  private boolean isNewOrInit(EntityImpl e) {
    byte status = e.getPostState();    
    return status == Entity.STATUS_NEW || status == Entity.STATUS_INITIALIZED;
  }

With these methods in place, we can now code our overridden  setAttributeInternal() method with the proper conditional behavior:

  /** [In file: CreateDepartmentAndFirstEmployeeRowImpl.java]
   *
   * Only allow attribute setting in rows that are new or initialized state
   */
  protected void setAttributeInternal(int index, Object val) {
    if (isNewOrInitRow()) {
      super.setAttributeInternal(index, val);
    }
    else {
      throw new JboException("Update is not allowed in this view");
    }    
  }

This will stop any attribute assignments in this view row for rows that are not new or initialized. For a more customized treatment, you could imagine a more elaborate scheme to check underlying entity status and make a more fine-grained decision about conditional attribute settability. It's just an extension of the simpler technique we've used here.

As a final touch of class, if we want clients to be able to proactively inquire about our view rows per-attribute updateability, since we have implemented a conditional attribute settability scheme above, we should complete the job by overriding the framework method  isAttributeUpdateable() to reflect the same conditional updateability. The simple implementation looks like this:

  /** [In file: CreateDepartmentAndFirstEmployeeRowImpl.java]
   *
   * Give clients who inquire about attribute updateability an accurate
   * answer based on our conditional attribute settability implemented here.
   */
  public boolean isAttributeUpdateable(int index) {
    // If the row is new or initialized, according to our custom
    // definition of view row state, then let the superclass return
    // the updateability of this attribute (might be updateable, might not).
    if (isNewOrInitRow()) {
      return super.isAttributeUpdateable(index);
    }
    // Otherwise, return false to indicate the attribute's not updateable.
    else {
      return false;
    }
  }

Now, we should be able to use the BC4J tester to:

  • Create new rows that insert a new department a new employee,
  • Attempt deletion of a row and be prevented from doing so,
  • Notice that we are prevented from updating attributes in a row that is not new

The BC4J Tester, and all of the BC4J client data-binding technologies, use the  isAttributeUpdateable() to inquire about the updateability of an attribute to render it appropriately on the screen.

Conclusion

We've seen in this article that when your application requires a customized treatment for how view object rows and entity objects coordinate, customizing the default behavior of the requires just a few lines of code, in the right overridden framework methods.

E-mail this page
Printer View Printer View
Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy