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…

Advertisements

JDeveloper: Don’t overlook warning signs in task flows or check your ‘Show Warning’ settings

This week I came across a hard to find error using task flows, which was caused by a simple reason.

I tired out Frank Nimphius samples #52 about using task flows which are not part of the application itself, but which are added from adf libraries to the application. First I tried the sample Frank provided in the Code Corner article, which worked as expected after changing the db connections to my environment.

Running Sample #52

Running Sample #52

Next I’d build my own task flow in an adf library and tired to use it in the parent application as reagion. All went OK till I run the application. Here is the picture

My application with Task Flow in Library

My application with Task Flow in Library

As you can see the region representing the task flow from the library is not shown or empty. The first step to analyze the error was to inspect the task flow in the library which looked like:

Rebuilded task flow in the adf library

Rebuilded task flow in the adf library

This looked OK for me and a further visual inspection of each of the other elements did not show any difference compared to Franks sample. Here are the images of the method call activity

Method call setdeptId

Method call setdeptId

and the navigation together with the navigation target

Navigation and target page

Navigation and target page

After some more checking I saw the hint which finally lead me to finding the error. In the image above you see a yellow or brownish frame around the ‘From Outcome’ field. I didn’t pay much attention to this at first, but after checking the ‘setdeptId’ method call outcome and the navigation targets ‘From Outcome’ I saw the difference.
The ‘setdeptId’ method call had ‘setdeptId’ set as ‘Fixed Outcome’ whereas the navigation was set to ‘goEmployees’. Once these two attributes are set to the same value the the color frame is gone.

Navigation target after correction

Navigation target after correction

Some questions still to answer:
(q1) Why don’t you get any error output in the log when running the application?
(q2) Why do you see no other error or warning in the design view in JDeveloper?

The answer to (q1) is easy once you found the reason for the error. As the method call activity (setting the department id and executing the query) did not throw an error, the navigation took place. As the outcome was set to not existing target, the region remains empty. A this could be desired behavior and no exception occurred no log output is generated.

Question (q2) took some more research. Lately I wrote a couple of blog entries with many pictures from the samples I used to show something. Some of of the images I used showed a small yellow attention sign when e.g. a page element on a task flow when the page is not created jet. As I took some of the images before the pages where ready and I didn’t want to show the warnings in my pictures, I turn the warnings off. This is not a problem as I later created the missing pages. But I forgot about this change with the result that the visual warning for the error I described here was hidden.
With the warning turned on again the error is easy to detect:

Task Flow in the library with 'Show Warnin' on

Task Flow in the library with 'Show Warnin' on

To turn the ‘Show Warning’ on or off you drop down the ‘Show’ list box in the diagram view of a task flow or page:

'Show Warnig'

'Show Warnig'

So, always remember to reset settings you only change for your convenience or make sure you never forget their impact.

Three ways to delete a row while navigate over a RowSet

An OTN user ask how to delete a row while navigation a row set. This question and the problem the user got with implementing this leads to this sample. Shay Shmeltzer mentioned a pending bug #12671112 in JDev 11.1.2 which may cause trouble deleting a row while navigation a row set. As far as I understand only the first method (direct delete via data control) is affected by the bug.

The are a couple of ways to archive this task. This sample uses the declarative way of doing this. So no bean code is involved. I’m using the HR schema and the departments table to show the techniques. Be sure to save the content of the DEPARTMENTS table as the deletion will remove some rows if you commit the operation. For this you can e.g. export the table content as SQL insert statements to a file.

You can download the sample code from here
You need to remove the suffix ‘.doc’ as the file itself is a zip archive.

The sample shows three approaches to the problem, all declarative.
First I use the ‘Delete’ method from the DataControl ViewObject to delete the currently selected row.
The second approach uses a custom method in th e VO which gets the department id to remove as a parameter.
Finally the third approach uses a task flow method call to the above mentioned method to archive the deletion of the row which is identified by the passed department id.

Final application

Final application

The application consists of a ADF form together with the navigation buttons, commit and rollback button and one button to activate each of the three solutions.

1. Solution

The delete button is bound directly to the ‘Delete’ method of the VO of the DataControl:

<af:commandButton actionListener="#{bindings.Delete.execute}"
    text="Delete" immediate="true" id="cb9"/>

and the binding

Delete Binding

Delete Binding


As mentioned above the solution might cause trouble. For more info read this thread.

2. Solution

The second approach uses a custom method which is implemented in the DepartmentViewObject. The method receives the department id to delete as parameter, uses a viewCriteria to find the row and deletes the row if found. The method is exposed in the client interface of the VO to make it accessible on the UI layer.

    /** Delete the department row with the department id aDepId
     * Search the department via its id and if found delete the row without committing
     * @param aDepId Id of the department to delete
     */
    public void deleteDepartmentById(Number aDepId)
    {
        this.setWhereClauseParams(null);
        this.setWhereClause(null);
        this.setbindDepId(null);
        ViewCriteria lCriteria = this.getViewCriteria("DepartmentByIdCriteria");
        removeApplyViewCriteriaName(lCriteria.getName());
        this.applyViewCriteria(lCriteria);
        this.setbindDepId(aDepId);
        this.executeQuery();
        Row row = this.first();
        if (row != null)
        {
            row.remove();
            mLogger.info("Row with DepId " + aDepId.toString() + " deleted (not committed yet)!");
            removeApplyViewCriteriaName(lCriteria.getName());
            this.executeQuery();
        }
        else
        {
            mLogger.info("Row with DepId " + aDepId.toString() + " not found!");
        }
    }

The button ‘deleteDepartmentById’ binds this method to the button

<af:commandButton actionListener="#{bindings.deleteDepartmentById.execute}"
    text="deleteDepartmentById"
    disabled="#{!bindings.deleteDepartmentById.enabled}"
    id="cb7" immediate="true"/>

and the binding of the method

Binding DeleteDepartmentById

Binding DeleteDepartmentById


In this case the selected department id is passed to the method directly in the binding dialog (which is opened when you drop a method which needs a parameter onto the page:
Edit Action Binding of DeleteDepartmentById

Edit Action Binding of DeleteDepartmentById

3. Solution

The final approach used the same method mentioned in the 2nd approach, but uses a task flow method call to execute it.

Task Flow Method Call

Task Flow Method Call


The Button uses a af:setPropertyListener to pass the selected department id to the method in pageFlowScope variable ‘#{pageFlowScope.depId}’

<af:commandButton text="Delete Row via TaskFlow" id="cb8"
    action="delete" immediate="true">
    <af:setPropertyListener from="#{bindings.DepartmentId.inputValue}"
        to="#{pageFlowScope.depId}"
        type="action"/>
</af:commandButton>

The task flow method call was dropped from the dataControl onto the bounded task flow. The picture below shows the pageDef file which was generated by the framework:

PageDef of Method in TaskFlow

PageDef of Method in TaskFlow


This time the parameter is read from the pageFlowScope variable ‘#{pageFlowScope.depId}’.

All three solutions are doing the same in the end: they delete a row from a row set. Solutions 2 and 3 can be used when the table you use to select the row is based on an other (e.g. read only) VO then the VO you use to delete the row.
The same technique can be used for other methods defined in the VO too, e.g. to select a row in a read only table and edit the row in a ADF form (using a method which find the row ba id).

Using diferent VOs for Master Detail Navigation (the Declatative Way)

A user on the OTN forum asked a question how to do a master detail like navigation where the master VO is not equal to the detail VO and no accessors or link is available between the tow VOs.

A use case for this scenario is e.g. you have a read only table as master which holds an attribute which is the foreign key to an other table (the master table has a FK to the detail you like to change). In the sample I’m talking about in this blog I used the HR schema, the employees table as master and the the department as detail. I show how to use the employees as read only table, select an employee to edit the department the employee is assigned to.

Here is the data model of the sample:

Data Model

Data Model


As you can see there are no view links defined which could be used to navigate from the employee to the related department.

I’ll do all this the declarative way, so I don’t use a bean or other Java code. I use a bounded task flow and start with a query panel with the read only employees table. Each row shows the id of the employee, the name and the department id. I add a button to the department id of each row and use this to navigate to the departments edit page. Here you see the running app, the query panel which I used to select employees records and the button which I added to the department is column.

Start Screen

Start Screen

I used a button here because of an error in this version (11.1.2) of jdev which prevent the table from selection the current row when you just hit a link in a row. Frank Nimphius provided a workaround for this here:JDeveloper 11.1.2 : Command Link in Table Column Work Around. A click on the ‘Department’ button for ‘Jannette King’ will navigate to Department ’80’ which is editable

Select a Department from the Table and Edit Department

Select a Department from the Table and Edit Department

The work flow is implemented as shown below:

Work Flow

Work Flow

As you see the whole work is done in bounded task flow which first presents the query panel together with the resulting employees table (read only). The column ‘Department ID’ shows the button I use to navigate to the editable departments page. As there is no view link, it’s not enough to select the employees row to mark it as current row. I have to extract the department id from the selected row and use this to search for the department before showing the departments edit page.

I store the department id in a page flow scope variable named ‘#{pageFlowScope.depKey}’. If you like you can store the value of the department id elsewhere e.g. in the variables iterator of the page binding. To extract and store the value I use a af:setPropertyListener which allows to react on the action of the button and transfer the value to page flow scope variable. Here is the code of the department id column:

                            <af:column sortProperty="#{bindings.EmployeesView1.hints.DepartmentId.name}"
                                       sortable="true"
                                       headerText="#{bindings.EmployeesView1.hints.DepartmentId.label}"
                                       id="resId1c4" width="114">
                                <af:outputText value="#{row.DepartmentId}" id="ot5">
                                    <af:convertNumber groupingUsed="false"
                                                      pattern="#{bindings.EmployeesView1.hints.DepartmentId.format}"/>
                                </af:outputText>
                                <af:commandButton text="ShowDepartment" id="cb2" action="showDep">
                                    <af:setPropertyListener from="#{row.DepartmentId}"
                                                            to="#{pageFlowScope.depKey}"
                                                            type="action"/>
                                </af:commandButton>
                            </af:column>

The button action navigates to he method call ‘SetCurrentRowWithKeyValue’ in the bounded task flow. This method I dragged from the data control palette from the DepatermetnsView1 operation onto the bounded task flow definition page

SetCurrentRowWithKeyValue from DepartmentsView1

SetCurrentRowWithKeyValue from DepartmentsView1

The method searches the department using the the value stored in the page flow scope variable. The dialog below opens automatically when you drop the method on the task flow and lets me enter the key value to search for:

setCurrentRowWithKeyValue  Edit Action Binding

setCurrentRowWithKeyValue Edit Action Binding

Here is the pagedef file for the method call:

PageDef of setCurrentRowWithKeyValue Method

PageDef of setCurrentRowWithKeyValue Method


After the search the current row is set in the DepaertmensView1 and I can navigate to to the edit page. That’s about it.

You can download the sample work space from here Sample Workspace blogmasterdetaildeclarative_v2-zip. You have to rename the file to ‘.zip’ after download!