BC4J Helper by Sung Im
Thoughts on how BC4J works, code samples, and other helpful techniques
        

Association Consistency on Non-Detail VOs

This memo explains how one can support association consistency on a non-detail VO.  That is, MyVO is not a detail VO but the user would like to see a new row appear in MyVO when a row is inserted into another VO, say MyOtherVO.  This problem is generally addressed in 9.0.5.  However, in 9.0.3 and 9.0.4, it requires custom ViewObject and ViewRow code.  I built a test case to show how it can be done in 9.0.3/4.  Please take a look at the sample below.

 

SAMPLE

Emp EO (on table EMP) has the following attributes:

   entity Emp
   {
      attribute Integer    EmpNum;  /* primary key */
      attribute String     EmpName;
      attribute String     EmpJob;
      attribute Integer    EmpMgr;
      attribute Timestamp  EmpHireDate;
      attribute BigDecimal EmpSal;
      attribute BigDecimal EmpComm;
      attribute Integer    EmpDeptNum;
   }

Two VOs are defined on this EO:

   VO EmpQO  -- maps all EO attrs
   VO si31mtEmpView -- also maps all EO attrs

All these objects are defined in a package named testp.kava.VO4.si31mt.

In the sample app, an instance of EmpQO and si31mtEmpView are created.  (In fact, the two instances could've been on the same View Def).  I customized si31mtEmpView to show the technique.

For si31mtEmpView, custom ViewObject .java file (si31mtEmpViewImpl.java) is generated.  Also, the row .java file (si31mtEmpViewRowImpl.java) is generated.

After edits, si31mtEmpViewImpl.java looks as follows:

===== si31mtEmpViewImpl.java BEGIN =====
package testp.kava.VO4.si31mt;

import com.sun.java.util.collections.Iterator;

import oracle.jbo.Row;
import oracle.jbo.RowSet;
import oracle.jbo.Version;
import oracle.jbo.common.Diagnostic;
import oracle.jbo.server.EntityDefImpl;
import oracle.jbo.server.EntityEvent;
import oracle.jbo.server.EntityImpl;
import oracle.jbo.server.QueryCollection;
import oracle.jbo.server.RowQualifier;
import oracle.jbo.server.ViewObjectImpl;
import oracle.jbo.server.ViewRowImpl;
import oracle.jbo.server.ViewRowSetImpl;

/*-------------------------------------------
   Assumptions:
 
   1. VO based on one EO.
   2. The VO filters rows whose EmpDeptNum = 10.
   3. The View Row class is generated (si31mtEmpViewRowImpl).
 
   Caveats:
 
   1. We walk through the entire entity cache looking for
      new rows.  Thus, this could be expensive.
   2. Care has been taken to ensure that this test case
      will work correctly even for 9.0.5 or later.
      In 9.0.5, executeQuery will insert new rows
      if assoc-consistent is true (even for non-detail VO).
      So, we don't do this row insertion thing if the version
      is >= 9.0.5.
 
-------------------------------------------*/

public class si31mtEmpViewImpl extends ViewObjectImpl
{
   /*-------------------------------------------

      isInExecuteQuery is used to mark whether we're
      in executeQueryForCollection or not.
    
      executeQueryForCollection will find new entity rows
      and possibly create view rows from them.  Then, the
      new view rows are inserted into the row collection.
      This process will call notifyRowInserted.
      If we don't bypass the event firing, rowInserted
      events will fire, which is inappropriate--rowInserted
      should only be fire when a *user action* (not a system
      action) calls insertRow().  So we use isInExecuteQuery
      to bypass event firing.
   -------------------------------------------*/

   private boolean isInExecuteQuery = false;


   public si31mtEmpViewImpl()
   {
   }

   protected void executeQueryForCollection(Object qcObj, Object[] params,

                                            int noUserParams)
   {
      /*-------------------------------------------
         If BC4J version is 9.0.5 or later, use the mechanism provided by
         the framework.
      -------------------------------------------*/

      if (Version.major >= 9 && Version.revision >= 5)
      {
         if (Diagnostic.isOn())
         {
            Diagnostic.println("## 9.0.5 or later -- call super.executeQueryForCollection()");
         }

         /*-------------------------------------------
            In 9.0.5 or later, you use RowQualifier to filter new rows
            inserted through assoc-consistency.
         -------------------------------------------*/
         if (getRowQualifier() == null)
         {
            setRowQualifier(new RowQualifier("EmpDeptNum = 10"));
         }

         super.executeQueryForCollection(qcObj, params, noUserParams);
         return;
      }

      isInExecuteQuery = true;

      super.executeQueryForCollection(qcObj, params, noUserParams);

      if (isAssociationConsistent())
      {
         /*-------------------------------------------
            If assoc-consistent, then we need to find new entity rows,
            create new view rows from them, and insert them.
         -------------------------------------------*/
         QueryCollection qc = (QueryCollection) qcObj;

         int siz = qc.size();

         /*-------------------------------------------
            siz == 0 is a double check to make sure that super.executeQueryCollection()
            didn't insert any rows in the QueryCollection.
         -------------------------------------------*/
         if (siz == 0)
         {
            if (Diagnostic.isOn())
            {
               Diagnostic.println("## Checking entity cache for new rows");
            }

            /* Get all entity rows from the entity cache. */
            EntityDefImpl eDef = getEntityDef(0);
            Iterator allERows = eDef.getAllEntityInstancesIterator(getDBTransaction());

            while (allERows.hasNext())
            {
               EntityImpl eRow = (EntityImpl) allERows.next();
               Integer deptNum = (Integer) eRow.getAttribute("EmpDeptNum");

               /*-------------------------------------------
                  Look for new rows.  Check to see if the EmpDeptNum attr
                  is 10.
               -------------------------------------------*/
               if ((eRow.getPostState() == EntityImpl.STATUS_NEW ||
                    eRow.getPostState() == EntityImpl.STATUS_INITIALIZED) &&
                   deptNum != null && deptNum.intValue() == 10)
               {
                  /*-------------------------------------------
                     Create a new view row and set its entity row with
                     the one that we've just found.
                  -------------------------------------------*/
                  si31mtEmpViewRowImpl newRow = (si31mtEmpViewRowImpl) createRow();

                  /*-------------------------------------------
                     testSetEntity is a method on si31mtEmpViewRowImpl
                     to make setEntity(int, Entity) public.
                  -------------------------------------------*/
                  newRow.testSetEntity(0, eRow);

                  /* Find a good RowSet and insert it. */
                  RowSet[] rowSets = getRowSets();

                  for (int j = 0; j < rowSets.length; j++)
                  {
                     if (rowSets[j] instanceof ViewObjectImpl)
                     {
                        rowSets[j] = getDefaultRowSet();
                     }

                     if (((ViewRowSetImpl) rowSets[j]).getQueryCollection() == qc)
                     {
                        rowSets[j].insertRow(newRow);
                        break;
                     }
                  }
               }
            }  /* end: while (allERows.hasNext()) */
         }  /* end: if (siz == 0) */
      }  /* end: if (isAssociationConsistent()) */
   }


   protected void notifyRowInserted(ViewRowSetImpl vrs, Row viewRow, int rowIndex)
   {
      /*-------------------------------------------
         Skip rowInserted notification if the row was being
         inserted by executeQueryForCollection.
      -------------------------------------------*/
      if (isInExecuteQuery == false)
      {
         super.notifyRowInserted(vrs, viewRow, rowIndex);
      }
   }

   /*-------------------------------------------
      We also need to handle sourceChanged.  Logic is added to
      handle only entity rows whose EmpDeptNum is 10.
   -------------------------------------------*/
  
   public void sourceChanged(EntityEvent event)
   {
      /*-------------------------------------------
         In 9.0.5, the RowQualifier already does the work.
         So, just call super and short-circuit.
      -------------------------------------------*/
      if (Version.major >= 9 && Version.revision >= 5)
      {
         if (Diagnostic.isOn())
         {
            Diagnostic.println("## 9.0.5 or later -- call super.sourceChanged()");
         }

         super.sourceChanged(event);
         return;
      }

      EntityImpl eRow = event.getEntity();
      int eventType = event.getEventType();
      byte eRowPostState = eRow.getPostState();

      if (eventType == EntityEvent.ATTRIBUTE_CHANGE)
      {
         Integer deptNum = (Integer) eRow.getAttribute("EmpDeptNum");

         /* If we have a new row and if its EmpDeptNum is not 10, skip it. */
         if ((eRowPostState == EntityImpl.STATUS_NEW ||
              eRowPostState == EntityImpl.STATUS_INITIALIZED) &&
             (deptNum == null || deptNum.intValue() != 10))
         {
            return;
         }
      }

      super.sourceChanged(event);
   }
}
===== si31mtEmpViewImpl.java END =====

For si31mtEmpViewRowImpl.java, the following method is added:

  public void testSetEntity(int index, oracle.jbo.server.EntityImpl eRow)
  {
    setEntity(index, eRow);
  }



© Copyright 2003 Sung Im. Click here to send an email to the editor of this weblog.
Last update: 7/10/03; 2:28:37 PM.