Showing posts with label ADF Trian. Show all posts
Showing posts with label ADF Trian. Show all posts

22 Jan 2012

ViewObject. Working with multiple RowSets.

Let's say we have a ViewObject with quite "heavy" SQL query from performance point of view. We need to show ViewObject's rows in two different tables on the same page and rows in the tables should be filtered in different ways. For example, we have a query from Employees table and we need to show separately clerks in one table and managers in another one. And let's suppose that the query is very heavy, so it's preferable to be executed once only. It would be nice to retrieve records from database once and after that filter them in memory and show in appropriate tables as many times as we need.
In most cases we work with ViewObjects containing one "Default" RowSet only. ViewObject creates it internally and implementing RowSet interface delegates it's methods to the "Default" RowSet. But ViewObject can support more than one RowSet. Except internally created "Default" RowSet we can create "secondary" RowSets. This feature can be very useful for our usecase. We're going to use "Default" RowSet to retrieve all rows from database and create two "secondary" RowSets (clerks and managers) with filtered in memory rows. These "secondary" RowSets we will show in our tables on the page.

In the model of our sample application we have very simple (just for example) entity based ViewObject EmployeesView with two ViewCiterias - EmployeesViewCriteriaClerk and EmployeesViewCriteriaMAN:




In EmployeesViewImpl we have a method that creates new "secondary" RowSet as a result of filtering in memory rows from main "Default" rowset. It uses one of our ViewCriterias to filter the rows:

    private RowSet getRowSet(String rowSetName, String criteriaName) {
        //Find created secondary rowset by name
        ViewRowSetImpl rs = (ViewRowSetImpl)findRowSet(rowSetName);

        //if not found
        if (rs == null) {
            //Create new rowset as a result of filtering in memory rows
            //from DefaultRowSet using ViewCriteria criteriaName
            rs =
               (ViewRowSetImpl)findByViewCriteria(getViewCriteria(criteriaName), -1,
                                     ViewObject.QUERY_MODE_SCAN_VIEW_ROWS);

            //RowSet is created with autogenerated name like "EmployeesView1_...
            //Let's remove the rowset from VO's rowsets table.           
            removeRowSet(rs);
            //Change rowset's name. 
            rs.setName(rowSetName);
            //And put it back. Next time we'll be able to find it by name. 
            addRowSet(rs);

        }

        return rs;

    }


And two methods-clients of the getRowSet method:

    private static String CLERK_RS = "ClerkRowSet";
    private static String MAN_RS = "ManRowSet";
    private static String CLERK_CRITERIA = "EmployeesViewCriteriaClerk";
    private static String MAN_CRITERIA = "EmployeesViewCriteriaMAN";
    
    //Get RowSet for clerks
    public RowSet getClerkRowSet () {
      return getRowSet(CLERK_RS, CLERK_CRITERIA);
    
    }
    
    //Get RowSet for managers 
    public RowSet getManRowSet () {
      return getRowSet(MAN_RS, MAN_CRITERIA);  
    } 


These two methods should be exposed via client interface to be accessible from the binding layer.

In our View we have the following TaskFlow:


The first activity is a method call, executing query of EmployeesView instance. So, on this step we're going to retrieve rows from the database. In order to retrieve all rows, be sure, that Fetch Mode attribute of the ViewObject is set to FETCH_ALL.


View activity MainView contains two tables for clerks and managers. Let's have a look at its pageDef:
  <executables>
    <variableIterator id="variables"/>

    <methodIterator Binds="getClerkRowSet.result"
                    DataControl="AppModuleDataControl" RangeSize="25"
                    id="getClerkRowsIterator"/>
    
    <methodIterator Binds="getManRowSet.result"
                    DataControl="AppModuleDataControl" RangeSize="25"
                    id="getManRowSetIterator"/>
  </executables>
  <bindings>
   
  
    <tree IterBinding="getClerkRowsIterator" id="EmployeesView11">
      <nodeDefinition DefName="com.cs.blog.testfilterVO.model.EmployeesView">
        <AttrNames>
          <Item Value="EmployeeId"/>
          <Item Value="FirstName"/>
          <Item Value="LastName"/>
          <Item Value="Email"/>
          <Item Value="PhoneNumber"/>
          <Item Value="HireDate"/>
          <Item Value="JobId"/>
          <Item Value="Salary"/>
          <Item Value="CommissionPct"/>
          <Item Value="ManagerId"/>
          <Item Value="DepartmentId"/>
          <Item Value="CreatedBy"/>
          <Item Value="CreatedDate"/>
          <Item Value="ModifiedBy"/>
          <Item Value="ModifiedDate"/>
          <Item Value="ActionComment"/>
        </AttrNames>
      </nodeDefinition>
    </tree>
    <tree IterBinding="getManRowSetIterator" id="EmployeesView12">
      <nodeDefinition DefName="com.cs.blog.testfilterVO.model.EmployeesView">
        <AttrNames>
          <Item Value="EmployeeId"/>
          <Item Value="FirstName"/>
          <Item Value="LastName"/>
          <Item Value="Email"/>
          <Item Value="PhoneNumber"/>
          <Item Value="HireDate"/>
          <Item Value="JobId"/>
          <Item Value="Salary"/>
          <Item Value="CommissionPct"/>
          <Item Value="ManagerId"/>
          <Item Value="DepartmentId"/>
          <Item Value="CreatedBy"/>
          <Item Value="CreatedDate"/>
          <Item Value="ModifiedBy"/>
          <Item Value="ModifiedDate"/>
          <Item Value="ActionComment"/>
        </AttrNames>
      </nodeDefinition>
    </tree>
    <methodAction id="getManRowSet" RequiresUpdateModel="true"
                  Action="invokeMethod" MethodName="getManRowSet"
                  IsViewObjectMethod="true" DataControl="AppModuleDataControl"
                  InstanceName="AppModuleDataControl.EmployeesView1"
                  ReturnName="AppModuleDataControl.methodResults.getManRowSet_AppModuleDataControl_EmployeesView1_getManRowSet_result"/>
    <methodAction id="getClerkRowSet" RequiresUpdateModel="true"
                  Action="invokeMethod" MethodName="getClerkRowSet"
                  IsViewObjectMethod="true" DataControl="AppModuleDataControl"
                  InstanceName="AppModuleDataControl.EmployeesView1"
                  ReturnName="AppModuleDataControl.methodResults.getClerkRowSet_AppModuleDataControl_EmployeesView1_getClerkRowSet_result"/>

  </bindings>

Note, that Instead of Iterators we use methodIterators representing results of methodActions getManRowSet and getClerkRowSet and these methodIterators are set in IterBinding attributes of tree bindings EmployeesView11 and EmployeesView12 that are  used by our tables on the page.

The result of our work looks like this:


You can download sample application for this post. It was designed for JDev 11.1.1.2.0.
That's it!



11 Dec 2011

Dynamic ADF Train. Showing train stops programmatically.

In one of my previous posts I showed how to create train stops programmatically. And I got a comment with a question on the post - "Is it possible to show different pages on each of the dynamic train stop?". The answer is - Yes, off course!
In this post I'm going to give an example of showing train stops programmatically. So, I need to show or hide some stops dynamically at runtime. Everybody knows that TrainStop has Ignore attribute

 
And if we could dynamically change it's value or put there some EL expression that could be evaluated during the taskflow's life cycle, It would be the best approach of showing/hiding train stops at runtime. But Ignore attribute is evaluated only once, at the task flow initialization phase and cannot be modified further.  But! As it was shown in this post we can programmatically add (or remove) train stops to the train model of the task flow. So, we can do it!

Let's say I've got the following train TaskFlow:


PassenerView activity, LuggageView and MealView  by default have <ignore>true</ignore> attribute and at the very beginning, after task flow initialization, are hidden. The train model doesn't have stops for these activities. On the first stop of the task flow I'm going to decide which activity should be included in the train and which one is hidden. On the task flow initialization I call the following managed bean method:

    private static String START_VIEW = "StartView";
    //Fill map with all activities of the TaskFlow
    //except StartView
    private void InitIgnoredStops() {
        for (Iterator it= getTaskFlowDefinition().getActivities().values().iterator(); it.hasNext();) {
          Activity act = (Activity) it.next();
          if (!START_VIEW.equals(act.getIdAttribute())) {
                ignoredStops.put(act.getId(), "false");
            }

        }
        
    }

The ignoredStops map is shown on the StartView page using techniques explained in this post. So, I have the following page:



The following method is called on Submit button's action:

public String buttonPress() {

    TrainModel trainModel = TrainUtils.findCurrentTrainModel();

    //Loop over the map
    for (Object actid : ignoredStops.keySet().toArray()) {
        //If activity is not chosen 
        if (!Boolean.parseBoolean(ignoredStops.get(actid).toString())) {
            // if activity is included in the train then remove it
            if (trainModel.getTrainStops().get(actid) != null)
                 trainModel.getTrainStops().remove(actid);
        } else {
            //If activity is chosen and it is not included in the train 
            // then we need to include it
            if (trainModel.getTrainStops().get(actid) == null) {
                MetadataService metadataService =
                    MetadataService.getInstance();
                
                //Get activity and its train stop definition
                Activity activity =
                    metadataService.getActivity((ActivityId)actid);                
                TrainStopContainer stopContainer =
                        (TrainStopContainer)activity.getMetadataObject();
                TrainStop ts = stopContainer.getTrainStop();
                
                
                //Create new train stop model and add it to the train
                trainModel.getTrainStops().put((ActivityId)actid,
                                                   new TrainStopModel(ts,
                                                                      (ActivityId)actid));
                }
            }


        }
    
}

If needed activities are chosen we can press Submit  button and get the following picture:




That's it!

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.