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

27 Jan 2019

Monitoring an ADF Application in a Docker Container. Easy Way.

In this short post I am going to show a simple approach to make sure that your ADF application running inside a Docker container is a healthy Java application in terms of memory utilization. I am going to use a standard tool JConsole which comes as a part of JDK installation on your computer. If there is a problem (i.e. a memory leak,  often GCs, long GCs, etc.) you will see it with JConsole. In an effort to analyze the root of the problem and find the solution you might want to use more powerful and fancy tools. I will discuss that in one of my following posts. A story of tuning JVM for an ADF application is available here.

So there is an ADF application running on top of Tomcat. The application and the Tomcat are packaged into a Docker container running on dkrlp01.flexagon host. There are some slides on running an ADF application in a Docker container.
In order to connect with JConsole from my laptop to a JVM running inside the container, we need to add the following JVM arguments in tomcat/bin/setenv.sh:
 -Dcom.sun.management.jmxremote=true
 -Dcom.sun.management.jmxremote.rmi.port=9010
 -Dcom.sun.management.jmxremote.port=9010
 -Dcom.sun.management.jmxremote.ssl=false
 -Dcom.sun.management.jmxremote.authenticate=false
 -Dcom.sun.management.jmxremote.local.only=false
 -Djava.rmi.server.hostname=dkrlp01.flexagon

Besides that the container has to expose port 9010, so it should be created with
"docker run -p 9010:9010 ..." command.

Having done that we can invoke jconsole command locally and connect to the container:


Now just give the application some load with you favorite testing tool (JMeter, OATS, SOAP UI, Selenium, etc..) and observe the memory utilization:



That's it!




29 Jun 2018

Oracle Jet vs Oracle ADF or Oracle Jet with Oracle ADF

In this post I would like to thank everyone, who managed to attend my session "Oracle Jet vs Oracle ADF or Oracle Jet with Oracle ADF" at ODTUG KScope18 conference in Orlando FL. Thank you guys for coming to listen to me, to learn something new and to ask a lot of interesting questions. 

I promised at the session that the presentation would be available for download. 
The presentation is available here:


That's it!

17 Nov 2013

Validating dates with af:validateDateTimeRange validator

Sometimes we need to validate that the date entered in an af:inputDate component is within some range. ADF Faces provides a very convenient approach for that - af:validateDateTimeRange. It is very easy to use and it could look like this in a jspx code:
<af:inputDate value="#{dateValue}">
   <af:validateDateTimeRange minimum="#{minimumValue}"
        maximum="#{maximumValue}"
        messageDetailNotInRange="The date value {1} is not in the range {2} - {3}"
        /> 
</af:inputDate>
But there is some pitfall to be aware.

Let's say we've got some ViewObject with three oracle.jbo.domain.Date attributes - ValueDate, MaturityDate and PaymentDate:


We put correspondent inputDate components on a page and set up autoSubmit attribute for ValueDate and MaturityDate:

 
We need to keep the value of PaymentDate within the range between ValueDate and MaturityDate. So, w're going to add a validator to the PaymentDate:

<af:inputDate value="#{bindings.PaymentDate.inputValue}"
              label="#{bindings.PaymentDate.hints.label}"
              required="#{bindings.PaymentDate.hints.mandatory}"
              columns="#{bindings.PaymentDate.hints.displayWidth}"
              shortDesc="#{bindings.PaymentDate.hints.tooltip}"
              id="id3">
  <f:validator binding="#{bindings.PaymentDate.validator}"/>
  <af:convertDateTime pattern="#{bindings.PaymentDate.format}"/>
  
  <af:validateDateTimeRange minimum="#{bindings.Valuedate.inputValue.value}"
                            maximum="#{bindings.Maturitydate.inputValue.value}"
                            />
  
</af:inputDate>


And let's test it now:

The error message is absolutely correct since PaymentDate is less than ValueDate.



The value of PaymentDate is equal to ValueDate and it is within the range ValueDate-MaturityDate. So there is no any validation exception.



Ooops!  The value of PaymentDate is equal to MaturityDate and it is within the range ValueDate-MaturityDate. But the validation exception is fired.
We can figure out the reason of this strange behavior in the specification of the maximum attribute of the af:validateDateTimeRange:

"... When binding to Date objects, it is advised to create the Date object with maximum value for any date-time components that aren't displayed (usually hours, minutes, seconds, milliseconds) to allow the largest range of values to be accepted."

So, in order to get it working correctly, it is advised to specify in the maximum attribute of the validateDateTimeRange something like this: "5/24/2012 23:59:59.999".

Let's give it what it wants. In our ViewObject we created a transient oracle.jbo.domain.Date attribute LimitMaturityDate with the following value expression:

 The expression actually adds a day (24 hours) to the Maturitydate and subtracts a milisecond, so we're going to get "Maturitydate 23:59:59.999". The new java.sql.Timestamp object is going to be converted into the oracle.jbo.domain.Date by the framework. And let's use this transient attribute as a maximum for the af:validateDateTimeRange:
  <af:validateDateTimeRange minimum="#{bindings.Valuedate.inputValue.value}"
                            maximum="#{bindings.LimitMaturityDate.inputValue.value}"
                            />


And if we test it again:


It will work fine.


That's it!

20 Oct 2013

Developing Web Applications with Oracle ADF Essentials

Since Oracle ADF Essentials has been released it changed the mind of Java EE developers across the world. They started to look at ADF as at the serious alternative to a set of heterogeneous frameworks and technologies the used to build Web applications. They realized that this single powerful framework could provide them with almost everything they needed, and absolutely for free!
Nowadays there are a number of books and various resources providing plenty of information about Oracle ADF. But what could be chosen as a starting point?

A few months ago I was proud to be a technical reviewer of a new ADF book Developing Web Applications with Oracle ADF Essentials by Sten Vesterli. The book has been published at the end of August 2013.



Being not a thick book, this resource manages to show the full power of Oracle ADF Essentials on 250 pages only, and the reading is really enjoyable and easy. I would recommend this book as an excellent starting point for those who want to know what Oracle ADF is all about.

That's it! 

29 May 2013

Displaying ADF Task Flow Stack with BreadCrumbs

Let's consider a page with a region running a task flow. The task flow can invoke some internal task flow and this internal task flow can invoke its internal task flow and so on. After a few navigations of that sort our users are going to get lost. They have no idea how deep they are and how they got there. There is a special component in ADF Faces af:breadCrumbs. Usually it is used to show users their path through the application's menu, so users can know how they got to this page and how they can get back.  For example:

Let's use this component to help users in case of a deep task flow stack.In this post I am going to show how we can display the task flow stack with af:breadCrumbs component and how we can use it to stop currently running task flow and allow a user to get back.

So, we have a region:
<af:region value="#{bindings.taskflowdefinition1.regionModel}"
           text="#{RegionBean.taskFlowDisplayName}"
           id="r1"
           />
The region uses the technique described here to display the region's name. And we have af:breadCrumbs on the same page:
<af:breadCrumbs id="bc1" value="#{RegionBean.menuModel}"
                var="task">
    <f:facet name="nodeStamp">
        <af:commandNavigationItem id="comID"
                                  text="#{task.label}"
                                  actionListener="#{RegionBean.commandAction}">
             <f:attribute name="depth" value="#{task.depth}"/>
        </af:commandNavigationItem>
    </f:facet>
</af:breadCrumbs>


The value of the breadCrumbs is going to be some menu model, provided by a managed bean method:
public MenuModel getMenuModel() {

  TaskFlowLink taskFlowLink = getTaskFlowLink();
  if (taskFlowLink!=null)
    return new ChildPropertyMenuModel(taskFlowLink, "child",
            Collections.nCopies(taskFlowLink.getDepth(), 0));
  else
    return null;

}


The method uses some hierarchical data structure represented by TaskFlowLink class and converts it into a menu model using some internal helper class ChildPropertyMenuModel.  The TaskFlowLink class is our custom wrapper of the PageFlowStackEntry internal class. Furthermore, it supports the hierarchical structure by providing the child field.

public class TaskFlowLink {
  TaskFlowLink child;
  PageFlowStackEntry stackEntry;
  int depth;


  public TaskFlowLink(PageFlowStackEntry stackEntry, TaskFlowLink child, int depth) {
      this.stackEntry = stackEntry;
      this.child = child;
      this.depth = depth;
  }

  //Extracting the definition of the task flow 
  //corresponding to the stack entry
  private TaskFlowDefinition getTaskFlowDefinition() {
    MetadataService metadataService = MetadataService.getInstance();
    return metadataService.getTaskFlowDefinition(
                             stackEntry.getTaskFlowDefinitionId());
  }


  public String getLabel() {
      return getTaskFlowDefinition().getDisplayName();
  }

  public int getDepth() {
      return depth;
  }

  public TaskFlowLink getChild() {
      return child;
  }
    
}
  

And the getTaskFlowLink() method converts the page flow stack into the TaskFlowLink structure:
private TaskFlowLink getTaskFlowLink() {
  TaskFlowLink taskFlowLink = null;
  
  //Get the page flow stack for the region's view port
  PageFlowStack pfs = getViewPort().getPageFlowStack();
  
  //Convert the stack into array. Just for convenience. 
  PageFlowStackEntry[] pageFlowStack = 
      pfs.toArray(new PageFlowStackEntry[pfs.size()]);
  
  //Convert the array into the TaskFlowLink structure
  for (int i = pageFlowStack.length-1; i>=0; i--)
      taskFlowLink = new TaskFlowLink(pageFlowStack[i], 
                                      taskFlowLink, 
                                      pageFlowStack.length - i);

  return taskFlowLink;
}


The getTaskFlowLink() method uses a couple of helper methods to get access to the view port:
//Get the task flow binding
private DCTaskFlowBinding getTaskFlowBinding() {  
  BindingContainer bc = BindingContext.getCurrent().getCurrentBindingsEntry();  
  
  //taskflowdefinition1 is Id of the task flow binding in the page def file  
  //like  <taskFlow id="taskflowdefinition1" ...  
  DCTaskFlowBinding dtb = (DCTaskFlowBinding) 
    ((DCBindingContainer) bc).findExecutableBinding("taskflowdefinition1");  
  
  return dtb;  
} 

//Get the view port
private ViewPortContextImpl getViewPort() {
    DCTaskFlowBinding dtb = getTaskFlowBinding();
    return (ViewPortContextImpl) dtb.getViewPort();
}


And we are almost happy:

So, we built a menu model acceptable by the af:breadCrumbs component based on the page flow stack. That's all indeed cool, but it would be better if a user could click on a crumb and return back to the corresponding task flow. For example, clicking on "Task Flow One" I want to abandon currently running "Task Flow Two" and return back to the "Task Flow One". Moreover, I want to return exactly to the same view activity from which I got to the "Task Flow Two".
Alrighty, let's do it! Did you notice that our af:breadCrumbs has a commandNavigationItem within its nodeStamp facet. So, we're going to do something in the commandAction method when we're clicking on the item:
public void commandAction(ActionEvent actionEvent) {
    UIComponent component = actionEvent.getComponent();
    
    //Get the flow's depth in the stack 
    int depth = Integer.valueOf(component.getAttributes().get("depth").toString());
    
    //Abandon all deepper flows and return 
    //to the calling view activity 
    popTaskFlow(depth);
}


And, finally, let's have a look at the popTaskFlow method:
private void popTaskFlow(int depth) {

  //Remember current view port
  AdfcContext adfcContext = AdfcContext.getCurrentInstance();
  ViewPortContextImpl currViewPort = adfcContext.getCurrentViewPort();


  try
  {
     //Set region's view port as a current one
     //This allows task flow's finalizers to work correctly
     ViewPortContextImpl viewPort = getViewPort();
     adfcContext.getControllerState().setCurrentViewPort(adfcContext,
                                                         viewPort.getViewPortId());
     viewPort.makeCurrent(adfcContext);

     PageFlowStack stack = viewPort.getPageFlowStack();
     PageFlowStackEntry entry = null;

     //Abandon all deeper flows
     for (int i=1; i<depth; i++) {
       TASK_FLOW_RETURN_LOGIC.abandonTaskFlow(adfcContext, stack.peek());
       entry = stack.pop(adfcContext);
      }

     //Update the view port's current view activity ID to point 
     //to the view that was displayed before the popped 
     //task flow was called.         
     ActivityId newViewActivityId = entry.getCallingViewActivity();
     viewPort.setViewActivityId(adfcContext, newViewActivityId);

  }
  finally
  {//Restore current view port
   adfcContext.getControllerState().setCurrentViewPort(adfcContext, 
                                                      currViewPort.getViewPortId());
   currViewPort.makeCurrent(adfcContext);
  }

}

private static final TaskFlowReturnActivityLogic TASK_FLOW_RETURN_LOGIC 
   = new TaskFlowReturnActivityLogic();


The sample application for this post is available here. It requires JDeveloper R2.

That's it!

21 Apr 2012

How to avoid validation of immediate inputs

Introduction
Setting the immediate attribute of JSF/ADF command components to true is not the silver bullet that can help you to avoid validation. This is explained in the Understanding the JSF Immediate attribute post. If you have immediate input controls on the same form, these controls are going to be validated in any case. But immediate input controls could be quite useful as it was shown in the Update model in the ValueChangeListener post.  So, let's say you have immediate required inputText and immediate commandButton (some "Cancel" button) on the same form. Leaving the inputText empty will cause a validation error when the commandButton is pressed.


This post is showing a technique of using some "super" Immediate button, that can help us to really avoid validation in any case. So, our "super" Immediate button looks like this:

   <af:commandButton text="Cancel" id="cb1" 
                      actionListener="#{TestBean.buttonActionListener}"        
                      immediate="true"
                      action="someAction">
                      
      <af:clientAttribute name="superImmediate" value="#{true}"/>                
      <af:clientListener method="catchActionEvent" type="action"/>
      <af:serverListener type="customButtonAction"
                         method="#{TestBean.customButtonActionListener}"/>

    </af:commandButton>                   


And a little bit of Java Script:
   <af:resource type="javascript">  
     
     // This hack avoids client-side validation for the "superImmediate"
     // commandComponents
     AdfActionEvent.prototype.isValidationNeeded = function()
       {
         return !this.getSource().getProperty("superImmediate");
       } 
       
    // Action event will cause server-side validation at the
    // Apply Request Values phase (for immediate button).
    // So we'll get validation error even if the client-side validation is suppressed
    // We need to catch the original Action event, cancel it and replace with our
    // custom event customButtonAction.
    function catchActionEvent(evt){
      AdfCustomEvent.queue(evt.getSource(), 'customButtonAction' , null, true); 
      evt.cancel();                  
    }   
    
   </af:resource>


And finally we need to emulate an Action Event in our customButtonActionListener in order to get the button's actionListener executed and the action processed:

  public void customButtonActionListener(ClientEvent clientEvent) {
      UIXCommand cb = (UIXCommand) clientEvent.getComponent();
      cb.broadcast(new ActionEvent(cb));
  }


That's it!

7 Mar 2011

Nested Page Templates

We are provided by quite powerful ADF feature "Page templates". The feature allows us to get common "Look and Feel" of our application, to reuse components, page fragments as well as application logic.
Let's say all pages in my application should have some image header on the top and two buttons (Ok, Cancel) on the bottom. In order to match the requirement I'm going to create page template MaintemplateDef.jspx with the following code:


    
      
        
          
                    
          
          
        
      
      
        
                
      
      
        
        
      
    
    
      
        MaintemplateDef
        
          
            centerFacet
          
        
      
    
  

And it looks like this:

Every page built on this template should provide its specific content within facet "centerFacet".
Ok, let's implement additional requirement - some pages should have space for menu on the left (except header on the top and buttons on the bottom).  We have to complicate our template to match new requirement:

    
      
        
          
          
          
          
        
      
      
        
        
        
          
            
            
          
          
                      
            
          
        
        
        
          
          
                     
        
      
      
        
        
      
    
    
      
        MaintemplateDef
        
          
            centerFacet
          
        
        
          
            menuFacet
          
        
        
          
            centermenuFacet
          
        
        
          
            menuVisible
          
          
            java.lang.Boolean
          
          
            false
          
        
      
    
  
I've added boolean attribute menuVisible and switcher showing panelSplitter with facet for menu. Pages with menu should set attribute menuVisible to true and use facets menuFacet and centermenuFacet. Let's imagine we have one more requirement - some pages should have panelTabbed component instead of left menu. Our page template is going to get more complicated, and it's going to keep complicating with every new requirement. Is it cool? No, it isn't! Some more requirements and our page template will be like a mess. It'll be much more difficult to make any changes and support this template. To resolve the problem I wish I had a possibility to create templates of templates or "nested templates".  And actually we have got it!

This is impossible to create nested page template declaratively with JDeveloper. But we can do it manually! And it works! So, let's go back to the first implementation of our template and create new page template in usual way. Let's call it LeftMenutemplateDef.jspx. We will get the following simple code:

    
      
        LeftMenutemplateDef
      
    
  

The trick is to add reference to MaintemplateDef.jspx manually:


    
    
    
  

    
      
        LeftMenutemplateDef
      
    
  

Now, our LeftMenutemplateDef.jspx template is page template built on MaintemplateDef.jspx. It is nested page template. In order to match the requirement with menu on the left we have to add some code:
  
    
        
          
            
            
          
          
            
            
          
        
      
  
    
      
        LeftMenutemplateDef
        
          
            menuFacet
          
        
        
          
            centerFacet
          
        
      
    
  

And now it looks like this:

Every page with menu on the left has to be built on LeftMenutemplateDef template, every page without menu should be built on MaintemplateDef template.

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;
}