JDeveloper: Using Task Flow Parameters to Show Different UI in a Region

Lately a couple of questions on the JDeveloper & ADF space regarding using task flow parameters came up.

Use Case

One specific use case was how to show different UI in the same region if a row is just created or if the user wants to edit an already existing row.

Full description is that the user sees a table with e.g. regions of the HR DB schema. Now there are two buttons, one ‘Create new…’ and one ‘Edit current…’. When clicking the ‘Edit current…’ button the currently selected row of the table should be loaded into a form. There the user can edit everything but the primary key (PK). If the user click the ‘Create new…’ button the same form should be visible, but the PK should be editable too.

Running Application

To make it more visible let’s start with the finished application:

selection_955

Running Application

The final UI looks like in the image above. The UI is composed of four areas as in the image below:

selection_955_comment

The ‘Header’ and ‘Search Panel’ area are only for convenience. In the ‘Panel Collection Bar’ holds a toolbar with two buttons ‘Create new…’ and ‘Edit current…’. The table below shows the result of a search of the Region table from the HR DB.

Selecting a row in the table we can edit the selected record by clicking on the ‘Edit current…’ button

selection_956

This will open a new screen showing the selected row. Above the ‘RegionId’ we see a text indicating that we are in ‘edit’ mode and we can’t edit the ‘RegionId’ attribute as it’s the PK of the row and should not be editable.

Here we can edit the RegionName attribute and store the change by clicking the ‘Commit’ button:

Likewise, if we click the ‘Create new…’ button we go to the same form, but this time the text above the ‘RegionId’ attribute tells us that we are in ‘create’ mode and we can edit the RegionId.

Committing the changes we get a new row in the Regions HR DB table.

Implementation

Ok, let’s talk about how to implement this. For the model layer we run the ‘Business Components from Table…’ wizard on the model project and select the regions table from the HR DB. For this demo this is all we need to do.

The UI consist of two pages, index.jsf and Region.jsf. The index.jsf page is the start page and shows the UI as in the first image. Everything is easily done by drag and drop the right components in the right order onto the page. I spare the details for this as you can look at the sample which you can download using the link at the end of the post.

The only thing I like to go into detail is the toolbar with the two buttons ‘Create new…’ and ‘Edit current…’. These buttons do two things:

  1. Set a mode property to pageFlowScope
  2. Navigate to the second page Region.jsf

The Toolbar definition looks like

 <af:toolbar id="t1">
   <af:button text="Create new..." id="b1" action="show">
     <af:setPropertyListener from="#{'create'}" to="#{pageFlowScope.mode}" type="action"/>
   </af:button>
   <af:button text="Edit current..." id="b2" action="show">
     <af:setPropertyListener from="#{'edit'}" to="#{pageFlowScope.mode}" type="action"/>
   </af:button>
 </af:toolbar>

The create button has a af:setPropertyListener added which sets a pageFlowScope attribute ‘mode’ to ‘create’ and navigates to the Region.jsf page by executing the ‘show’ navigation from the unbounded task flow adfc-config.xml

adfc-config.xml

adfc-config.xml

The edit button uses an af:setPropertyListener which sets a pageFlowScope attribute ‘mode’ to ‘edit’ and then executes the navigation ‘show’ to go to the Region.jsf page. The logic to insert a new row or to edit an existing row is done in the bounded task flow ‘region-edit-create-btf.xml’ which we talk about later.

The Region.jsf page consists of a Header and a Region holding an af:form of the selected row of the Region:

selection_957_comment

Region.jsf

The region itself is a bounded task flow with the following properties

selection_964

Here we see one parameter with the name ‘mode’ which stores its value in a pageFlowScope attribute named ‘mode’. One other thing we need to make sure of is that the region shares the data control with its parent (in this case the adfc-config unbounded task flow) and always begins a new transaction. This make the bounded task flow a unit of work, it encapsulates the work in the task flow. The interface of the bounded task flow describes what the unit of work does:

Interface of ‘region-edit-create-btf.xml’ task flow:

If mode is set to ‘edit’, the current selected row of the Region table is shown in a form and can be edited. 

If the mode is set to ‘create’, a new row is created and inserted into the Region table and can then be edited.

The user can commit or cancel the operation. After each of this operations the task flow executes a parent action ‘back’.

selection_965

We see that the default action of the task flow is a router which uses the parameter set to the task flow to execute the create of the edit navigation:

selection_966

after that the now current record is shown on the fragment (see the area marked ‘Region’ in image Region.jsf). Below we see the panelFormLayout used for the region:

 <af:panelFormLayout id="pfl1">
   <af:outputText value="we are in #{pageFlowScope.mode eq 'create'? 'create' : 'edit'} mode" id="ot1"/>
   <af:inputText value="#{bindings.RegionId.inputValue}" label="#{bindings.RegionId.hints.label}"
     required="#{bindings.RegionId.hints.mandatory}" columns="#{bindings.RegionId.hints.displayWidth}"
     maximumLength="#{bindings.RegionId.hints.precision}" shortDesc="#{bindings.RegionId.hints.tooltip}" id="it1"
     disabled="#{pageFlowScope.mode ne 'create'}">
     <f:validator binding="#{bindings.RegionId.validator}"/>
     <af:convertNumber groupingUsed="false" pattern="#{bindings.RegionId.format}"/>
   </af:inputText>
   <af:inputText value="#{bindings.RegionName.inputValue}" label="#{bindings.RegionName.hints.label}"
     required="#{bindings.RegionName.hints.mandatory}" columns="#{bindings.RegionName.hints.displayWidth}"
     maximumLength="#{bindings.RegionName.hints.precision}" shortDesc="#{bindings.RegionName.hints.tooltip}" id="it2">
     <f:validator binding="#{bindings.RegionName.validator}"/>
   </af:inputText>
   <f:facet name="footer">
     <af:panelGroupLayout id="pgl2">
       <af:button text="Commit" id="b2" action="commit"/>
       <af:button text="Rollback" id="b1" immediate="true" action="rollback">
         <af:resetActionListener/>
       </af:button>
     </af:panelGroupLayout>
   </f:facet>
 </af:panelFormLayout>

Let’s look at the actions which are done in the region. If the user commits the changes the commit action from the data control is called which saves the changes to the db. If the ‘cancel’ button is clicked, the rollback method from the data control is called which reverts any changes done in the task flow. After the commit or rollback a parentAction (paraneAction1) is called which executes the ‘back’ navigation in the adfc-config.xml which navigates back to the index.jsf page.

Please note that we could have added the calls to commit and rollback to the buttons in the region.jsff. I decided to put them into the task flow instead to show the whole task flow and how it works in one place.

Implement different UI according to the task flow parameter

So, how do we use the parameter passed to the bounded task flow to switch the UI?

This is done by using an expression language (EL) which points to the ‘mode’ attribute stored in the pageFlowScope. Sample: the text above the RegionId is created with an af:outputText like

<af:outputText value="we are in #{pageFlowScope.mode eq 'create'? 'create' : 'edit'} mode" id="ot1"/>

The EL ‘#{pageFlowScope.mode eq ‘create’? ‘create’ : ‘edit’} ‘ is used to differentiate between the modes. Likewise the disable property of the RegionId attribute uses the EL

...disabled="#{pageFlowScope.mode ne 'create'}"...

which is true when the passed parameter is not ‘create’. In this case the disabled property is set to false, meaning that the field can’t be edited.

That’s it. There is no line of java code necessary to implement this use case.

Download

You can download the sample which was build using JDeveloper 12.2.1.2 and uses the HR DB schema from GitHub BlogTaskFlowParameter.

JDev 12.1.3: Using Parent Action to Navigate Tabs of a af:panelTabbed from Inside a Region

This blog is based on a question in the OTN JDeveloper and ADF forum. The Question was how to navigate from one selected tab to the next tab when the af:showDetailItem in the tab is a region and the button to navigate is inside the region.

We implement two cases, the first is the easy one where the button to navigate is in the page holding the af:panelTabbed. The second one uses a button is inside a bounded task flow which is shown in the af:showDetailItem in a tab to navigate the af:panelTabbed.

We start with creating a new ‘ADF Fusion Web Application’ from the gallery. We only change the application name and the path of the application, otherwise we can just use the default values. The sample is simple and doesn’t need a model project or connection to a DB. You can download the finished workspace using the link provided at the end of the post.

We skip all the needed steps and going right into creating the starting page which holds the af:panelTabbed. It has three af:showDetailItem and a af:Button to navigate the tabs directly from the page. This button implements the first use case.

Start Page with Outer Navigation

Start Page with Outer Navigation

The button has a listener attached which is implemented in a viewScope bean ‘NavigateTabBean’. The listener implements the needed logic to navigate from the selected tab to the next tab. If the last tab is reached the first tab is selected.

    private static ADFLogger _logger = ADFLogger.createADFLogger(NavigateTabBean.class);
    private static final String PANELTAB = &quot;pt1&quot;;

    /**
     * Eventhandler to navigate to the next tab in a af:panelTabbed
     * @param actionEvent event which called the listener
     */
    public void naviGateButtonAction(ActionEvent actionEvent) {
        UIComponent ui = getUIComponent(PANELTAB);
        if (ui == null) {
            _logger.info(&quot;PanelTab component not found!&quot;);
            return;
        }
        if (!(ui instanceof RichPanelTabbed)) {
            _logger.info(&quot;Component is not an af:panelTabbed&quot;);
            return;
        }

        RichPanelTabbed rpt = (RichPanelTabbed) ui;
        int childCount = rpt.getChildCount();
        List&lt;UIComponent&gt; children = rpt.getChildren();
        for (int ii = 0; ii &lt; childCount; ii++) {
            UIComponent uiSDI = children.get(ii);
            if (uiSDI instanceof RichShowDetailItem) {
                RichShowDetailItem rsdi = (RichShowDetailItem) uiSDI;
                if (rsdi.isDisclosed()) {
                    //close current tab
                    rsdi.setDisclosed(false);
                    //calculate next tab to disclose as next_tab_index = (current_tab_index + 1) % number_of_tabs
                    int kk = ii + 1;
                    int jj = kk % childCount;
                    _logger.info(&quot;old disclosed tab: &quot; + ii + &quot; new disclodes tab: &quot; + jj);
                    RichShowDetailItem newSDI = (RichShowDetailItem) children.get(jj);
                    //open new tab
                    newSDI.setDisclosed(true);
                    AdfFacesContext.getCurrentInstance().addPartialTarget(rpt);
                    return;
                }
            }
        }
    }

    // find a jsf component
    private UIComponent getUIComponent(String name) {
        FacesContext facesCtx = FacesContext.getCurrentInstance();
        return facesCtx.getViewRoot().findComponent(name);
    }

    public void nextTab() {
        naviGateButtonAction(null);
    }

The logic in the action listener first searches for the af:panelTabbed in the viewRoot and gets the number of children from it. Each child is one of the af:showDetailItem representing a tab. Then we iterate over the child list and search the currently disclosed tab. We close this tab and the next tab in the list gets disclosed. If the currently selected tab is the last in the list, the first tab is disclosed (see the comments in the code section).

To Implement the second use case, the one we really want to talk about, we first need to implement three bounded task flows which we later use as regions in the tabs.

Bounden Task Flow with Parent Action

Bounden Task Flow with Parent Action

The image shows the bounded task flow for one tab. The other bounded task flows are build in the same way and are just showing different text. The reason for this is that you normally would use different regions aka different task flows in the tabs. We could have used only one bounded task flow with a parameter to change the text shown in the fragment. In the sample you’ll find this implemented for tabs 4 and 5.
The region is simple and only shows one fragment which has a button to navigate to the next tab and a test to distinguish the regions when navigating. The whole magic is the parent action in the bounded task flow. This parent action executes a navigation case ‘nextTab’ in the parent task flow.

Unbounded Task Flow with Start Page

Unbounded Task Flow with Start Page

In the image above we the the unbounded task flow which is the parent of the bounded task flow. Here a wild card rule navigates to a method call activity ‘selectNextTab’ using the navigation case ‘nextTab’ we entered to the parent action of the regions.
The method action calls the ‘nextTab()’ in the managed bean from the code section above. All this method does is to call the action listener which is called from the af:Button of the start page (Start.jsf). As the action listener needs an ActionEvent as parameter, which we don’t use in the code we pass ‘null’ when we call the listener from the method call activity.

This concludes the implementation. Here are some images from the running application

The sample application can be downloaded form ADFEMG Sample Project.

A version of the software build with JDeveloper 11.1.1.7.0 can be downloaded from GitHub

JDev 12.1.3: Use Default Activity Instead of the Deprecated Invoke Action

Since JDeveloper 12.1.3 the invoke action used in earlier version has been deprecated. Users still using the old invoke action to load data on page load should migrate their code to using the default activity in a bounded task flow instead. This article describes how to use the executeWithParams method as a default activity in a bounded task flow (btf) to load data to be shown in a region. For this we implement a common

Use Case:
in a text field the user enters a string which should be used to look-up data in the DB and show the data as a table in a region.
For this we use the HR schema and build a look-up for locations after the name of the city of the location. In a page the user can insert the name or part of a cities name into a text field. This input is send as parameter to a bounded task flow. The default activity of the btf calls a method in the view object which uses a view criteria to search for cities starting with the given input data. In a second implementation the same technique is used but a where clause is used in the VO and the VO is called with executeWithParams. The result of the search is displayed as a table in a region.

Implementation

Model Project:
We start by creating a new ‘Fusion Web Application’ and creating a model project of the HR DB schema. Here we only use the location table for which we create entity object and view object.
Now we create the view criteria which we use to find locations by part of the city name.

Next step is to create the java class for the view object including the method to safely access the created bind variable. In the class we add a method to apply the created view criteria which we expose in the client interface well as the methods to access bind variables.


Finally we have to make sure that the locations view object is part of the data model of the application module.
Resulting Application Module Data Model

Resulting Application Module Data Model


Next we add another view object to the data model which we use to implement the use case a second time. This time we use the view criteria we defined in the view object LocationsView and select it as the default where clause.

ViewController Project:
We start implementing the view controller project by first adding a start page, ‘Start’, to the unbounded task flow in adfc-config.xml. For this page we use a quick layout (One Column, Header stretched).

After opening the page (which creates it) we add a third grid row to the panelGridLayout we got from the quick layout which later holds the result table. In the first grid row we add a captain for the page, ‘Execute with param sample’, the second grid row we add an af:inputText which holds the users input for the city name to search for.
The page looks like

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html>
<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <af:document title="Start.jsf" id="d1">
        <af:form id="f1">
            <af:panelGridLayout id="pgl1">
                <af:gridRow height="50px" id="gr1">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc1">
                        <!-- Header -->
                        <af:outputText value="ExecuteWithParams Test" id="ot1" inlineStyle="font-size:x-large;"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow height="50px" id="gr2">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc2">
                        <!-- Content -->
                        <af:inputText label="City" id="it1" value="" autoSubmit="true"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow id="gr3">
                    <af:gridCell id="gc3">
                        <!-- REGION HERE -->
                    </af:gridCell>
                </af:gridRow>
            </af:panelGridLayout>
        </af:form>
    </af:document>
</f:view>

Now we create a pageDefinition for the page, where we define a variable and an attribute binding which holds the users input into the inputText we added to a grid row below the header.


The final inputText look like

<af:inputText label="City" id="it1" value="#{bindings.searchCityName1.inputValue}" autoSubmit="true"/>

As you see we set the autoSubmit property to true as we don’t have (and need) a button to submit the data to the binding layer.

The next task is to create a new bounded task flow which has one input parameter, which is used to search for locations with cities starting with the given parameter from the inputText component.

Once the bounded task flow is created we can drag this btf onto the start page and drop it in the girdCell in the third gridRow and wire the parameter for the task flow to the value we have stored in the in the variable iterator via the inputText.

Finally we make the region refresh whenever the inputParamter of the task flow changes by setting the regions refresh property to ‘ifNeeded’.
The final ‘Start’ page layout looks like

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html>
<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <af:document title="Start.jsf" id="d1">
        <af:form id="f1">
            <af:panelGridLayout id="pgl1">
                <af:gridRow height="50px" id="gr1">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc1">
                        <!-- Header -->
                        <af:outputText value="ExecuteWithParams Test" id="ot1" inlineStyle="font-size:x-large;"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow height="50px" id="gr2">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc2">
                        <!-- Content -->
                        <af:inputText label="City" id="it1" value="#{bindings.searchCityName1.inputValue}" autoSubmit="true"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow id="gr3">
                    <af:gridCell id="gc3">
                        <af:region value="#{bindings.showlocatiobycitybtf1.regionModel}" id="r1"/>
                    </af:gridCell>
                </af:gridRow>
            </af:panelGridLayout>
        </af:form>
    </af:document>
</f:view>

This concludes the first implementation and we can run the application

The sample application can be downloaded form ADFEMG Sample Project. It contains a second page (Start2) which uses the other view object (LocationsWithParamsView) inside the region. It’s build like the first version. The difference is that the default activity nor is the executeWithParams from the VOs operations instead the self implemented method from the VO. You spare writing the method and exposing the method in the client interface this way.
Be aware that the sample uses the HR DB schema and you have to change the connection information to point to your DB.

JDeveloper 11.1.1.5.0 : Use Router to Create New Row or Edit Existing

Based on a question on the OTN ADF forum I wrote a small sample to show how to use a router in a bounded task flow to either edit an existing row, or create a new row and present it to enter data.

Use Case
Based on a query which returns either zero or one row an edit form should should be presented to the user. This form show the data from the row found, or an empty form to allow creation of a new row.

Implementation
The sample workspace, which you can download from the ADF Samples webside using the link at the end of this post, uses a very simple model. It contains only one view object, based of the LOCATIONS table of the HR schema.
The view object has one custom method implemented which search for a location record by an id. This method is used as query which returns either one row (if the location is found) or no row (if the location is not found).
The UI is build with one page which has a splitter component. On the left facet we find an simple form which we use to input the location id to search for and a button to execute the query. To allow to save or cancel the actions we add a rollback and a commit button. On the second facet we see a region which shoes the panelForm to either edit the existing record or to enter data into a new created record.
The image below shows the page layout.

General page layout

General page layout

Running

The next image shows the running application with a location id found

Running application with Location Id found

Running application with Location Id found

and then with a location id not found

Location ID not found

Location ID not found

You clearly see the empty from to enter new row data.

Now, to implement this the first splitter facet has a button to executes the query (find location by id) with the id given in the inputText. The region on the second splitter facet refreshes itself as we set the ‘Refresh’ property to ‘ifNeeded’. To make this work we need to change an input parameter to the region. This is done by binding the input parameter of the region to the inputText component for the location id.

Setup of input parameter of the region

Setup of input parameter of the region

So whenever the input parameter changes the region gets restarted. this effect we use in the bounded task flow to check if we have to create a new row and thus show a empty form, or to just edit an existing row. For this the bounded task flow uses a router component as start activity. This router checks if the iterator of the locations view object contains zero rows or more.

Bounded task flow

Bounded task flow

The router uses the estimatedRowCount of the underlying view object in an expression to check the number of rows:

EL to check if iterator has zero rows

EL to check if iterator has zero rows

The router navigates to the ‘insert’ case if the EL evaluated to true, to the ‘edit’ case otherwise. That concludes the use case.

You can down load the sample work space from ADF Samples Repository. The sample was build using JDeveloper 11.1.1.5.0 but should work using 11.1.1.6.0 and 11.1.2.x too. It uses the HR schema and you need to correct the db credentials to run the sample.

JDeveloper & ADF: Carefully Select the Data Control Scope of Bounded Task Flows

A customer asked me to look into an error which showed up in one of their applications. Without going into the details of real use case I like to bring this issue to your attention as it might happen to you too.

Use Case
I set up a mock use case to show the problem as this: A page (.jspx or .jsf) includes one (or more) other task flows as a region. The base page retrieves some data using a method call exposed in the data control, which should be displayed in the region for information purpose.

Implementation
If the task flows share their data controls this is not a big deal to implement this. You can download the sample using the link provided at the end of this blog. The sample uses the HR schema and was build using JDev 11.1.1.5.0 and was additional tested with JDev 11.1.1.1.6.0 and JDev 11.1.2.2.0, which show the same behavior.

On the base page I set up an inputText which I use to pass a parameter to the method (callMeTest) which retrieves some info from the application module. The callMeTest method simply returns the passed parameter together with a ‘nice’ message:

    public String callMeTest(String name) {
        _logger.info("Called with: "+name);
        return "Call me Test "+name;
    }

The return value is then displayed on the base page at the bottom under the button and should be displayed in the region, under the table, too. The sample just uses one region. When no value is present ‘****’ is used as parameter. The image below shows the application after starting it.

Start Screen

Start Screen

After entering a value into the inputText field below the table and hitting the ‘callMeTest’ button underneath it, you see that the return value is displayed below the button on the base page and under the table inside the region. If the button is hit with another value, both values are updated.

After entering a value and hitting the button

After entering a value and hitting the button

Nothing unusual so far. Now, for some unknown reason, after a change to another part the application, that functionality did not work any longer. The value in the base page still updates after the call to the method, but the value in the region stopped updating.

Screen after next deployment

Screen after next deployment

The reason for this behavior is a simple one. A look into the task flow properties of the region, revealed that the checkmark for the ‘Share data controls with calling task flow’ was not set, meaning that the task flow get its own data control frame. The result is that the method outcome from the base page is not visible in the region.

TaskFlow properties (1)

TaskFlow properties (1)

The question was how did this happen, when the change which results in the new deployment had nothing to do with this part of the application?

Analysis
It turned out, that when you create a new bounded task flow this property value is set to ‘default’. This is shown in the ‘Overview’ of the task flow as bluish shade in the check box. No checkmark is set and a look into the source reveals that the whole section

    <data-control-scope>
      <....../>
    </data-control-scope>

is just missing from the XML file.

TaskFlow after creation

TaskFlow after creation

Once you click on this property in the property editor you can just switch between ‘isolated’ or ‘shared’.

TaskFlow properties (2)

TaskFlow properties (2)

The only possible way to get the bluish shade back is to delete the section in the source view of the task flow.

TaskFlow properties in 'Source Mode'

TaskFlow properties in ‘Source Mode’

So this checkbox has three states ‘bluish’, ‘isolated’, and ‘shared’. Well, there is a forth state which you get if you open the property editor in source mode and select ‘default’. The following table shown all states together with their meaning:

State  Meaning
Bluish  Shared
Check marked  Shared
Unchecked not bluish  Isolated
Data-control-scope empty  Isolated

While the application works according to the meaning of the flag, I find it not intuitive how JDev handles this setting. The programmer who made the change to the application clicked this property, but meant to do so on a different task flow. So he unchecked the ‘Share data controls with calling task flow’ property as he remembered that it was not set before.

Recommendation
My advise is to set the flag whenever you create a task flow so you know which share mode the task flow uses. This avoids errors like the one described in this blog.

UPDATE
In the meantime Chris Muir raised the issue with the UX specialists (Oracle intern) and Bug 14390576 has been raised for this issue.
Thank you Chris!

The sample uses the HR schema and was build with JDeveloper 11.1.1.5.0, tested with 11.1.1.6.0 and 11.1.2.2.0 . You can download the sample work space form ADF Samples: Source Code

JDev11.1.2.1.0: Handling images/files in ADF (Part 2)

This blog article is part 2 of a series of posts showing how to deal with images or files in an ADF application. Each of the techniques to do this are described in other blog posts or documents, but I couldn’t find a sample doing it all together in on sample application.
The goal of this series is to provide a sample showing how to upload a file from a client to the server, store the data on the server, make it available for later retrieval and show the data in the user interface (form and table).

    Part 1 gives an overview of the sample application I’m going to build and how to set it up
    Part 2 shows how to upload a file, store it and download it back to the client
    Part 3 implements two techniques to show the data (image) on the user interface
    Part 4 backport of the sample to JDeveloper 11gR1
    Part 5 implements a technique to show the uploaded file right after upload without the need to commit first


Uploading, downloading and storing of data in a blob

In this part of the series I show how to upload a file from the client to the server and store the data in a blob column in a db. The tables I’m using are CATALOG and IMAGES. The DML to define the tables and can be found in PART 1 of the series or in the sample workspace at the end of this part.
Lets start with uploading a file from client to the server. ADF rich faces provide the tag af:inputFile to allow uploading of data.

&lt;af:inputFile label=&quot;Select new file&quot; id=&quot;if1&quot; autoSubmit=&quot;true&quot;
              valueChangeListener=&quot;#{ImageBean.uploadFileValueChangeEvent}&quot;/&gt;

As you see the tag has set its autoSubmit property to true to allow direct upload of data. The real work is done in the valueChangeListener which is bound to a backing bean in request scope. The value the event carries allows access to the data and give us the original filename and mime type.

    public void uploadFileValueChangeEvent(ValueChangeEvent valueChangeEvent)
    {
        // The event give access to an Uploade dFile which contains data about the file and its content
        UploadedFile file = (UploadedFile) valueChangeEvent.getNewValue();
        // Get the original file name
        String fileName = file.getFilename();
        // get the mime type 
        String contentType = ContentTypes.get(fileName);
        // get the current roew from the ImagesView2Iterator via the binding
        DCBindingContainer lBindingContainer =
            (DCBindingContainer) BindingContext.getCurrent().getCurrentBindingsEntry();
        DCIteratorBinding lBinding = lBindingContainer.findIteratorBinding(&quot;ImagesView2Iterator&quot;);
        Row newRow = lBinding.getCurrentRow();
        // set the file name
        newRow.setAttribute(&quot;ImageName&quot;, fileName);
        // create the BlobDomain and set it into the row
        newRow.setAttribute(&quot;ImageData&quot;, createBlobDomain(file));
        // set the mime type
        newRow.setAttribute(&quot;ContentType&quot;, contentType);
    }

    private BlobDomain createBlobDomain(UploadedFile file)
    {
        // init the internal variables
        InputStream in = null;
        BlobDomain blobDomain = null;
        OutputStream out = null;

        try
        {
            // Get the input stream representing the data from the client
            in = file.getInputStream();
            // create the BlobDomain datatype to store the data in the db
            blobDomain = new BlobDomain();
            // get the outputStream for hte BlobDomain
            out = blobDomain.getBinaryOutputStream();
            // copy the input stream into the output stream
            /*
             * IOUtils is a class from the Apache Commons IO Package (http://www.apache.org/)
             * Here version 2.0.1 is used
             * please download it directly from http://projects.apache.org/projects/commons_io.html
             */ 
            IOUtils.copy(in, out);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (SQLException e)
        {
            e.fillInStackTrace();
        }

        // return the filled BlobDomain
        return blobDomain;
    }

Please note the I use the Apache Commons IO package in the version 2.0.1 which you need to download from the Apache Software Foundation web side. The use the class IOUtils which allow easy copying of streams.
If your are using an older version of JDeveloper you may need to add the usesUpload property to the af:form tag. In the current JDev version it should work automatically (but please check it).

&lt;af:form id=&quot;f1&quot; usesUpload=&quot;true&quot;&gt;

If you use fragments (as in this sample) you need to check the jspx or jsf page which is holding the af:form tag (Catalog.jsf), as the fragments don’t have a form tag.
By default, Oracle ADF 11g application allows to upload maximum 2 MB size files. This maximum can be configured in the web.xml file if you need to upload files bigger then 2 MB. For this you need to specify the context parameters

    org.apache.myfaces.trinidad.UPLOAD_MAX_MEMORY
    org.apache.myfaces.trinidad.UPLOAD_MAX_DISK_SPACE
    org.apache.myfaces.trinidad.UPLOAD_TEMP_DIR

For more information about the parameters and how they work check the doc Oracle® Fusion Middleware Web User Interface Developer’s Guide for Oracle Application Development Framework.

Now to the download part. This is handled in ADF via the af:fileDownloadActionListener tag. The tag is a client listener tag and is therefor applied to a command ui tag. In the sample I use a af:commandButton:

&lt;af:commandButton text=&quot;Download Data&quot; id=&quot;cb3&quot;
                  visible=&quot;#{bindings.ImageData.inputValue ne null}&quot;
                  binding=&quot;#{ImageBean.downloadButton}&quot;&gt;
          &lt;af:fileDownloadActionListener contentType=&quot;#{bindings.ContentType.inputValue}&quot;
                                         filename=&quot;#{bindings.ImageName.inputValue}&quot;
                                         method=&quot;#{ImageBean.downloadImage}&quot;/&gt;
&lt;/af:commandButton&gt;

The real work is done in the downloadImage method in the managed bean. The signature of the method is

public void downloadImage(FacesContext facesContext, OutputStream outputStream)

This allows you to access to the FacesContext and the output stream which you use to pipe the data to the client.

    public void downloadImage(FacesContext facesContext, OutputStream outputStream)
    {
        BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();

        // get an ADF attributevalue from the ADF page definitions
        AttributeBinding attr = (AttributeBinding) bindings.getControlBinding(&quot;ImageData&quot;);
        if (attr == null)
        {
            return;
        }

        // the value is a BlobDomain data type
        BlobDomain blob = (BlobDomain) attr.getInputValue();

        try
        {   // copy hte data from the BlobDomain to the output stream 
            IOUtils.copy(blob.getInputStream(), outputStream);
            // cloase the blob to release the recources
            blob.closeInputStream();
            // flush the outout stream
            outputStream.flush();
        }
        catch (IOException e)
        {
            // handle errors
            e.printStackTrace();
            FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), &quot;&quot;);
            FacesContext.getCurrentInstance().addMessage(null, msg);
        }
    }

This is all you need to do to download a blob from the db and send it back to the client.

I like to mention one other function of the sample application. If you hit the ‘Cancel’ button in the insert or edit image page I use a rollback to get the original data back and remove all changes made in the form. A rollback resets all current row pointers of the iterators. To avoid that the user sees the first row of the catalog table the rollback has to be handled in a special way. You have to save the current row (of the catalog iterator), do the rollback and reset the current row back to the saved one. This is done in the bean method

public String cancel_action() {...}

which you find in the ImageBean class.

This concludes part 2. In part three I implement two techniques to show the data (image) on the user interface (page)

The sample application workspace is build with JDeveloper 11.1.2.1.0 and can be loaded from here BlogUploadDownload_P2.zip
Please rename the file to ‘.zip’ after downloading it!
The Commons IO package in the version 2.0.1 you can download from the Apache Software Foundation apache web side

To be continued…

JDev11.1.2.1.0: Handling images/files in ADF (Part 1)

This blog article is part 1 of a series of posts showing how to deal with images or files in an ADF application. Each of the techniques to do this are described in other blog posts or documents, but I couldn’t find a sample doing it all together in on sample application.
The goal of this series is to provide a sample showing how to upload a file from a client to the server, store the data on the server, make it available for later retrieval and show the data in the user interface (form and table).

    Part 1 gives an overview of the sample application I’m going to build and how to set it up
    Part 2 shows how to upload a file, store it and download it back to the client
    Part 3 implements two techniques to show the data (image) on the user interface
    Part 4 backport of the sample to JDeveloper 11gR1
    Part 5 implements a technique to show the uploaded file right after upload without the need to commit first


Overview of the sample application

The sample allows to create and manage catalogs. A catalog has a unique id, a name and may contain files and images which the user can upload into a catalog and download from it. All this is implemented in a simple way, so no fancy layout, only bare functionality. Here is a screen shot of the sample application after part 2 is finished:

Sample app end of part 2

Sample app end of part 2

As you see the UI is nothing I would use in a real world application, but for this sample it does the trick.
To create a new catalog you click the ‘New Catalog’ button and can fill in a name for the new catalog. The id is automatically assigned via a groovy expression which calls a sequence defined in the db. In the catalog screen you see the catalog together with all images added to this catalog. Here you can remove the whole catalog. The image data is deleted too in this case.

Create Catalog

Create Catalog

Once you have a catalog created you can add images or other files to it by using the ‘New Image’ button.

Create Image

Create Image

When adding a new image to a catalog you can specify a name for the image, the content type which will be read from the file once you hit the upload button. The image id is assigned by a groovy expression, the catalog id is populated by the master record, the catalog. As there is no visible image of the data in this version, an output text shows you if data has already been uploaded (Image Data available/not available).
This concludes the short run through the sample application.
The following db diagram shows the two tables involved (CATALOG and IMAGES) and their 1:* relationship. For reference I added the two sequences which generate the primary key for the tables.

DB Diagram

DB Diagram

Next is the DML to generate the two tables and the sequences. You can add the tables to an existing DB or create a new schema and add them their. If you later download the source code you’ notice, that I added the DML to the well known HR schema. If you use an other schema, you have to change the db connection accordingly.

CREATE TABLE CATALOG 
(
  CATALOG_ID NUMBER(12) NOT NULL 
, CATALOG_NAME VARCHAR2(200 CHAR) NOT NULL 
, CONSTRAINT CATALOG_PK PRIMARY KEY 
  (
    CATALOG_ID 
  )
  ENABLE 
);

CREATE UNIQUE INDEX CATALOG_PK ON CATALOG (null ASC);

CREATE SEQUENCE CATALOG_SEQ INCREMENT BY 1 START WITH 100 NOCACHE;

CREATE TABLE IMAGES 
(
  IMAGE_ID NUMBER(12) NOT NULL 
, IMAGE_NAME VARCHAR2(200 CHAR) NOT NULL 
, CONTENT_TYPE VARCHAR2(50 CHAR) 
, IMAGE_DATA BLOB 
, CATALOG_ID NUMBER(12) NOT NULL 
, CONSTRAINT IMAGES_PK PRIMARY KEY 
  (
    IMAGE_ID 
  )
  ENABLE 
);

CREATE UNIQUE INDEX IMAGES_PK ON IMAGES (null ASC);

ALTER TABLE IMAGES
ADD CONSTRAINT IMAGES_CATALOG_FK FOREIGN KEY
(
  CATALOG_ID 
)
REFERENCES CATALOG
(
  CATALOG_ID 
)
ENABLE;

CREATE SEQUENCE IMAGE_SEQ INCREMENT BY 1 START WITH 100 NOCACHE;

Finally here are the task flows which build the first part of the sample application:

Task flows

Task flows

The start page ‘Catalog’ contains a region (catalog-task-flow-description.xml) which is used to add or edit catalogs and to add or edit images for a catalog.

This concludes part 1. In part two I describe in detail how to implement the file upload and download feature and store the data in a blob column in the db.

To be continued…