29 Jan 2011

Using Inheritance in View Controller Layer

Introduction
I like inheritance feature of ADF BC. It allows to create inheritance tree of entity objects and view objects. The feature is good described in Developer's Guide. Using this approach we can build really elegant business model. But how to use it in ViewConroller layer in the same way? How to use inheritance instead of copy-pasting?

Use Case
I have in my database three tables:
  • Deal - contains some common fields of some agreement with some customer
  • Loan - contains some extra fields specific for loan agreements
  • Forex - contains some extra fields specific for forex agreements



Model 
In my model I've created three read-only VO's: 
  • VDeal - selects all fields from Deal table
  • VLoan - extends VDeal. Selects all fields from Deal and Loan tables
  • VForex - extends VDeal. Selects all fields from Deal and Forex tables


ViewController
 Let's create TaskFlow template to work with our model. In real life the taskflow is going to contain number of different activities, but, just to simplify this post, my taskflow consists of one view activity only. FormView activity contains some form to show record from VLoan or VForex. 




Ok. TaskFlow template is created in draft.
Task flows that implements this template are going to attach some real page fragment to the FormView view activity in order to show a record of corresponding deal (Loan or Forex). Obviously, some of fields are common and it's preferable to have the same look-and-feel and UI logic for these fields in every implementation. I'm going to create page fragment template and put all common fields on it .






From the DataControls palette I'm dragging VDeal and dropping it on the page as ADF Read-only Form:
















After adding facet "extendFacet" for pages implementing this template and fixing some "design" issues I got the following page:



Everything seems to be OK. But!!! Let's have a look at the page definition file for our template page. The iterator binding points to VDeal  view object:


    
    
  



Actually VDeal is just ancestor definition. Its instance will hardly be created. Pages implementing our template will have their own real VO instances of VLoan and VForex. To fix the problem I'm going to add some managed bean to my task flow template:


  
    FormView
    
      DealFlowBean
      com.cs.blog.inherit.view.DealFlow
      request
    
    
    
  


The DealFlowBean has method getDealVOName. This method is going to be overridden and  it is responsible to return correct name of the corresponding VO:

package com.cs.blog.inherit.view;

public class DealFlow {
    public DealFlow() {
        super();
    }
    
    /*Extenders override this method and return correct name
     * of the corresponding VO
     * */
    public String getDealVOName() {
        return "VDeal";
    }
}

The next step is to change a little bit page definition file for page template:


    
    
  

I used EL expression to resolve VO's name.




Ok. Let's create taskflow to work with loans:





We have to add manually on the Loan taskflow view activity and give it the same name FormView. We are implementing it by creating new page fragment based on the DealViewTemplate.


We put loan specific extra fields (using drag-n-drop from the Data Controls palette) on the extendFacet of our page:



 After that, we have to change PageDef for the new page fragment: change Binds="VLoan" to Binds="#{DealFlowBean.dealVOName}" and change given by default iterator ID from VLoanIterator to VDealIterator.


  
  
    
    
    
  
  
    
      
        
      
    
    
      
        
      
    
    
      
        
      
    
  


Off-course we have to extend DealFlowBean and override the getDealVOName method.

package com.cs.blog.inherit.view;

public class LoanFlow extends DealFlow {
    public LoanFlow() {
        super();
    }
    
    public String getDealVOName() {
        return "VLoan";
    }
    
}


And we need to define LoanFlow class for DealFlowBean in the definition of our taskflow.


  
    
      /WEB-INF/deal-flow-template.xml
      deal-flow-template
    
    FormView
    
      DealFlowBean
      com.cs.blog.inherit.view.LoanFlow
      request
    
    
      /LoanFormView.jsff
    
    
  



Finishing... Task flow for loans is complete and ready to be used. Using the same approach we create task flow for forex deals.
As a reward for our work we can enjoy the following working(!) pages for loan and forex deals:



 That's it!
 You can download sample application for this post.







23 Jan 2011

ADF BC. Programmatically populated VO example.

Introduction
View objects with rows populated programmatically can be very useful to display data from alternative data sources like PL/SQL procedure's out parameters, Ref Cursors, XML files, ...
In this post I will show how to build view object and display information about PL/SQL procedure's parameters. How to get this information you can see in the previous post ADF BC. PL/SQL procedure params


To create VO with rows populated programmatically you need to select "Rows populated programmatically, not based on query" option in the "Create View Object" wizard:


On the next step of the wizard you have to define attributes of your VO:


After finishing the wizard JDeveloper is generating source ViewObjectImpl code with some methods supposed to be overridden like this:

package com.cs.blog.sproc.model;

import java.sql.ResultSet;

import oracle.jbo.server.ViewObjectImpl;
import oracle.jbo.server.ViewRowImpl;
import oracle.jbo.server.ViewRowSetImpl;
// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---    Tue Jan 04 18:47:17 EET 2011
// ---    Custom code may be added to this class.
// ---    Warning: Do not modify method signatures of generated methods.
// ---------------------------------------------------------------------
public class VStoredProcParams1Imp extends ViewObjectImpl {
    /**
     * This is the default constructor (do not remove).
     */
    public VStoredProcParams1Imp() {
    }

    /**
     * executeQueryForCollection - overridden for custom java data source support.
     */
    protected void executeQueryForCollection(Object qc, Object[] params,
                                             int noUserParams) {
        super.executeQueryForCollection(qc, params, noUserParams);
    }

    /**
     * hasNextForCollection - overridden for custom java data source support.
     */
    protected boolean hasNextForCollection(Object qc) {
        boolean bRet = super.hasNextForCollection(qc);
        return bRet;
    }

    /**
     * createRowFromResultSet - overridden for custom java data source support.
     */
    protected ViewRowImpl createRowFromResultSet(Object qc,
                                                 ResultSet resultSet) {
        ViewRowImpl value = super.createRowFromResultSet(qc, resultSet);
        return value;
    }

    /**
     * getQueryHitCount - overridden for custom java data source support.
     */
    public long getQueryHitCount(ViewRowSetImpl viewRowSet) {
        long value = super.getQueryHitCount(viewRowSet);
        return value;
    }
}

Actually, you have to implement a little bit more methods:

/**
/**
 * Overridden framework method.
 *
 * Wipe out all traces of a built-in query for this VO
 */
protected void create() {
    getViewDef().setQuery(null);
    getViewDef().setSelectClause(null);
    setQuery(null);
}


/**
 * executeQueryForCollection - overridden for custom java data source support.
 */
protected void executeQueryForCollection(Object qc, Object[] params,
                                         int noUserParams) {
    storeNewResultSet(qc, retrieveParamsResultSet(qc, params));
    super.executeQueryForCollection(qc, params, noUserParams);
}

private ResultSet retrieveParamsResultSet(Object qc, Object[] params) {
    ResultSet rs =
        StoredProcParams.getStoredProcParams(getDBTransaction(), (String)getParamValue(PACKAGE_NAME,
                                                                                       params),
                                             (String)getParamValue(PROCEDURE_NAME,
                                                                   params));
    return rs;
}


private Object getParamValue(String varName, Object[] params) {
    if (getBindingStyle() == SQLBuilder.BINDING_STYLE_ORACLE_NAME) {
        if (params != null) {
            for (Object param : params) {
                Object[] nameValue = (Object[])param;
                String name = (String)nameValue[0];
                if (name.equals(varName)) {
                    return nameValue[1];
                }
            }
        }
    }
    throw new JboException("No bind variable named '" + varName + "'");
}


/**
 * Store a new result set in the query-collection-private user-data context
 */
private void storeNewResultSet(Object qc, ResultSet rs) {
    ResultSet existingRs = (ResultSet)getUserDataForCollection(qc);
    // If this query collection is getting reused, close out any previous rowset
    if (existingRs != null) {
        try {
            existingRs.close();
        } catch (SQLException e) {
            throw new JboException(e);
        }
    }
    setUserDataForCollection(qc, rs);
    hasNextForCollection(qc); // Prime the pump with the first row.
}


/**
 * hasNextForCollection - overridden for custom java data source support.
 */
protected boolean hasNextForCollection(Object qc) {
    ResultSet rs = (ResultSet)getUserDataForCollection(qc);
    boolean nextOne = false;
    if (rs != null) {
        try {
            nextOne = rs.next();
            /*
           * When were at the end of the result set, mark the query collection
           * as "FetchComplete".
           */
            if (!nextOne) {
                setFetchCompleteForCollection(qc, true);
                /*
             * Close the result set, we're done with it
             */
                rs.close();
            }
        } catch (SQLException s) {
            throw new JboException(s);
        }
    }
    return nextOne;
}

/**
 * createRowFromResultSet - overridden for custom java data source support.
 */
protected ViewRowImpl createRowFromResultSet(Object qc,
                                             ResultSet resultSet) {
    resultSet = (ResultSet)getUserDataForCollection(qc);


    /*
          * Create a new row to populate
          */
    ViewRowImpl r = createNewRowForCollection(qc);

    if (resultSet != null) {
        try {
            /*
           * Populate new row by attribute slot number for current row in Result Set
           */
            populateAttributeForRow(r, 0,
                                    resultSet.getString("COLUMN_NAME"));
            populateAttributeForRow(r, 1,
                                    resultSet.getString("DATA_TYPE"));
            populateAttributeForRow(r, 2,
                                    resultSet.getString("TYPE_NAME"));
        } catch (SQLException s) {
            throw new JboException(s);
        }
    }
    return r;
}

protected void releaseUserDataForCollection(Object qc, Object rs) {
    ResultSet userDataRS = (ResultSet)getUserDataForCollection(qc);
    if (userDataRS != null) {
        try {
            userDataRS.close();
        } catch (SQLException s) {

        }
    }
    super.releaseUserDataForCollection(qc, rs);
}

/**
 * getQueryHitCount - overridden for custom java data source support.
 */
public long getQueryHitCount(ViewRowSetImpl viewRowSet) {
    return 0;
}


There are two most important methods to focus your attention: retrieveParamsResultSet and createRowFromResultSet.
Method retrieveParamsResultSet actually retrieves data from your alternative datasource. In my case this is some static method supposed to return information about PL/SQL procedure's params represented by ResultSet with three attributes (COLUMN_NAME, DATA_TYPE and TYPE_NAME).
 
Method createRowFromResultSet creates new row and populates attributes of your view object by values of COLUMN_NAME, DATA_TYPE and TYPE_NAME.

In addition I defined two parameters (bind variables) for my VO - packageName and procName (PL/SQL package and procedure names to be described).



I implemented and published (via client interface) some method to set up values for these parameters:

    public void initParamValues(String packageName, String procName) {
        setpackageName(packageName);
        setprocName(procName);
        executeQuery();
    }

I created jspx page and dropped this method as a parameters form and VO as a table. As a result of our work I got something like this (sorry for design):


Download sample application for this post - AppOraStoredProc.zip. It requires connection to standard HR scheme in Oracle database.

22 Jan 2011

ADF BC. PL/SQL procedure params.

Introduction
Some days ago I had a task to get names and types of parameters of some PL/SQL procedure from BC model. I asked Google to help me and was surprised. Almost nothing how to do it. 
Information about PL/SQL procedure's parameters can be very useful to build for example a wrapper to call PL/SQL procedures instead of writing ugly code using JDBC API directly. In this post I will try to put some light in this dark corner.

 We can get description of procudure's parameters using JDBC API DatabaseMetadata interface and it's method getProcedureColumns. The following static method getSoredProcParams prints some information about PL/SQL procedure's parameters:

public static ResultSet getStoredProcParams(DBTransaction dbtransaction,
                                            String packageName,
                                            String procedureName) {
  if (procedureName == null || procedureName.isEmpty())
      return null;
  Connection conn = null;
  ResultSet rs = null;

  //We need this PreparedStatement to get Connection only
  PreparedStatement statement =
      dbtransaction.createPreparedStatement("commit", 1);

  try {

      conn = statement.getConnection();
      String upperPackageName =
          (packageName == null ? null : packageName.toUpperCase());
      rs =
conn.getMetaData().getProcedureColumns(upperPackageName, null, 
                                  procedureName.toUpperCase(),
                                  null);

      while (rs.next()) {
          System.out.println("ParamName = " +
                             rs.getString("COLUMN_NAME"));
          System.out.println("ParamType = " + 
                             rs.getString("DATA_TYPE"));
          System.out.println("ParamTypeName = " +
                             rs.getString("TYPE_NAME"));
          System.out.println(" ");
      }
  } catch (SQLException sqlerr) {
      throw new JboException(sqlerr);
  } finally {
      try {
          if (statement != null) {
              statement.close();
          }
      } catch (SQLException closeerr) {
          throw new JboException(closeerr);
      }
  }

  return rs;
}