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.

8 comments:

  1. I download the application but it is not compile
    can u upload it aging pls

    ReplyDelete
  2. I've just checked. Everything is ok. It can be compiled and it works. What version of JDeveloper do you use? The application is developed for JDeveloper 11.1.1.2.0.

    ReplyDelete
  3. hi Eugene Fedorenko, i also got a compilation error when compile your program.

    In the method addDynamicStops() of MainTrain.java, you create a new instance of TrainStopModel with this piece of code: "TrainStopModel trainStopModel = new TrainStopModel(new TrainStopImpl(baseTrainStop, i+2), activityId);".

    The above piece of code can't compile because

    the constructor of oracle.adfinternal.controller.train.TrainStopModel is "TrainStopModel(oracle.adf.controller.metadata.model.TrainStop p1, oracle.adf.controller.metadata.ActivityId p2) { }".


    However your TrainStopImpl.java class implements "oracle.adf.controller.internal.metadata.TrainStop".

    These two TrainStop classes are from different packages.

    So can u modify ur code and upload again pls.

    ReplyDelete
  4. What version of JDeveloper do you use?

    ReplyDelete
  5. hi, My JDeveloper version is "Studio Edition Version 11.1.2.0.0"

    ReplyDelete
  6. That's the reason. Sample application is developed for JDeveloper 11.1.1.2.0.

    ReplyDelete
  7. Hi Eugene,

    I found this article nicely. Thanks for blogging.

    On the other hand, I would like to ask if its possible to show different pages on each of the dynamic page that you are creating?

    And if its possible, what should be the approach to be use?

    Say Page 1 displays different info,
    Page 2 displays another...
    .
    .
    Page N as another?

    Would like to ask input from you if you are given this task?

    ReplyDelete
  8. Hi!
    Probably this post http://adfpractice-fedor.blogspot.com/2011/12/dynamic-adf-train-showing-train-stops.html will give you the answer.

    ReplyDelete

Post Comment