Showing posts with label programmatically. Show all posts
Showing posts with label programmatically. Show all posts

15 Jun 2012

Oracle OpenWorld 2012. Deep Dive into Oracle ADF.

I am happy to invite everybody to attend my session at Oracle OpenWorld 2012.

Session ID: CON5623
Session Title: Deep Dive into Oracle ADF: Advanced Techniques

You are welcome to have a journey from the surface of the declarative approach into the deep of the real-life hands-on programming. The session is completely technical and concentrates on advanced techniques of creating and manipulating ADF objects programmatically on-the-fly.

Looking forward to seeing you in San Francisco!

22 May 2011

Dynamic ADF Train. Adding train stops programmatically.

In this post I'm going to show how to add train stops to ADF train programmatically "on-the-fly". In my use-case I have some ticket-booking application. It has a bounded task flow with train model. At the first stop of the train users input number of passengers and at the following stops they input some passengers' info. The number of stops with passengers' info has to be changed dynamically depending on the value submitted at the first train stop. So, the result of described behaviour should look like this:


The bounded task flow has the following structure:
StartView activity is a page fragment where we input number of passengers and DynamicView activity provides a page fragment to input passenger's info. At the moment we have only one activity for passenger's info and I will add extra activities if the number of passengers is greater than one.
The inputNumberSpinbox in StartView page fragment submits its value to passengersNumber property of some PageFlowScope backing bean and action for the Submit button is a method of the same bean:

public class MainTrain {
    //Extra added train stops
    private List<ActivityId> dynamicStops = new ArrayList<ActivityId>();
    
    //Value of inputNumberSpinbox
    private int passengersNumber = 1;
    
    public String buttonPress(){
        //The number of extra added train stops is greater than needed
        if (passengersNumber <= dynamicStops.size())
            clearExtraStops();
        else //The number of extra added train stops is less than needed        
          if (passengersNumber-1 > dynamicStops.size()) 
              addDynamicStops(); 
        return null;
    }

So, by pressing on Submit button we either add some train stops or clear extra stops depending on the value of  inputNumberSpinbox. We save all added dynamic stops in dynamicStops list. Let's have a look at the clearExtraStops() method:
    private void clearExtraStops() {
        for (int i = dynamicStops.size(); i >= passengersNumber; i--) {
            //Get ActivityId to be removed
            ActivityId removeActivityId =  dynamicStops.get(i-1);

            //Get current train model and remove train stop
            TrainModel trainModel = TrainUtils.findCurrentTrainModel();
            trainModel.getTrainStops().remove(removeActivityId);
            
            //Remove activity from task flow definition
            getTaskFlowDefinition().getActivities().remove(removeActivityId);
            dynamicStops.remove(i-1);
        }                            
    }

The method removes two things: the train stop from the train model and the activity from the task flow definition. The addDynamicStops() method is going to be much more interesting:
private void addDynamicStops() {    
    for (int i = dynamicStops.size(); i < passengersNumber - 1; i++) {
       //Creating new ActivityId
       ActivityId activityId = 
           new ActivityId(getTaskFlowId(), new StringBuilder("DynamicView").append(i).toString()); 

       //The main trick of the post.
       //We consider DynamicView activity as a base for new train stop and new activity
           
       //Get base activity (DynamicView) and its train stop
       Activity baseActivity = getBaseDynamicActivity();
       TrainStopContainer stopContainer = (TrainStopContainer)baseActivity.getMetadataObject();
       TrainStop baseTrainStop = stopContainer.getTrainStop();

       //Create new Activity based on DynamicView but with new ActivityId            
       ActivityImpl activityImpl = new ActivityImpl(baseActivity, activityId);  
       //Add created activity to the task flow definition
       getTaskFlowDefinition().getActivities().put(activityId, activityImpl);

       //Create new train stop based on the DynamicView's train stop
       TrainStopModel trainStopModel = new TrainStopModel(
                          new TrainStopImpl(baseTrainStop, i+2), activityId);
       //Add created train stop to the train stop model
       TrainModel trainModel = TrainUtils.findCurrentTrainModel();
       trainModel.getTrainStops().put(activityId, trainStopModel);             
       //Add created activity to our list
       dynamicStops.add(activityId); 
    }
}
    
private Activity getBaseDynamicActivity() {
   ActivityId baseActivityId = new ActivityId(getTaskFlowId(), "DynamicView");   
   MetadataService metadataService = MetadataService.getInstance();
   return metadataService.getActivity(baseActivityId); 
}

private TaskFlowDefinition getTaskFlowDefinition() {
    MetadataService metadataService = MetadataService.getInstance();
    return metadataService.getTaskFlowDefinition(getTaskFlowId());        
}


private TaskFlowId getTaskFlowId() {
    ControllerContext controllerContext = ControllerContext.getInstance(); 
    ViewPortContext currentViewPortCtx = controllerContext.getCurrentViewPort(); 
    TaskFlowContext taskFlowCtx = currentViewPortCtx.getTaskFlowContext(); 
    return taskFlowCtx.getTaskFlowId();
}

So, the principal trick of this post is to create new activity and train stops basing on existing ones for DynamicView. In order to implement the idea I created two classes: ActivityImpl and TrainStopImpl. The classes are nothing else than just proxy classes implementing Activity and TrainStop interfaces correspondently. They delegates interface implementation to the base instances except some specific methods like getters for Id and DisplayName:

public class TrainStopImpl implements TrainStop {    
    //Base instance 
    private TrainStop baseTrainStop;
    
    private int mpassNo;
    private static final String PASSANGER_FORM = "Passenger's data: ";
    
    public TrainStopImpl(TrainStop trainStop, int passNo) {
       baseTrainStop = trainStop; 
       mpassNo = passNo;
    }

    //Specific implementation
    public String getDisplayName() {
        return new StringBuilder(PASSANGER_FORM).append(mpassNo).toString();
    }

    public String getOutcome() {
        return baseTrainStop.getOutcome();
    }

    public String getSequential() {
        return baseTrainStop.getSequential();
    }

...

public class ActivityImpl implements Activity {
    private Activity baseActivity;
    private ActivityId mid;
    
    public ActivityImpl(Activity activity, ActivityId id) {
        baseActivity = activity;
        mid = id;
    }

    //Specific implementation
    public ActivityId getId() {
        return mid;
    }

    public String getType() {
        return baseActivity.getType();
    }

    public Object getMetadataObject() {
        return baseActivity.getMetadataObject();
    }
...
 

And one more picture for this post, just to show it's working:

That's all! You can download sample application for JDeveloper 11.1.1.2.0.

2 May 2011

Adding Groovy validation expression programmatically

In this post I'm going to show how you can add Groovy validation rule for an entity's attribute programmatically "on-the-fly". When you build Groovy expression to validate the value of an attribute, you can use predefined keywords "newValue" and "oldValue" referring to the old and new values of the attribute correspondingly.
In the following piece of code we can see how to add Groovy validation expression for some attribute checking that new value of the attribute is not greater than 20:

    //Groovy validation expression
    private static final String VALIDATION_EXPR="newValue <= 20";
    
    //Keyword in the message bundles resource file
    //exprvalue_err=Value must not be greater than {0}   
    private static final String EXPR_VALUE_ERROR="exprvalue_err";    
    private static final int EXPR_MAX_VALUE=20;    
    
    private void addExpressionValidator(AttributeDef at) {
        //creating new validator
        JboExpressionValidator jcv = new JboExpressionValidator(false, VALIDATION_EXPR);
        
        //setting error message
        jcv.setErrorMsgId(EXPR_VALUE_ERROR);
        
        //setting value for the message's token {0}
        HashMap errvaluesMap = new HashMap();
        errvaluesMap.put("0", EXPR_MAX_VALUE);         
        jcv.setErrorMsgExpressions(errvaluesMap);                
        
        //adding validator to the attribute's validators list
        ((AttributeDefImpl) at).addValidator(jcv);
    }


That was really easy!

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.