Using Groovy Expression to set a Primary Key with a Sequence Number

Just set up a workspace for JDeveloper 11.1.2.0.0 to show how to use a Groovy expression to set the primary key of an entity object. Chris Muir blogged about this back in 2009 here ADF BC: Using Groovy to fetch sequence numbers for EO/VO attribute default values. There exists a white paper Introduction to Groovy Support in JDeveloper and Oracle ADF 11g which covers Groovy support in JDeveloper.
So, I don’t cover the basic here but only show how to use this in a sample application. The application uses the HR schema and allows you to insert a new employee. The workspace which you can download (see at the end of this blog) is build using JDeveloper 11.1.2.0.0.
I use the EMPLOYEES_SEQ defined in the HR schema to set the PK of the new employee, EMPLOYEE_ID to the next available sequence number.

Employee Sequence

Employee Sequence


Now we can open the Employee EO from the model layer. Double click on the Employees EO to open the properties inspector for the EO and select the ‘Attribute’ section.
Employees Attributes

Employees Attributes


Now select the EmployeeId in the attributes to get to the attributes properties.
EmployeeId Properties

EmployeeId Properties


Here we can add a default value as literal, expression or as SQL. We select the ‘Expression’ radio button and click on hte pencel on the right side of the input field.
Edit Expression Editor

Edit Expression Editor


Here we enter the Groovy expression to get the next sequence number

(new oracle.jbo.server.SequenceImpl("EMPLOYEES_SEQ",adf.object.getDBTransaction())).getSequenceNumber()

After submitting the dialog with OK you should set the ‘Refresh Expression Value’ to ‘true’ and the ‘Updatable’ LOV to ‘While New’.

Finished Dialog

Finished Dialog


A word of caution here: JDev 11.1.2.0.0 saves the setting for the refresh condition in the xml file, but the next time you open the dialog again the ‘Refresh Expression Value’ value is gone! I’ll file a bug for this later.
This wraps up the the model layer of the app. You can test your work with the Application Module Tester. When you create a new record you’ll see that the EmployeeId is set to the next sequence number.
Oracle ADF Model Tester

Oracle ADF Model Tester


The ViewController project is pretty simple. It consists of an ADF form with with navigation buttons and a button to call a bounded task flow to create the new employee.
Task Flows

Task Flows


The bounded task flow first calls the CreateInsert operation to create a new record which is then displayed in the form. As you notice, the EmployeeId is an af:outputText element, so that you can’t change it.
Running Application

Running Application

The workspace for JDeveloper 11.1.2.0.0 can be downloaded from here: Workspace BlogPKwithGrooy.zip
After downloading the file, remove the suffix ‘.doc’ and rename it to ‘ BlogPKwithGrooy.zip’, as the file is a Zip file.

Advertisements

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).

Read Only Filter Table with Sort

Just to help somebody out on OTN I made a small demo app to show how to set up a read only ADF table with filter and sort.
The app uses the default HR schema. The Model project contains a VO EmpRoView which is based on a SQL query (select * from Employees) to get its data.
The view controller project has only one page which shows the resulting ADF table. You can enter e.g. ‘<4000' in the salary filter field and hit 'enter' to see the filter working.
The resulting workspace can be downloaded here ROFTTestApp.zip. After downloading the file you have to remove the ‘.doc’ from the filename!

The workspace is build with JDev 11.1.1.5.0 but works an 11.1.2 too.

JDev: Always Test Your App with ApplicationModule Pooling turned off

In the last couple of weeks I saw a couple of question which mentioned a sporadic misbehavior of the application under different circumstances. In the end they could be answered:

“Your application has not been tested with application module pooling turned off”

Whenever you encounter a sporadic misbehavior of the application under different circumstances, this should ring a bell. Most developers came across a situation like this when programming ADF applications. At some point the application does not react normal or throws exceptions. The errors are not reproducible (most of the times) and you only see them on the production server (never on the developer machine).

The first time I came across this problem it took me about a week to figure it out and solve it. Basically the problem has been private data (stored with the session) which is part of the application module and is stored in the user data hash map. This is not a problem as long as you can guarantee that each user always works with the same application module. This is the case when you test run your application on the developers machine (you are the only user and the application module pool always returns the same application module to the client). On a production server where more then one user uses the application at the same time, the application may be forced to reuse an application module which was formally used by a different user. At this point application module pooling take over.

The general algorithm used is that the application module pool has a number of application module available to use. If more requests arrive the pool generates the additional module until a high water mark is reached. Further requests getting rejected. Currently not used modules are given back to the pool and are available for the next request. The pool tries to return the same application module as long as it’s available to the same session for subsequent requests. If it is not available it uses a currently available module stores the current status of the module into a store (DB or file), clears the module from all information and reconstructs the state from the other session from saved state information. Once the application module is restored you can’t distinguish if it’s a new application module or a reused one. This way your application don’t need an module for each user request, but it shares the available modules between the requests, saving lots of resources.

All this can be read about here 43.2 Introduction to Fusion Web Application State Management.

After this more theoretical prologue, lets do a practical project (workspace for JDev 11.1.2 available, see end of article). To make it as simple as possible, but still useful for anybody running into problems with activation/passivation, we use the HR schema and try to emulate a scenario where a user only sees employee data which depends on a department number. This department number should be set in the application module and be accessible for all queries. In a real world scenario this information is connected to the login of a user and stored in a central place. In the sample we use the user data of the application module to store the number.
The applications start page (I do spare the login part) has a af:query panel to select employees which might be filtered by their last name. As there is no login I added a field to enter the department number which should be used to further filter the result set.The Web UI lokke like

Test app UITest Application UI

As you see there is an input field for the last name, in the bottom era an input field to insert the department number and an other panel to retrieve the currently set department number from the application module.
Lets have a look at the service method to get/set the department number and how it is stored:

    private static final String PRIVATEDATA = "privData";

    public void setPrivateToUserData(String aVal) {
        mLogger.info("Set PrivData:" + aVal);
        if (aVal == null)
            return;

        Session lSession = getSession();
        if (lSession == null) {
            mLogger.warning("getSession returned null!");
            return;
        }
        Hashtable lHashtable = lSession.getUserData();
        if (lHashtable == null) {
            mLogger.warning("getUserData returned null!");
            return;
        }
        lHashtable.put(PRIVATEDATA, aVal);
    }

    public String getPrivateToUserData() {
        Session lSession = getSession();
        if (lSession == null) {
            mLogger.warning("getSession returned null!");
            return null;
        }
        Hashtable lHashtable = lSession.getUserData();
        if (lHashtable == null) {
            mLogger.warning("getUserData returned null!");
            return null;
        }
        String lData = (String) lHashtable.get(PRIVATEDATA);
        mLogger.info("Get PrivData: " + lData);
        return lData;
    }

As you see the string from the UI is stored and retrieved under the key PRIVATEDATA = “privData” in the userData hash map of the application module. This is the place to store data for the current user session (Storing Information About the Current User Session)
The configuration of the application module is the default you see after generating a ‘Fusion Web Application’.

ApplicationModule Default Configuration

ApplicationModule Default Configuration


As you can see ‘Application Module Pooling’ is on. When we run the application, set the department number to e.g. 30, send the data to the AM and hit the search button in the query panel we see
Pooling On

Pooling On


You can hit the edit button in a row to edit the employee and come back to the page and see the application running as expected.
Now, we switch ‘Application Module Pooling’ off and run again:
Application Module Pooling Off

Application Module Pooling Off


Doing the same actions as earlier it look like the application does not see the department number at all:
Application Module Pooling Off

Application Module Pooling Off


Even if you set the department number and directly hit the ‘Get User Data’ button you’ll don’t get the department number back.

The reason for this behavior is that we store the department number in the user data hash map which is NOT passivated when the application module is given back to the pool and given to an other requester. This happens every time you go the the server when am pooling is switched off.
What we need to to is to passivate the session user data together with the other state data stored by the framework and load it back when the AM is requested the next time (when it gets activated again). To do this we have to overwrite two methodes in the ApplicationModuleImpl class.

    @Override
    protected void activateState(Element aElement) {
        super.activateState(aElement);
        mLogger.info("++++++++++ activateState");

        Hashtable lData = getSession().getUserData();
        if (aElement != null) {
            // 1. Search the element for any <PrivData> elements
            NodeList nl = aElement.getElementsByTagName(PRIVATEDATA);
            if (nl != null) {
                // 2. If any found, loop over the nodes found
                for (int i = 0, length = nl.getLength(); i < length; i++) {
                    // 3. Get first child node of the <PrivData> element
                    Node child = nl.item(i).getFirstChild();
                    if (child != null) {
                        // 4. Set the data value to the user data hashmap
                        String lDataString = child.getNodeValue();
                        String[] lSplitkeyval = lDataString.split(";");
                        for (int ii = 0; ii < lSplitkeyval.length; ii++) {
                            mLogger.fine("..."+lSplitkeyval[ii]);
                            String[] lSplit = lSplitkeyval[ii].split("=");
                            lData.put(lSplit[0], lSplit[1]);
                        }
                        break;
                    }
                }
            }
        }
    }


    @Override
    protected void passivateState(Document aDocument, Element aElement) {
        super.passivateState(aDocument, aElement);
        mLogger.info("---------- passivateState");

        // 1. Retrieve the value of the user data to save and build a string representation
        Session lSession = getSession();
        Hashtable lData = lSession.getUserData();
        String lDataString = "";
        Set<String> keyset = lData.keySet();
        if (!keyset.isEmpty()) {
            Iterator<String> keys = keyset.iterator();
            while (keys.hasNext()) {
                String key = keys.next();
                mLogger.fine("..."+key + "=" + lData.get(key));
                lDataString += key + "=" + lData.get(key) + ";";
            }
        }

        // 2. Create an XML element to contain the value
        Node node = aDocument.createElement(PRIVATEDATA);
        // 3. Create an XML text node to represent the value
        Node cNode = aDocument.createTextNode(lDataString);
        // 4. Append the text node as a child of the element
        node.appendChild(cNode);
        // 5. Append the element to the parent element passed in
        aElement.appendChild(node);
    }

The passivateState method gets a Document and an Element as parameter. After calling super() to let the framework do its work, we get the user data hash map and store each key-value pair in a string which is then appended as a node to the element we got as parameter. In a real world application I would use a java to XML serialization tool like XStream which is capable to store more complex data.
The activateState method gets an Element as parameter which we search for the node we save when passivateState was called and restore the user data hash map.

After putting the two methods in the ApplicationModuleImpl class (the one you get when you create the java classes for an application module) the application run OK again, application module pooling still turned off.

When you examine the sample workspace which you can get here BlogActivatePassivateSample_V1.zip (remove the ‘.doc’ extension after downloading the workspace!) you’ll notice, that EmployeesViewImpl class too have the two methods to store private data which is not automatically saved by the framework. Further there are log messages throughout the code to let you follow the action in the log window.

As you see it’s essential to test an application with application module pooling turned off to find activation/passivation errors before the application goes to production.

Get the start URL of your Web Application the easy way in JDev 11.1.2.0.0

WOW, as always Oracle surprises me (and hopefully many other users) with a new version of JDeveloper, this time in short succession of the latest 11.1.1.5.0 version.

It got me right in the middle of a short vacation, so I could only read some news and blogs via my smart phone. Back home I tarted to take a closer look on the new JDev 11.1.2.0.0.
Like many other users I can say: Impressive!
I like fast loading of JDev itself and the faster embedded WLS startup time best (so far, as I’m still checking out other new features).

One new feature which caught my eyes is that you can easily get the starting URL of your web application without scrolling and searching the WLS log output. You simply select the ‘Actions’ drop down box in log output window and select ‘Show Target URLs’ to get the starting URL you specified.

Show Target URL action

Show Target URL action


and your get
Start URL in Log window

Start URL in Log window


This is one feature I never asked for but will use it pretty often.