Blog Using External REST Servies (Part 1)

Using External REST Services with JDeveloper 12.2.1.2 (Part 1)

In this blog we look how we can use an external REST service with JDev 12.2.1.2. To make things more interesting we don’t use an ADF based REST service and we look how to get nested data into the UI.

For this sample we like to create an application which allows to search for music tracks and show the results in a table or listview. To get the music data we use a REST service and to display the data we use ADF faces application.

In part 1 we create the application and the project for the REST Data Control. Part 2 we will create the UI using the REST Data Control.

Setting up an external REST services

Let’s start be selecting a REST service which is available for public use without the need to get a key first. We use such a service to make it easier for you to run the sample and to look at the code. If we would need a key to use the API, you would need to register yourself with the service before you can run the sample.

There are a couple of such REST services like Spotify, iTunes or MusicBrainz which offer search APIs for music data as public REST service. Spotify we have to eliminate from the list as this service requires an API key since Mai 2017, meaning that it’s not public available without you register yourself before using it. ITunes REST API allows public access and the data structure returned is very simple. The result for a search get you everything in a flat structure. This will make things easy, too easy 🙂

For this sample where we like to show how to work with more complex data structures returned by a REST service. So, the final vote for this blog goes to MusicBrainz (MusicBrainz Rest API).

MusicBrainz

Musicbrainz REST API comes in different versions (V1 and V2). The current version v2 is what we are interested in as V1 is already deprecated. The documentation tell us, that the service is an XML style REST service. However, there is a JSON style REST service available too. This JSON style RSET service is what we use for the sample.

Before we implement the REST service calls we need to find out how to search for the data we like to show. For this a tool like Postman is a great help. Postman allows you to enter calls to REST services in a browser like UI. You can set all kind of headers, e.g. below we see a sample of the Postman UI (in the result a couple of sub structures are folded to show the relevant data). The query searched for recordings named ‘yesterday’ and asked for a result in JSON format:

To learn more about the possible searches refer to Web Service Search.The data structures and their meaning are described in in the MusicBrainz Data Structure.

Use Case

Before we begin implementing something which uses the external REST service we have to think about the use case. We like to implement a music title search using the external MusicBrainz REST service. A user should be able to enter a music title or part of a music title and as a result of the search she/he should get a list of titles, the artist or artists, the album and an id.

Creating a REST Web Service Project

After we looked at the REST Service and the data it returned we have identified the data we need to get from the REST service. The first step is to create a project which communicate with the REST service.

We create a normal Fusion Web Application which will create a ADF model project and a view controller project. If you need a script on how to do this you can look at Writing Reproducible Test Cases: Why and How. The model project we don’t need for this sample. You can delete it or just leave it empty.

For the access to the MusicBrainz data we create a new REST Web Service Project inside the application:

Name it and go through the rest of the wizard

Before we create the web service data control, we need to create a REST Connection from the resource pallete we create a new IDE connection of REST type

Then we later need data returned from the REST service which the wizard uses to produce the data structure. A simple way to get such data is to use e.g. Postman to call the REST service:

Copy the result (all of it!) and save it to a file. Next we create a new Web Service Data Control from the gallery

Select the ‘WebService Data Control (SOAP/REST)’

And fill out the wizard. Select the REST connection created before

In the next image click on the green ‘+’ sign

And change the path to ‘/ws/2/recording’, select JSON as data format and checkmark the GET method to enter ‘recording’ into the field.

In the next screen we need to select ‘Parse from Sample Code’ and copy the content of the file we saved from Postman into the textarea

And finally test the Web Service Data Control

The finish the wizard. Now we can test the data contron by finding the DataControl.dcx file in the project and right click on it. Choose ‘Run’ from the context menu:

In the dialog window right click hte data control and choose ‘Operations’

Fill in the fields and click the execute button

The result should look like

You can copy the return value into an editor to fully see it. If you don’t get the successful result, check the steps against the ones in the blog.

This concludes part 1 of this series. In part 2 we develop the UI for the application using the Web Service Data Control we created in this part. The source of the sample can be downloaded from GitHub. The link to it will be provided with part 2.

Advertisements

JDeveloper: How to setup and use a converter

JDeveloper: How to setup and use a converter

In this post I show how to setup the server side part of a converter and how to use it in an application. Converters can have a client side too and all af:converter do have one. For a nice sample on what you can do with client side converters see ADF: Smart Input Date Client Converter. The big difference is that the client side converter is done on the client side with JavaScript and no server round trip is done for the conversation.

Why are converters needed at all?

Sometimes the data you get from a source like the database table is not in a format you like to show to the user. Common cases are showing strings in special formatting, e.g. social security numbers or phone numbers. You can use converters to show the content of clob and blob columns in the UI too.

The ADF framework provided some converters out of the box:

These can be used without the need to program anything.

What is missing from the out of the box converters is one which can be used to format a string.

One thing to remember that the new format should only be used in the UI to show the data in a specific format. You normally don’t want to store it in this special format.

We create a converter which exchanges each uppercase character ‘B’ in a string with the string “-Z-”. The sample is not very useful, but it shows what can be done with converters.

Use Case

A string can contain any character. However when the string is shown on the UI there should be no ‘B’ visible. Instead of the ‘B’ we should show ‘-Z-’. This should only be done when the string is visible on the UI. When the string is stored in the db or some other place it should be stored with the ‘B’.

Implementation

I used JDev 11.1.1.7.0 for this sample, which is the oldest JDev version I have access to. The steps to create a converter should be almost equal in all versions, but I deliberately choose the oldest JDev I have so that other users with other version should have no problem migrating this sample to their version.

The final sample can be downloaded from GitHub at BlogConverterSample.

Model Project

We start by creating a fresh ADF Web Application. If you want a detailed description on how to do this, you can follow Writing Reproducible Test Cases: Why and How. For the model part I only use one DB table, the EMPLOYEES table. The resulting model project looks like

We don’t need to make any change to the generated project. This model project is only created to show that the converter works on data read from the DB table too.

ViewController Project

For users interested in more details about converters, please read the doc at http://docs.oracle.com/cd/E48682_01/web.1111/b31973/af_validate.htm#BABGIEDH. To start with the converter, we create a java class in the ViewController project and name it MyB2ZConverter.java. As package we choose ‘de.hahn.blog.convertersample.view.converter’

As the class will be a converter we have to implement the javax.faces.convert.Converter interface. For this you click on the green ‘+’ sign and can search for the right interface

This process will create the java class and two methods

These are the methods we have to implement for our use case. The first method ‘getAsObject’ is called when the data from the UI is send to the server for further processing. The ‘getAsString’ method is called when data from a storage (DB, bean property or pagedef variable) is going to be rendered to the UI.

As our use case is to exchange every “B” with the string “-Z-” we can implement the getAsString method easily by replacing every “B’ with “-Z-”. The method has three parameters, the current FacesContext which you can use to write messages, the UIComponent for which the converter is called and finally an Object representing the data which we want to convert. The result of the conversion must be a String. The resulting method look like

/** Method to get the string representation of hte object to use in the UI
* @param facesContext current facesContext
* @param uIComponent component which was used to deliver the data
* @param object data from storage to be converted
* @return sting to use for in the UI
*/
public String getAsString(FacesContext facesContext, UIComponent uIComponent, Object object) {
  if (object != null) {
    String ret = object.toString().replaceAll("B", "-Z-");
    return ret;
  } else {
    return null;
  }
}

After the check if the object to convert is null (in this case there is nothing to do), we use the String.replaceAll(…) method to search for ‘B’ and replace it with “-Z-”.

Keep in mind that the first parameter to the replaceAll method is a regular expression (see String.replaceAll(java.lang.String, java.lang.String)).

Now, if the data from the UI is send back to the model layer, it has to be converted back into the original format. So we have to do the conversion backwards by replacing all “-Z-” with “B” in the getAsObject(…) method:

/** Method which get the data from a uiComponent and should return it in the format we like to store in the DB (or elswhere)
* @param facesContext current facesContext
* @param uIComponent component which was used to deliver the data
* @param string data from the ui component
* @return object to use for further work (e.g. storage in the DB)
*/
public Object getAsObject(FacesContext facesContext, UIComponent uIComponent, String string) {
  if (string != null) {
    String ret = string.replaceAll("-Z-", "B");
    return ret;
  } else {
    return null;
  }
}

The result will be an Object which will be passed back to the model layer. If you don’t implement the getAsObject(…) method and just return the third parameter as resulting object, you would change every data in the back end to the new format. This may be your intention, but most often you don’t want to do this. It would mark every row of data dirty you have visited without any user interaction. This is because you pass different data back to the model than you read from it.

The last step to do is to register the custom converter in the faces-config.xml file of the ViewController project. Open the faces-config.xml file in JDev and select the ‘Converter’ tab

Click the green ‘+’ sign to get the an empty row in the converter section. Go to the property window and you see

Where we click on the ‘…’ button on the right end of the ‘Class’ field. We get the search for a class dialog where we look for the MyB2ZConverter class

Select the class and enter an ID fro the converter. This ID will be used in the UI to tell a component to use this converter.

Finally the converter section look like

UI Page

Now we can use the converter in a page or fragment. We start with a simple page where we define a inputText field and a button to submit the content of the field to see the converter working.

In the adfc-config.xml we add a JSPX page named ‘index’

And this page uses a quick layout as seen here

We add a title and the inputText field, a button to submit the data and two outputText fields to show what the converter has done to the data. The page layout looks like

or in code

If you like to copy the code use the following representation:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
 xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
 <jsp:directive.page contentType="text/html;charset=UTF-8"/>
 <f:view>
 <af:document id="d1">
 <af:form id="f1">
 <af:panelStretchLayout topHeight="50px" id="psl1">
 <f:facet name="top">
 <af:outputText value="Converter Sample" id="ot1" inlineStyle="font-size:x-large;"/>
 </f:facet>
 <f:facet name="center">
 <af:panelGroupLayout layout="scroll" xmlns:af="http://xmlns.oracle.com/adf/faces/rich" id="pgl1">
 <af:inputText label="Enter String" id="it1" value="#{bindings.myInput1.inputValue}">
 <f:converter converterId="B2ZConverter"/>
 </af:inputText>
 <af:commandButton text="refresh" id="cb1"/>
 <af:outputText value="current data: #{bindings.myInput1.inputValue}" id="ot2"/>
 <af:outputText value="current data with converter: #{bindings.myInput1.inputValue}" id="ot3">
 <f:converter converterId="B2ZConverter"/>
 </af:outputText>
 <af:commandButton text="Converter with DB Data" id="cb2" action="emp"/>
 </af:panelGroupLayout>
 <!-- id="af_one_column_header_stretched" -->
 </f:facet>
 </af:panelStretchLayout>
 </af:form>
 </af:document>
 </f:view>
</jsp:root>

Hint: you might notice another component, a button which is later used to navigate to a second page. This is described later.

For the inputText field we need to store the data a user enters. For this we can either use a DB table, a bean property or a pagedef variable. We use a pagedef variable (more on see see Creating Variables and Attribute Bindings to Store Values Temporarily in the PageDef) which we bind to the value property of the inputText component (value=”#{bindings.myInput1.inputValue}”). The converter is setup by adding an f:converter tag like

<af:inputText label="Enter String" id="it1" value="#{bindings.myInput1.inputValue}">
  <f:converter converterId="B2ZConverter"/>
</af:inputText>

The converterId points to the ID defined in the faces-config.xml file. Running the page will show

Enter ‘Hello’ into the field and clicking outside the field (so that it looses the focus) will show

As we see, the two outputText fields don’t show anything as the data in not submitted jet. Clicking the ‘refresh’ button submits the data and the converter goes to action

Well, as the input did not have any ‘B’ nothing changes. So lets us add another word ‘Beta’ and click outside the inputText

As we did not submit the data to the server, we still see ‘Hello Beta’ and the outputText fields show ‘Hello’ both. Now click the ‘refresh’ button to get

The inputText has changed to the new format where the “B” is exchanged with the “-Z-”, however the outputtext ‘current data’ still shows the ‘Hello Beta’. The reason for this is that the data send to the binding layer was converted back using the getAsObject(…) method which exchanged the “-Z-” with “B”.

This implements the use case described at the beginning.

Now, to show that the same converter works with data from a DB table as well we add another two pages to the adfc-config.xml. One showing the employees in a read only table with a link on the employeeId which navigates to the employee details in a form.

The navigation to the second use case is done with the button mentioned earlier (‘Converter with DB data’)

Clicking on the button will show a table with employees where the EMail column was used to add the converter

The column tag looks like

<af:column sortProperty="#{bindings.EmployeesView1.hints.Email.name}" filterable="true" sortable="true"
    headerText="#{bindings.EmployeesView1.hints.Email.label}" id="c3">
  <af:outputText value="#{row.Email}" id="ot5">
    <f:converter converterId="B2ZConverter"/>
  </af:outputText>
</af:column>

Like with the inputText we just add a f:converter tag with the right ID “B2ZConverter”. With this use case we see why the getAsObject(…) method should undo the formatting. You don’t want to store the Email like this. You only want to show it this way, but not overwrite the correct Email fro the employee. You can check the DB data and see that the Email is still stored with the “B” and not the “-Z-”

To verify this we can click the link in the first column to goto the detail page of the selected employee

Again, we see the ‘Email’ in the new format and the original data ‘NO CONVERTER Email’ in the normal data. The tags used for this are

  <af:inputText value="#{bindings.Email.inputValue}" label="#{bindings.Email.hints.label}" required="#{bindings.Email.hints.mandatory}"
      columns="#{bindings.Email.hints.displayWidth}" maximumLength="#{bindings.Email.hints.precision}"
      shortDesc="#{bindings.Email.hints.tooltip}" id="it1">
    <f:validator binding="#{bindings.Email.validator}"/>
    <f:converter converterId="B2ZConverter"/>
  </af:inputText>
  <af:panelLabelAndMessage label="NO CONVERTER #{bindings.Email.hints.label}" id="plam1">
    <af:outputText value="#{bindings.Email.inputValue}" id="ot2"/>
  </af:panelLabelAndMessage>

When using the binding for the Email without the converter we see the data as it’s stored in the DB. Using the converter we see the converted data.

The sample was build with JDeveloper 11.1.1.17.0 using the HR DB schema. You can download the sample from GitHub BlogConverterSample.zip

JDev 12c: Change Label depending on Data in Field

A question on OTN forum JDev & ADF caught my attention. A user ask how to change the label of a field in an af:query depending on data entered in another field of the af:query.

This is an interesting problem as it can be used in other use cases, e.g. in forms, too.

Use case

Before going into detail on how this is implemented, let’s look at the use case in detail. Starting with a normal af:query component showing a search form for locations

We want to change the label of the ‘State’ field depending on the selected Value of the ‘CountryId’ field. The page is simply created by dragging the named criteria ‘All Queryable Attributes’ onto the page as ‘Query with Table’.

To make the UI more interesting we use an af:selectOneChoice to select the country. Depending on the selected country we like to show different labels for the ‘State’ field. If we select the ‘United States of America’ as country, the label should show ‘US States’, if we select ‘Germany’ we want to see ‘Bundesland’ and for Switzerland we want to show ‘Kanton’. For the remaining countries we show ‘State’.

Here we see that the label changed to ‘Kanton’ for the country Switzerland. Selecting the USA will change the label to ‘US State’

Implementation

To implement this we only need to add some groovy script to the model project. To be precise we add groovy to the attribute label of the view which is used in the UI for the af:query.

Adding the groovy to the view will guarantee that the UI always shows the effect. In the sample (download instructions below) you’ll find a second page where the view is dropped as a af:form. Running this page you’ll see the same effect.

OK, let’s have a look at the groovy script

if (CountryId == 'US') {
  return 'US State';
} else if (CountryId == 'DE') {
  return 'Bundesland';
} else if (CountryId == 'CH') {
  return 'Kanton';
} else if (CountryId != null) {
  return CountryId + ' State';
} else {
  return 'State';
}

The script checks for specific countries and depending on which country is currently selected it return a special label. For country ‘DE’ it return ‘Bundesland’, for country ‘CH’ it returns ‘Kanton’. For other countries we return the country code we get from the country attribute and add ‘State’ to it. A special case we have to handle is if country is null or empty. In this case we return ‘State’.

Below we see that we add the script to the attributes ‘UI Hint’ tab as ‘Label’. This is only possible in 12c, so if you are using an earlier version, you have to use java code to get the same result.

This is all we have to do. The sample which is build using JDev 12.2.1.2.0 can be downloaded from BlogChangeQueryLabel. The sample uses the HR DB schema.

Enable Oracle JCS to access External REST API

Enable Oracle JCS to access External REST API

For a training I’m preparing I had to implement a POC on how to access an external REST API and to make it available in an ADF application running in the Oracle Java Could Service.

This task sounds pretty easy, nevertheless it’s best to see this working before starting a training which in the end will not work.

I decided to use Spotify public available REST API at https://api.spotify.com for this task. I started by creating a simple Fusion Web Application using JDev version 12.2.1.2.0. To this project I added a custom model Project which I later used to add the REST DataControl pointing to the Spotify track search API.

As this post isn’t about how to create such a project and use it in your normal ADF application, I spare the details here and write this up in another blog post.

Implementing a REST DataControl is pretty straight forward and the sample application was set up quickly. The first problem you might run into, even on the local development machine is this

null

error you get when you try to access the public API from inside the WebLogicServer. To make this error searchable for other users here is a part of the stack trace

javax.net.ssl.SSLKeyException: Hostname verification failed: HostnameVerifier=weblogic.security.utils.SSLWLSHostnameVerifier, hostname=api.spotify.com.
at weblogic.security.SSL.jsseadapter.JaSSLEngine.doPostHandshake(JaSSLEngine.java:686)
at weblogic.security.SSL.jsseadapter.JaSSLEngine.doAction(JaSSLEngine.java:757)
at weblogic.security.SSL.jsseadapter.JaSSLEngine.unwrap(JaSSLEngine.java:133)
at weblogic.socket.JSSEFilterImpl.unwrap(JSSEFilterImpl.java:644)
at weblogic.socket.JSSEFilterImpl.unwrapAndHandleResults(JSSEFilterImpl.java:541)
at weblogic.socket.JSSEFilterImpl.doHandshake(JSSEFilterImpl.java:99)
at weblogic.socket.JSSEFilterImpl.doHandshake(JSSEFilterImpl.java:78)
at weblogic.socket.JSSESocket.startHandshake(JSSESocket.java:240)
at weblogic.net.http.HttpsClient.New(HttpsClient.java:574)
at weblogic.net.http.HttpsClient.New(HttpsClient.java:545)
at weblogic.net.http.HttpsURLConnection.connect(HttpsURLConnection.java:236)
at weblogic.net.http.HttpURLConnection.getInputStream(HttpURLConnection.java:685)
at weblogic.net.http.SOAPHttpsURLConnection.getInputStream(SOAPHttpsURLConnection.java:41)
at weblogic.net.http.HttpURLConnection.getResponseCode(HttpURLConnection.java:1545)
...

This problem can easily sorted out by changing the ‘Hostname Verification’ to custom and to specify ‘weblogic.security.utils.SSLWLSWildcardHostnameVerifier’ as ‘Custom Hostname Verifier’. Here are the detailed steps:

  1. Go to the WebLogic admin console -> Environment -> Servers -> Server -> Configuration -> SSL
  2. Under advanced options , change “Hostname Verification” from “BEA Hostname Verifier” to “Custom Hostname Verifier”.
  3. Set “Custom Hostname Verifier” to weblogic.security.utils.SSLWLSWildcardHostnameVerifier
  4. Click “Save” and then “Activate Changes”
  5. Restart your server.

After this the sample application will run on the local development machine or an stand alone webLogic Server.

Now the fun part begins, making the application run in the Oracle JCS. Here I started with setting up a new Java Cloud Service with a WebLogic Server of version 12.2.1.2 and deployed my local running application to this new JCS.

The application did start OK, but when I came to the point where the application tried to call the external REST API all I got is: NOTHING

I only saw a spinning cursor, no error message on the UI regardless of my exception handler. It turned out, that I did not wait long enough for the error message to come up. The REST call timed out eventually providing more info in the servers log file

Exception in invoking HTTP method GET from Rest data control. Cause: javax.ws.rs.ProcessingException: java.net.ConnectException: Tried all: 3 addresses, but could not connect over HTTPS to server: api.spotify.com port: 443

It looks like the external REST call is not allowed. Here is the REST call

https://api.spotify.com/v1/search?q=sorry&type=track

If you write this into your browser you’ll get a JSON string in return, something like

{
 "tracks" : {
 "href" : "https://api.spotify.com/v1/search?query=sorry&type=track&offset=0&limit=20",
 "items" : [ {
 "album" : {
 "album_type" : "album",
 "artists" : [ {
 "external_urls" : {
 "spotify" : "https://open.spotify.com/artist/1uNFoZAHBGtllmz…

telling me that the problem is somewhere with the JCS. I ask for help in the OTN Java Cloud Service space and got an answer that there must be a rule missing. This is true to some point as I tried to access the REST API with curl from the JCS command shell

null

which did not work either. The interesting part is that you can do the same from the DBCS command shell and get the right answer.

I tried to add a rule to allow the access but looking at the possible source and destinations lists this did not work. Only

  • OTD — The Oracle Traffic Director load balancer VMs
  • WLS_ADMIN_SERVER — The WebLogic Server Administration Server VM
  • WLS_MANAGED_SERVER — The WebLogic Server Managed Server VMs

are allowed as destinations and my rule should allow the managed server (source) to access the PUBLIC_INTERNET or allow access to https protocol port 443.

After some more reading and testing I found a solution, however I’m not sure if this is the best way to handle this. Anyway, for others users who run into the same problem here it is:

First you have to create a ‘Security List’ Which you name e.g. outbound_wlsms_https_traffic which denies incoming packages and allows outgoing packages

null

Next a ‘Security Rule’ can be created like

null

with source set as the managed server and destination the new security list. After that the access to the external REST API works.

Use LOV without af:selectoneChoice

A question on the JDev & ADF forum caught my attention. A user asked how to get the attribute value from a list of value (LOV) without using an af:selectOneChoise component. To make the use case clear, let’s look at a listview from the Departments table of the HR DB schema.

this will produce a very rudimentary output like

Selection_030

This doesn’t look charming. OK we can change this to something more meaningful like

Selection_031

But still we see only the key values instead meaningful attribute values like we get if we use a af:selectOneChoce component.

To get the output using an af:selectOneChoise we need to define list of values at the attributes in the view object, DepartmentsView in this case:

Now, when we drag the DepartmentsView onto a page and drop it as a form or table we would get the af:selectOneChoice component. However, if we create the listview again, nothing changes. JDev uses af:outputText components in this case.

To show the managers name behind the ManagerId, we can e.g. add another attribute to the view and get the manager name via a join in the sql query.

Or we put a af:selectOnChoice in the list view cell like we get for a cell in a table. This would look like

 <af:panelGroupLayout id="pgl3" layout="horizontal">
   <af:outputFormatted value="ID: #{item.bindings.ManagerId.inputValue} Name:" id="of2"/>
   <af:selectOneChoice value="#{item.bindings.ManagerId.inputValue}" label="#{row.bindings.ManagerId.label}"
     required="#{bindings.DepartmentsView1.hints.ManagerId.mandatory}"
     shortDesc="#{bindings.DepartmentsView1.hints.ManagerId.tooltip}" id="soc3" disabled="true">
     <f:selectItems value="#{item.bindings.ManagerId.items}" id="si3"/>
     <f:validator binding="#{item.bindings.ManagerId.validator}"/>
   </af:selectOneChoice>
 </af:panelGroupLayout>

and generate

Selection_039

The gray rectangle is because we have set the disabled property to true to disable the component. To get a better look we can set the readOnly property instead to get

Selection_040

which look much better. However to get this result we have to add a lot of tags to the page.

The final solution is to use the data which is present in the model to show the attribute name instead of the value like it’S done ba the framework for af:selectOneChoice. For this we only need one af:outputText tag like

 <af:outputFormatted value="ID: #{item.bindings.ManagerId.inputValue} Name: #{item.bindings.ManagerId.items[item.bindings.ManagerId.inputValue].label}"
 id="of1"/>

This will generate

Selection_041.png

The magic is the expression language

#{item.bindings.ManagerId.items[item.bindings.ManagerId.inputValue].label}"

which uses the items defined for the selectOneChoice and located the right display attribute in the collection using the attribute value.

You can download the sample application which is build with JDev 12.2.1.2 and uses the HR DB schema from GitHub BlogShowLOVattributeWithoutLOV

DOAG DevCamp 2017: How much cost the Oracle Cloud?

February 7th, I attended the DOAG Dev Camp 2017 in Hannover, Germany. The DevCamp is held in a ‘Bar Camp’ format, meaning that everybody can bring proposals for sessions at the day the bar camp takes place. The proposals are rated at the beginning and the auditorium can decide if they want to discuss the proposal or not.

img_20170208_124925

I pitched a session on the ‘Cost of the Oracle Cloud’ which was accepted by the other attendees. At the begin of the week (around 5th of February) Oracle changed the pricing of the Oracle Cloud Trials. Before this week you could get a trail which lasted one month, now you get a credit of 300$ which you can spend on Oracles Cloud offerings in one month. Once the 300$ are gone or the month is over you get a mail asking if you like to continue to use the cloud services which are then charged on your credit card. Yes, you have to give a valid credit card if you sign up for a trail. The card is not charged until you say so, accepting the continuation of the services.

Gone are the time when you could request another trail account using a different mail address (not that anybody did such a bad thing).

Anyway, I stumbled over this change when I look at the cloud.oracle.com page in preparation of the session I planned for the DevCamp.

selection_228

Well, you see that you get 300$ to spend on the cloud trail, but not how much a specific trail costs. From my experience with the cloud I know that you need DB Cloud, Storage Cloud, Compute Cloud and Java Cloud to get a Java Development environment for ADF in the cloud. Are the 300$ enough to get all those services for a decent amount of time?

In this session I tried to get an answer to this question. The attendees of the session helped to get through the different services, live during the session, to show the process!

The first impression was that nobody knows how the 300$ are spent on the services or which offerings to add up to get the price. One hint is that after the trail is over, you are charged in the ‘pay as you go’ mode. So we ended up adding the prices for the hourly services. We started with the Developer Cloud Service pricing table

selection_229 As you see, it’s for free, but you have to subscribe other cloud services like Java, DB, Storage and Compute. We started building a table where we added all the prices for the different services

selection_231

A question nobody could really answer was how many request are needed (in the above table). Only time can tell. As the amount looks small we decided to ‘forget’ about these cost for the moment. This question will be addressed to Oracle in the near future.

Finally you can add up all these numbers for all services you need and multiply them with the hours you plan to use the services. Assuming 8 hours per day for 21 days (168 hours) you end up with about 300$. Summary is that you can work with the 300$ for about a month, so there isn’t much of a change here.

However, you have to shut down the services when you not need them, otherwise the clock is ticking and your 300$ won’t last long.

For another sample we tried to Mobile Cloud Service. There you see a ‘Pricing’ tab, but we couldn’t find the ‘Try it’ or ‘Buy Now’  button. Clicking on the ‘Get started for free’ button transfers you back to the overview page.

selection_232

It look like you can’t get a Mobile Cloud Trail at the moment. This is possibly a glitch in the web page, but it might be by intention.

Best Things Last

In the final 10 minutes of the 45 minute session we decided to go for it and to ‘buy’ the Java Cloud Service

selection_233

We clicked on the ‘Buy Now’ and Eureka!

selection_234

You land on a page where Oracle put together some shapes of services for you and what they would cost you per month. The computed prise is dependent on data like hours you plan to use the service per month a number of OCPUs you like to use. Anyway you can easily change this settings and end up with a nice detailed prise information.

And there is a nice summary where you see the detailed calculation

selection_239

I would have liked this information up front!

Who’s clicking the ‘Buy Now’ button first thing?

To whom it may concern at Oracle, you should make this information available on the pricing page or even on the Cloud Service landing page!

JDeveloper: Advanced Skin Technique

This post is about an advanced technique to change the look and feel of an ADF application. Changes to the look & feel are normally done via a skin which you use to change descriptors which are used by the ADF components. The general technique to do this is described in many blogs and articles like ADF Faces Skin Editor – How to Work with It and the official documentation at Oracle ADF Skin Editor.

In this blog we look at an advanced technique which helps to change the look and feel of components like af:query and pf:panelCollection which you can’t change using the normal available descriptors. In the below image you see the Skin Editor showing the ADF components skin descriptors.

selection_985

Use Case

In this use case we work with the af:panelCollection component. This component is used to wrap af:tree, af:treeTable and af:table components to provide additional functions. From the documentation of af:panelCollection

A panel component that aggregates collection components like table, treeTable and tree to display standard/application menus, toolbars and statusbar items.

The default top level menu and toolbar items vary depending on the component used as the child of the panelCollection.

  • For table, tree and treeTable, the default top level menu item is View.
  • For table and treeTable with selectable columns, the default top level menu items are View and Format.
  • For table and treeTable, the default toolbar item is Detach.
  • For table and treeTable with selectable columns, the default top level toolbar items are Freeze, Detach and Wrap.
  • For tree and treeTable, if the pathStamp facet is used, the toolbar buttons Go Up, Go To Top, Show as Top also appear.

The component allows us to switch off some function

Value Turns off
statusBar Status bar
viewMenu ‘View’ menu
formatMenu ‘Format’ menu
columnsMenuItem ‘Columns’ sub-menu item
columnsMenuItem:col1,col20 Columns with column ID: ‘col1’ and ‘col20’ inside ‘Columns’ sub-menu
freezeMenuItem ‘Freeze’ menu item
detachMenuItem ‘Detach’ menu item
sortMenuItem ‘Sort’ menu item
reorderColumnsMenuItem ‘Reorder Columns’ menu item
resizeColumnsMenuItem ‘Resize Columns’ menu item
wrapMenuItem ‘Wrap’ menu item
showAsTopMenuItem Tree/TreeTable ‘Show As Top’ menu item
scrollToFirstMenuItem Tree/TreeTable ‘Scroll To First’ menu item
scrollToLastMenuItem Tree/TreeTable ‘Scroll To Last’ menu item
freezeToolbarItem ‘Freeze’ toolbar item
detachToolbarItem ‘Detach’ toolbar item
wrapToolbarItem ‘Wrap’ toolbar item
showAsTopToolbarItem Tree/TreeTable ‘Show As Top’ toolbar item
wrap ‘Wrap’ menu and toolbar items
freeze ‘Freeze’ menu and toolbar items
detach ‘Detach’ menu and toolbar items

As a sample the image below shows a normal af:panelCollection (upper half) and an af:panelCollection with the view menu and the toolbar switched off (lower half)

selection_979

Looking at the possible things to switch off we don’t see anything to switch off the ‘Query by Example’ (QBE) icon. There is no feature toggle to turn this function on or off. An easy way to get rid of the icon would be to make the table not filterable. However, if we like the table to be filterable but don’t want to show the icon to switch the feature off, we have to use an advanced skin technique.

What can we do to get rid of the icon in the tool bar?

The idea is to use a skin or special css to hide the icon or the container which holds the icon. To find the container we first inspect the page in the browser using the browsers ‘Developer Tools’ which you can reach by hitting F12 in your browser. Below you see Chrome 55 with activated ‘Developer Tools’

Selection_211.jpg

The image shows the toolbars QBE image as selected element on the page (left red rectangle) and the style classes which are in use for this element (right red rectangle). The names ‘.xfo’ and ‘.xfr’ are the names of the style classes. They are minimized to reduce the download size of the page, but they are not ‘readable’. 

The first thing to do is to make the names ‘readable’ for us. We need to know which skin selector generated the style class. For this we set a context parameter in the web.xml file

 <context-param>
 <param-name>org.apache.myfaces.trinidad.DISABLE_CONTENT_COMPRESSION</param-name>
 <param-value>true</param-value>
 </context-param>

Setting this parameter to true will show us the clear names. The image below shows the same selection only this time with the real names

selection_212

One other nice feature of the ‘Developer Tools’ is that you can inspect elements by just hover over them on the page. This allow us to easily find the element we want to hide via css. Click on the icon marked in hte below image

selection_213

and move the mouse cursor over the page. You see the HTML and the active styles of the element under the cursor. This feature we use to find an element which holds the icon we want to hide and which we can address via css .

selection_214

CSS allows us to address elements inside a skin selector.  For this you need to know the skin selector, the tag or container and it’s ID inside the selector you want to address. In the image above we see the ID of the icon container we want to hide as “id=’pc1:_qbeTbr'” and the container or tag itself which is a ‘div’. The skin selector is the af|panelCollection. With this information we can can change the style attached to the ‘div’ container with the id ‘*_qbeTbr’  in the af|panelCollection as

af|panelCollection div[id$='_qbeTbr'] {
    display: none;
}

This we can add to our skin.css file. However, if we just add it this way it’s changing all af:panelCollection in our application.  If we want this only to be active for specific af:panelColletion we can add a style class name like

af|panelCollection.myPCClass div[id$='_qbeTbr'] {
    display: none;
}

Now we can add the stale class name ‘myPCClass’ to the af:panelCollection when we like the QBE icon not to be shown

 <af:panelCollection id="pc1" styleClass="myPCClass">
   <f:facet name="menus"/>
   <f:facet name="toolbar"/>
   <af:table value="#{bindings.EmployeesView1.collectionModel}" ...
   ...
 <af:panelCollection id="pc2" featuresOff="detachToolbarItem viewMenu">
 <f:facet name="menus"/>
 <f:facet name="toolbar"/>
 ...

will generate this UI output

selection_217

 

As we see, the QBE icon is gone. In the original page we have placed two af:panelCollection components. As you added the new style class only to one of them, the other QBE icon is still visible.

Extending

You can use hte same technique for other complex ADF components like af:query. Here you can style the save button which normally not  supported.

Download

You can download the sample which is build using JDev 12.2.1.2.0  and uses the HR DB schema from GitHub BlogAdvancedSkin

JDeveloper: Prevent Navigation from Field on Failed Validation

This post shows how to implement a use case where you don’t want the user to leave a field which has a validation error. ADF does a good job enforcing that all validations are checked at the time you submit the data. However, if you want the user to correct an error in a field directly, you have to implement it yourself.

Use Case

A validation error can be a mandatory field which the user has not filled in or a where the user has filled in wrong data. In such a case you want that the user must correct the error before she/he is allowed to move to the next field.

Implementation

We start with a fragment whihc holds two af:inputText components. One we use to simulate a mandatory input text and the other for a normal input text field.

selection_973

The ‘Test’ button is used to submit the form. If we hit the Test button without any data in the fields we get

selection_974

as hte ID field is marks required. One you enter something into this field you can successfully submit the form

selection_975

That is the normal behavior ADF gives you out of the box. The use case  we implement here requires, that the user can’t leave the field once an error is shown for the field. So if the user sets the cursor to the ID field he can’t navigate away from it until he fixes the error. He can’t even click hte button. ADF would allow the user to leave the ID field to enter some value into the Name field.

So how do we prevent the user leaving the field by clicking in another field or clicking on a button?

We use an af:clientListener which listens for the ‘blur’ event checking if the field contains a value. If not, we set the focus back to the component and cancel the ongoing even. Setting the focus back to the component is essential as the blur event tell us that we loose the focus. This only happens if the user navigates away from the field.

 function setFocusWhenValidateFail(event) {
   var element = event.getSource();
   var val = element.getValue();
   var cid = element.getClientId();
   console.log("Value=" + val);
   if (val == null) {
     element.focus();
     event.cancel();
   }
}

The JavaScript function above shows this. This function is called via an af:clientListener we added to the af:inputText component

 <af:inputText label="ID (mandatory)" id="it1" required="true" 
     value="#{bindings.myId1.inputValue}" autoSubmit="true">
   <af:clientListener method="setFocusWhenValidateFail" type="blur"/>
 </af:inputText>

This is it. But wait, what if we like to check if the value entered by the user is valid?

Now, this add another complexity. For this we need to either code the validation in JavaScript or we have to call a bean method which can do the complex check on hte server side model. For easy checks it’s no problem to implement them in JavaScript in the ‘else’ part of the function above.

To call a server side method we need to add an af:serverListener which calls a bean method

 <af:inputText label="ID (mandatory)" id="it1" required="true" 
     value="#{bindings.myId1.inputValue}" autoSubmit="true">
   <af:clientListener method="setFocusWhenValidateFail" type="blur"/>
   <af:serverListener type="validateServerListener" 
       method="#{viewScope.InputValidationBean.handleValidationEvent}"/>
 </af:inputText>

and we change the JavaScript function

 function setFocusWhenValidateFail(event) {
   var element = event.getSource();
   var val = element.getValue();
   var cid = element.getClientId();
   console.log("Value=" + val);
   if (val == null) {
     element.focus();
     event.cancel();
   }
   else {
     console.log("call server with " + cid + " and " + val)
     //call bean method validateServerListener
     AdfCustomEvent.queue(element, "validateServerListener", 
       {
         fcid : cid, fvalue : val
       }, false);
     event.cancel();
   }
 }

Note that we add two parameters to the call to the bean method. The first parameter fcid is the client id of the component which calls the bean method. The second parameter fvalue is the value the user has entered into the field. We see why we need the parameters when we talk about the bean method.

In the bean we implement the custom validateServerListener method. First we get the two parameters from the ClientEvent and log them to the console. At this point we can e.g. call an application module method with the parameter  we got. In this simple same we just check the value for a specific value, in this case ’88’. When the value is ’88’ we add a  FacesMessage to the component with the clientId we got as second parameter.

However, just adding the FacesMessage wont be enough. At this point he focus has already shifted out of the component and the message would not displayed until the component is selected again. The event.cancel() in the JavaScript function does not prevent that the focus is shifted out of the component. The solution is to reset the focus to the component from the bean again. For this we add JavaScript code which will be executed after the event has finished. The JavaScript searches for the component by it’s Id and then calls component.focus()  to set the focus back to this component.

   public void handleValidationEvent(ClientEvent ce) {
        // get client id from event
        Object obj = ce.getParameters().get("fcid");
        String clientId = "";
        if (obj != null) {
            clientId = obj.toString();
        }

        // get field value from event     
        String val = "";
        Object obj2 = ce.getParameters().get("fvalue");
        if (obj2 != null) {
            val = obj2.toString();
        }
        
        logger.info("client id =" + clientId + " value=" + val);
        // do a check if hte value if OK
        // here we check against 88 to keep it simple. You can check against other model values here too!
        if ("88".equals(val)) {
            // check failed -> add message to the component and set the focus back to the component
            FacesContext facesContext = FacesContext.getCurrentInstance();
            FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "Wrong vlaue");
            facesContext.addMessage(clientId, msg);
            // javascript to set focus to component identified by it's clientId
            String script = "var t=document.getElementById('" + clientId + "::content');t.focus();";
            writeJavaScriptToClient(script);
        }
    }

    //generic, reusable helper method to call JavaScript on a client
    private void writeJavaScriptToClient(String script) {
        FacesContext fctx = FacesContext.getCurrentInstance();
        ExtendedRenderKitService erks = null;
        erks = Service.getRenderKitService(fctx, ExtendedRenderKitService.class);
        erks.addScript(fctx, script);
    }

Sample Application

You can download the sample application from GitHub BlogNoNavigationValidation. The sample doesn’t use a model layer, so no DB connection is needed.

 

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.

JDev12c: Searching an af:tree

On the JDev & ADF OTN space I got a question on how to search an af:tree and select and disclose the nodes found matching the search criteria.

Problem description

We like to search an af:tree component for string values and if we find the value we like to select the node where we found the string we searched for. If the node where we found the string is a child node we disclose the node to make it visible.

Final sample Application

I started with building a sample application and show the final result here:

selection_935

We see a tree and a check box and a search field. The checkbox is used to search only the data visible in the tree or the whole data model the tree is build on. The difference is that you build the tree from view objects which can hold more attributes than you like to show in the tree node. This is the case with the sample tree as we see when we search for e.g. ‘sa’ in the visible data

selection_936

When we unmark the check box and repeat the search we get

selection_937

As you see we found another node ‘2900 1739 Geneva’ which doesn’T have the searched string ‘sa’. A look into the data model, the row behind this node shows

selection_938

We see that the street address which we don’t show in the node has the search string. To show that the search works for every node we set the search field to ‘2’ and get hits in different levels

selection_939

The sample application can be downloaded from GitHub. For details on this see the end of this blog.

Implementation

Now that we saw the running final application let’s look at how to implement this. We start by creating a small ADF Fusion Web Application. Is you like to you can start by following the steps given in  Why and how to write reproducible test cases.

Model Layer

Once the base application is created we setup the data model we use to build the tree. For this sample we use ‘Regions’, ‘Countries’ and ‘Location’ of the HR DB schema. To build the model we can use the ‘Create Business Components from Table’ wizard and end up with

selection_942

As you see I’ve renamed the views. The names now show what you’ll see when you use them. We only have one top level view object ‘RegionsView’ which will be the root of our tree in the UI. The child view are used to show detailed data.

View Controller

For the view controller layer we start by a simple page from the ‘Quick Layout’ section

selection_943

Now we add a title and add an af:splitter to the content area. Here we set the width of the first facet to 250 px to have enough room for the search field. We start with building the af:tree from the data control by dragging the ‘RegionsView’ from the data control onto the content area and dropping it as af:tree

Here we don’t select to show all attributes available but only a few.  Later we see that we can search the whole data model and not just only the visible data. Finally we bind the tree to a bean attribute to have access to the tree from the bean when we have searched it. This is a pure convenience, we could search the component tree each time we need the component to avoid the binding to a bean attribute.  When we create the bean we name it ‘TreeSelectionBean’ and set its scope to ‘Request’.  The bean will end in the adfc-config.xml

selection_950

the final code for the af:tree looks like

<af:tree value="#{bindings.RegionsView.treeModel}" var="node"
selectionListener="#{bindings.RegionsView.treeModel.makeCurrent}"
rowSelection="single" id="t1"
binding="#{TreeSelectionBean.tree}">
  <f:facet name="nodeStamp">
    <af:outputText value="#{node}" id="ot2"/>
  </f:facet>
</af:tree>

Now we create two pageDef variables as java.lang.String to hold the search string and the selection for the check box. If you need more information on how to create pageDef variables see Creating Variables and Attribute Bindings to Store Values Temporarily in the PageDef.

selection_949

In the first facet we add a check box and an af:inputText inside an af:panelGroupLayout and bind the value properties to the pageDef variables as

<af:panelGroupLayout id="pgl2" layout="vertical">
  <af:selectBooleanCheckbox text="node only" label="Seach" id="sbc1"
value="#{bindings.myNodeOnly1.inputValue}"/>
  <af:inputText label="Search for" id="it1" value="#{bindings.mySearchString1.inputValue}"/>
  <af:button text="Select" id="b1"
actionListener="#{TreeSelectionBean.onSelection}"/>
</af:panelGroupLayout>

The final thing to do is to wire the button to a bean method which does all the hard work. In the code above this is done with an actionListener which is pointing to the same bean created for the tree binding.


<span></span>public void onSelection(ActionEvent actionEvent) {
<span></span>JUCtrlHierBinding treeBinding = null;
// get the binding container
<span></span>BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
<span></span> // get an ADF attributevalue from the ADF page definitions
<span></span> AttributeBinding attr = (AttributeBinding) bindings.getControlBinding("mySearchString1");
 String node = (String) attr.getInputValue();

// nothing to search!
 // clear selected nodes
<span></span> if (node == null || node.isEmpty()){
<span></span> RichTree tree = getTree();
<span></span> RowKeySet rks = new RowKeySetImpl();
<span></span> tree.setDisclosedRowKeys(rks);
 //refresh the tree after the search
<span></span> AdfFacesContext.getCurrentInstance().addPartialTarget(getTree());

return;
 }

<span></span> // get an ADF attributevalue from the ADF page definitions
<span></span> AttributeBinding attrNodeOnly = (AttributeBinding) bindings.getControlBinding("myNodeOnly1");
<span></span> String strNodeOnly = (String) attrNodeOnly.getInputValue();
<span></span> // if not initializued set it to false!
<span></span> if (strNodeOnly == null) {
<span></span> strNodeOnly = "false";
 }
<span></span> _logger.info("Information: search node only: " + strNodeOnly);

<span></span>//Get the JUCtrlHierbinding reference from the PageDef
<span></span> // For JDev 12c use the next two lines to get the treebinding
<span></span> TreeModel tmodel = (TreeModel) getTree().getValue();
<span></span> treeBinding = (JUCtrlHierBinding) tmodel.getWrappedData();
<span></span> // For JDev 11g use the next two lines to get the treebinding
<span></span> // CollectionModel collectionModel = (CollectionModel)getTree().getValue();
<span></span> // treeBinding = (JUCtrlHierBinding)collectionModel.getWrappedData();
<span></span> _logger.info("Information tree value:" + treeBinding);

//Define a node to search in. In this example, the root node
 //is used
<span></span> JUCtrlHierNodeBinding root = treeBinding.getRootNodeBinding();
 //However, if the user used the "Show as Top" context menu option to
 //shorten the tree display, then we only search starting from this
 //top mode
<span></span> List topNode = (List) getTree().getFocusRowKey();
<span></span> if (topNode != null) {
 //make top node the root node for the search
<span></span> root = treeBinding.findNodeByKeyPath(topNode);
 }
<span></span> RichTree tree = getTree();
<span></span> RowKeySet rks = searchTreeNode(root, node.toString(), strNodeOnly);
<span></span> tree.setSelectedRowKeys(rks);
 //define the row key set that determines the nodes to disclose.
<span></span> RowKeySet disclosedRowKeySet = buildDiscloseRowKeySet(treeBinding, rks);
<span></span> tree.setDisclosedRowKeys(disclosedRowKeySet);
 //refresh the tree after the search
<span></span> AdfFacesContext.getCurrentInstance().addPartialTarget(tree);
 }

In line 4-7 we get the value the user entered into the search field. Lines 9-19 check if the user has given a search string. If not we clear the currently selected nodes from the tree by creating a new empty RowKeySet and setting this to the tree.

If he got a search string we check if we should search the visible data only or the whole data model. This is done by getting the value from the check box (lines 21-28). Now we data from the tree (lines 30-37).

One thing we have to check before starting the search is if the user has used the ‘show as top’ feature of the tree. This would mean that we only search beginning from the current top node down (lines 39-49).

The search is done in a method

private RowKeySet searchTreeNode(JUCtrlHierNodeBinding node, String searchString, String nodeOnly)

this we pass the start node, the search string and a flag if we want to search the whole data model or only the visible part. The method returns a RowKeySet containing the keys to the rows containing the search string (line 51-52). This list of row keys we set to the tree as selected rows (line 54). As we would like to disclose all rows which we have found, we have to do one more step. This step uses the row key and traverses upward in the tree to add all parent node until the node is found where we started the search (line 53-55). This is necessary as you only see a disclosed child node in a tree if the parent node is disclosed too. For this we you a helper method (line 54) and set the row keys as disclosed rows in the tree.


 /**
<span></span> * Helper method that returns a list of parent node for the RowKeySet
<span></span> * passed as the keys argument. The RowKeySet can be used to disclose
 * the folders in which the keys reside. Node that to disclose a full
<span></span> * branch, all RowKeySet that are in the path must be defined
 *
<span></span> * @param treeBinding ADF tree binding instance read from the PageDef
 * file
<span></span> * @param keys RowKeySet containing List entries of oracle.jbo.Key
<span></span> * @return RowKeySet of parent keys to disclose
 */
<span></span> private RowKeySet buildDiscloseRowKeySet(JUCtrlHierBinding treeBinding, RowKeySet keys) {
<span></span> RowKeySetImpl discloseRowKeySet = new RowKeySetImpl();
<span></span> Iterator iter = keys.iterator();
 while (iter.hasNext()) {
<span></span> List keyPath = (List) iter.next();
<span></span> JUCtrlHierNodeBinding node = treeBinding.findNodeByKeyPath(keyPath);
<span></span> if (node != null && node.getParent() != null && !node.getParent().getKeyPath().isEmpty()) {
 //store the parent path
<span></span> discloseRowKeySet.add(node.getParent().getKeyPath());
 //call method recursively until no parents are found
<span></span> RowKeySetImpl parentKeySet = new RowKeySetImpl();
<span></span> parentKeySet.add(node.getParent().getKeyPath());
<span></span> RowKeySet rks = buildDiscloseRowKeySet(treeBinding, parentKeySet);
<span></span> discloseRowKeySet.addAll(rks);
 }
 }
<span></span> return discloseRowKeySet;
 }

This concludes the implementation of the sear in a tree.

Download

The sample application uses the HR DB schema and can be downloaded from GitHub

The sample was build using JDev 12.2.1.2.