JDeveloper 12.2.1.4: responsive af:panelFormLayout

One of the new notable features in JDeveloper 12.2.1.4 is an extension to the af:panelFormLayout component that allows a responsive behavior of the component.

af:panelFormLayout before 12.2.1.4

The image above shows the typical af:panelFormLayout. Actually we see two af:panelFormLayout:

  1. At the top, we see an af:panelFormLayout where we can alter the layout with the af:selectOneChoice. This we use to investigate the new setting
  2. The one at the bottom that just shows the old af:panleFormLayout without any change. This is just there to better see the difference

In the image above both look the same. There is no responsiveness in either panelFormLayout. If the browser window gets smaller than the needed size for the two columns you get

We see that the label now takes two rows, but the inputText components in the second column are clipped.

Changes for 12.2.1.4

JDeveloper 12.2.1.4 added a new property ‘layout’, that changes this behavior to make the component more responsive to changes in the width of the window size.

The new property can have one of three possible values:

  1. Weighted: this is the default. It will size the panelFormLayout to the size to the content and then break the labels if the space gets too small.
  2. Fixed: will size to the width of the screen and truncate the labels and fields
  3. Responsive: will auto arrange the fields in multiple columns and manage labelAlignment based on the space available for the panel

Weighted layout

As this is the default layout (and the only one before 12.2.1.4) we skip this here.

Fixed layout

The image above shows the difference between ‘fixed’ layout (top part) and ‘weighted’ layout (bottom part). Reducing the width of the screen will result in

Still not what we call responsive.

Responsive layout

OK, let’s look at the ‘responsive’ property. If the container width if big enough, it almost looks like the ‘fixed’ layout (see image below).

When the width is reduced the labels are arranged above the input field, which quite an improvement over the weighted layout.

When the width is reduced further, the multi-column layout is reduced to a one-column layout

By default, ADF defined three points or width where the layout changes. These points are defined in the skin and can be changed by altering the skin

af|panelFormLayout {
  -tr-panel-size-sm: 768;
  -tr-panel-size-md: 1024;
  -tr-panel-size-lg: 1281; /* anything greater than this is xl */
}

If you need different points where the layout should change, you can easily change the skin and define different style classes for different scenarios.

The sample was built using JDeveloper 12.2.1.4.0 and can be found at GitHub under BlogResponsivePanelFormLayout.

JDeveloper 12.2.1.4: Exposing Custom Methods from ADFbc ViewObjects to REST API

The new JDeveloper version 12.2.1.4 brought some changes to the ADF business components REST features. One of the features I wondered about is how to expose custom methods built in a view object to the REST API. The release documentation at (https://docs.oracle.com/en/middleware/developer-tools/adf/12.2.1.4/develop/whats-new-this-guide-release-12c-12.2.1.4.0.html#GUID-F0E4CCC6-5419-4FFD-B2F3-E29B40A487DB) says

What does this mean?

The REST part in the documentation still shows some pictures showing the ‘Custom Method’ tab for a REST resource

However. Searching the documentation for ‘custom method’ doesn’t show any result.

Defining a public custom method in the employee’s view and adding it to the client interface (only then methods are visible from the outside)

Looking at the same dialog in JDeveloper 12.2.1.4 does not show the ‘custom method’ tab 😦

So, is the documentation correct?

NO, a hint from @JDeveloper in a tweet gives a hint on this.

Methods are exposed by default!

But how to find them?

Well, let’s try the ‘describe’ operation on the VO using the URL ‘http://127.0.0.1:7101/BlogAdfRestMethod-RESTWebService-context-root/rest/v0/Employees/describe’. This will return plenty of information about the REST API. There we find the information about the Method ‘getVersionInfo’ which I wrote as a custom method in the EmployeesViewObjectImpl class.

To see the method in the REST API, the method must be part of the client interface. This is what the tweet means by ‘updating the metadata’, I guess.

Now, to call the method we use a POST request with the content type set to ‘application/vnd.oracle.adf.action+json’ as noted in the description of the method. The body of the POST request is set to

{
    "name" : "getVersionInfo"
}

Sending the request will give us

I added another method to the EmployeesViewObjImpl method

Named ‘sayHello’. This method I did not expose to the client interface. As we see in the description, this method is not exposed in the REST API. After exposing the method to the client interface too, we get this method in the REST API too.

Now, the description lists

So, the new method can be called via REST too

You can download the sample application from GitHub BlogAdfRestMethod. The sample was built using JDeveloper 12.2.1.4 and uses the HR DB schema.

JDeveloper 12.2.1.4: af:chooseDate

The new JDeveloper 12.2.1.4 has added an enhancement an a new feature to the af:chooseDate component. We are looking at both in this article.

Enhancement

The enhancement is that you can now set a default date which is shown as the selected date when the field you show the af:chooseDate for is empty. In the older JDeveloper versions, the selected date was always the current date. The image below shows the situation of how the af:chooseDate component looks like:

The af:inputText ‘Datum’ is not set and because of this, the af:chooseDate shows the current date (when I took the picture) as the selected date. Problem is, that there are use cases where you don’T want the current date to be the selected date, e.g. your use case might want to show the next Friday after the current date as the selected date. This was not easily possible.

Now, the enhancement to the af:hcooseDate component allows us to set a default date via the property ‘defaultValue’. When set to a literal value, this will be parsed as “yyyy-MM-dd hh:mm:ss” or “yyyy-MM-dd”.

I added an af:inputdate ‘Set DefaultDate’ to allow selection of a date which then will be used as the default date in the af:chooseDate for the ‘Datum’ field. The current date is marked with gray and the date set in the ‘Set Default Date’ is shown as the selected date.

As you see, the ‘Datum’ field is still empty. If you select a date like ‘12/20/2019’ you get

Sample Code

<af:inputDate label="Set Default Date:"
     Nhn ng  value="#{bindings.defaultDate1.inputValue}" id="id2"
     autoSubmit="true"/>
<af:spacer id="s1" height="10px"/>
<af:panelGroupLayout id="pgl2" layout="horizontal">
<af:inputDate label="Datum" id="id1" chooseId="cd1"/>
<af:chooseDate id="cd1"
    defaultValue="#{bindings.defaultDate1.inputValue}"
    partialTriggers="id2"/>

New Feature: “Multiple Date Selection”

The af:chooseDate component has another feature added. Now you can select multiple dates at once. This feature is very handy as you can now use one af:chooseDate to get a start date and an end date or date ranges. The image below shows the component with multiple dates selected.

The interesting thing is, that there is no implementation for this hardcoded in the component. Meaning is that we, the developers, need to implement this feature ourselves. For this ADF provides two new client events ‘load’ and ‘dateSelection’ that are triggered by the af:chooseDate component. We use these client events to implement the multi-select feature. The load event can be used to pass an array of dates to the component which are then shown as selected. This event is triggered when the component loads, as the name implies. The dateSelection event is triggered each time a user selects a date. It passes information about the keyboard state so that we can handle range selection.

To make it easier, the ADF Rich Client Demo has a sample implementation of the needed JavaScript code which I used as the base of my implementation:

/**
 * Shows a popup from an "action" type event.
 * @param {AdfActionEvent} actionEvent the event being handled
 */
var dates = [];
var minDate;
var maxDate;

function dateSelectionEventHandler(event) {
    var eventSource = event.getSource();
    var selectedDate = event.getSelectedDate();
    var modifier = event.getModifiers();

    if (modifier.indexOf(AdfRichChooseDate.MULTI_SELECTION) !=  - 1) {
        dates.push(selectedDate);
    }
    else if (modifier.indexOf(AdfRichChooseDate.RANGE_SELECTION) !=  - 1) {
        if (!minDate || (minDate.getTime() > selectedDate.getTime())) {
            minDate = selectedDate;
        }
        if (!maxDate || (maxDate.getTime() < selectedDate.getTime())) {
            maxDate = selectedDate;
        }
        var timeDiff = Math.abs(maxDate.getTime() - minDate.getTime());
        var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)) + 1;
        dates = []
        for (var i = 0;i < diffDays;i++) {
            var selDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate() + i, 0, 0, 0, 0);
            if (!eventSource.isDisabled(selDate))
                dates.push(selDate)
        }
    }
    else if (modifier.indexOf(AdfRichChooseDate.SINGLE_SELECTION) !=  - 1) {
        minDate = null;
        maxDate = null;
        dates = []
        dates.push(selectedDate)
    }
    eventSource.setSelectedDates(dates);
}

function chooseDateLoadEventHandler(event) {
    var eventSource = event.getSource();
    eventSource.setSelectedDates(dates);
}

function processSelectedDates(event) {
    component = event.getSource();
    AdfCustomEvent.queue(component, "processSelectedDates", 
    {
        payload : dates
    },
    true);
    event.cancel();
}

There are three JavaScript funtions:

  1. dateSelectionEventHandler(event) handles the selection of one or more dates
  2. chooseDateLoadEventHandler(event) handles the inital load of hte component and allows us to set dates as selected
  3. processSelectedDates(event) is the funtion which passes the selected dates to a server event

The functions are added to the af:chooseDate compponent as client listerner

<af:chooseDate id="cd2" clientComponent="true">
    <af:clientListener type="dateSelection" 
        method="dateSelectionEventHandler"/>
    <af:clientListener type="load" method="chooseDateLoadEventHandler"/>
</af:chooseDate>

To pass the selected dates to a server event a button is used like

<af:button text="Selected Dates" id="b2">
    <af:clientListener method="processSelectedDates" type="action"/>
    <af:serverListener type="processSelectedDates"
        method="#{viewScope.ChooseDateBean.procressSelectedDates}"/>
 </af:button>

The image below shows the selected dates once the button ‘Selected Dates’ is clicked.

This JavaScript function simply passes the array of selected dates as payload from the client to the server. There the dates are just stored in a bean variable. This variable is used to show the dates in an af:outputText.

The same works for a range selection as shown below

And after the button ‘Selected Dates’ is clicked we get

The sample can be downloaded from GitHub BlogChooseDate or directly as a zip. The sample is written using JDeveloper 12.2.1.4 and doesn’t use any DB connection.

JDeveloper 12.2.1.4: af:panelTabbed

The new JDeveloper 12.2.1.4 provides some new features and enhancements. In the next couple of posts we are going to investigate some of them.

We start with the af:panelTabbed component. There is an enhancement fro this component we all waited for a long time. Vertical stacked tabs with icons and text. This sounds like a small thing, but in earlier versions of ADF vertical stacked tabs could only show icons.

We are calling tabs vertical stacked, if the tab is on the left or right side of the panel. Horizontal stacked tabs are the ones at the top or the button of the panel.

Vertical Stacked Tab with Icons and Text

As said before, in earlier versions of ADF you needed to do a workaround to get tabs on the left or right showing an icon, text or both. You needed to create an icon from the icon, Text or icon with text and add it to the af:showDetailItem. The drawback was that you couldn’t just edit the text (e.g. correcting a spelling mistake) or had to generate different such icons for different languages along with some logic to exchange the icon according to the selected language.

The new af:panelTabbed has a new property ‘verticalTabMode‘ with two modes:

  • iconOnly this mode behaves like the old versions and only shows an icon defined for the af:showDetailItem
  • iconAndText this mode shows an icon and the text defined in the text property of the af:showDetailItem
af:panelTabbed with verticalTabMode=”iconOnly’
af:panelTabbed with verticalTabMode=”iconAndText’

The images above show the af:panelTabbed with the new verticalTabMode. The sample application for this can be downloaded from GitHub BlogPanelTabbed. It does not use a DB connection and was built using JDeveloper 12.2.1.4.

JDeveloper 12.2.1.4.0: First Impressions

27th Sep 2019, the new JDeveloper version 12.2.1.4.0 became publicly available. We waited two years for the latest version. Is this version worse waiting for it?

In the next couple of blogs, I go through some of the new features offered. Before I start with the new features, I summarize my findings.

The list of fixed bugs available from https://www.oracle.com/technologies/developer-tools/jdeveloper/12214rn.html is impressive. However, some of the bug descriptions are not descriptive without knowing the actual case. Anyway, I found many bugs I filed for 12.2.1.x versions. You should read through the list, and you’ll find that some bugs which you have run into have been fixed in 12.2.1.4.

When 12.2.1.4 became available the documentation about what has changed or which features have been added, was thin, very thin. In the last couple of weeks, this documentation became available too. Some broken links have also been corrected. You should look at the JDeveloper homepage (https://www.oracle.com/technologies/developer-tools/jdeveloper/jdeveloper.html) regularly and search for new documents. Some other users and I ask support and the product management to add documentation which we found missing. Oracle support and product management reacted very fast and provided everything we asked for within a couple of weeks.

I’m using JDev 12.2.1.4 for four weeks now regularly. My impression is that the IDE is more stable. I do see some exceptions in the IDE, but the IDE keeps running. Older versions of the IDE (12.2.1.3, 12.2.1.2 and 12.2.1.1) sometimes crashed (once a day). It no fun to lose small parts of your latest changes. 

One defect (see https://community.oracle.com/thread/4299239) is reproducible in 12.2.1.4 too. This problem, while old, has just filed as an SR to support.oracle.com. Let’s hope Oracle can fix it ASAP.

I don’t see a significant performance gain in my daily work. Starting the integrated WLS takes a bit longer on my test machine. I’m running it on a LINUX system (Ubuntu 18.04 with 8GB RAM and JDK 1.8.0_221, 4 CPUs) in a VirtualBox. For me, it looks like the minimal configuration you can work on. I can work fine, but it’s not cutting edge. I don’t have to wait too long for audits or insights. I know it becomes better with more resources. In the next couple of months, I’ll move the installation to a better setup with more memory and more CPUs. 

The first task, as usual, was to create a new ADF Fusion Web Application, add some business components from DB tables and build a small UI with the created objects. As I said before, the list of fixed bugs is impressive. However, making this first application, I found new problems. 

The wizard used to build ‘Business Components from Tables’ doesn’t obey all the package names specified. The ApplicationModule package is simply ignored. It’s not a big deal, as you can refactor this easily. If you specify the package names globally (Tools->Preferences->ADF Business Components->Packages) it works OK. Another problem using this wizard is that an application module is generated, even if you deselected the option for this. This problem is known in the community (I learned that other users have this problem in older JDev versions), but it seems unknown to Oracle. Maybe nobody filed an SR for this. I submitted an SR for this now.

Migrating some of my projects to 12.2.1.4 (from 12.2.1.1, 12.2.1.3) worked smoothly. All applications I wrote from my blog articles (12c only) migrated without a problem. I could run all of them too.

One problem I run into is using Maven as a build tool for ADF applications. There were problems using Maven as a build tool in old 12c JDeveloper versions. Oracle fixed a lot of bugs regarding Maven. While testing Maven again in 12.2.1.4.0 I found most of the older bugs fixed, but I found a new one. I can’t say that they fixed all Maven bugs as the newly found bug prevents the complete test. Running JUnit test for business components from Maven doesn’t work. Somehow the fixture used to create the application module can’t create the JDBC connection because the credentials can’t be found or are wrong. Oracle is investigating this problem. For now, Maven testing is stalled.

The next thing I tried was installing the Rich Client Demo on my test machine. The last time I tried this (for 12.2.1.1) this process was laborious. Once you had installed the demo all you could do was running it. There is a new description of how to install the demo on your client machine (https://www.oracle.com/technetwork/developer-tools/adf/documentation/adf-faces-rc-demo-083799.html). This description isn’t perfect as you need to add two more libraries (‘ADF Model Runtime’ and ‘apache-commons-lang-2.6’), but it’s simple to follow. The client demo runs on my test installation. I can even change things (or debug it). I’m not sure if this was possible for older version, I simply can’t remember the last time I tried to do this.

I found some other small problems which I disclose in one of the next blogs when I start to show some of the new features or enhancements of JDeveloper 12.2.1.4.0

I expected to find some problems. Some of them are stupid or embarrassing. Still, I like the new version. Keep in mind that I did not mention all the good stuff which works OK.  

JDev 12c: Multi Line Button

An interesting question came up in the JDeveloper & ADF ODC space. A user asked how to display a button which shows a long text in multiple lines.

The image above shows an af:button with a longer text. If you don’t have enough space in your layout to show such a long text in a button, you can shorten the Text. If this is not a acceptable, one solution is to break the long text into multiple lines.

Think about an af:panelSplitter which should show the same button

but the space i for the left pane in the splitter s limited. The result will be that the text of the button can’t be read. In other layouts the button might overflow the given space. his can crumble your whole page layout.

In this blog I’ll show you how to design a button which can handle this situation by showing the text in multiple lines. The green dotted rectangles shows the size of the layout container. This is for information only.

As you see in the image above, the text of the button breaks into multiple lines if the space is not wide enough to show it in one line. If we move the splitter to the right you see the effect

Solution

The solution is to create a style class for the button which we use for button which should be able to show their text in multiple lines. This style class is put into a skin to make it available to the ADF application.

.multiLineButton af|button::text {
    white-space: normal; 
}

The usage of the style class is simple as we see in the sample code for the af:panelSplitter

<af:panelSplitter id="ps1" splitterPosition="100" orientation="horizontal" dimensionsFrom="parent">
	<f:facet name="first">
	        <af:button text="This Button has a very long text to show" id="b3"/>
        </f:facet>
        <f:facet name="second">
                <af:button text="This Button has a very long text to show" id="b4"
                           styleClass="multiLineButton"/>
        </f:facet>
</af:panelSplitter>

You can download the sample BlogMultilineButton (or the zipped workspace) from GihHub. The code was developed using JDeveloper 12.2.1.3 and doesn’t use a DB connection.

Update: InputNumberSpinbox without Spin inside af:query

A user asked how to get rid of the spin buttons if the InputNumberSpinbox is used in an af:query component?

Whenever you have a number attribute in a VO and use it in a view criteria which you then use to show an af:query using this view criteria, the af:query uses an af:inputNumberSpinBox in the query panel to allow the user to enter values. The problem is, that you can’t control how the components rendered inside an af:query is rendered. There are no properties you can change which are available if you use the same component directly.

af:query with af:inputNumberSpinBox and spin buttons

Using the af:inputNumberSpinBox in the af:query has the same advantage as I mentioned in the original post.

And the same disadvantage too. In most cases, you don’t want or need the up/down buttons to select a number. Well, this can be done by adding the same style class we added to the af:numberInputSpinBox to the af:query component. The result can be seen in the below image

And we get the same behavior inside the af:query component too.

The reason this works is, that the af:query uses an af:inputNumberSpinBox in the query panel. The skin selector we used in the skin file works for the af:numberInputSpinBox too.

As you see, if you select the af:query in the skin file and hover over the spin button, it tells you which selector is used. This is the same selector we use in our skin file

I added another sample which I extended from the original one build with JDeveloper 12.2.1.1.0. This sample uses the HR DB schema and can be downloaded from GitHub BlogInputSpinBoxWithoutSpinV2

InputNumberSpinbox without Spin

ADF offers a wide range of components which allow user to input data. There is a build in intelligence which chooses the ‚right‘ component for the given data type when you create the UI from a data control. This allows e.g. to create a form to input data which e.g. covers the basic formatting and error handling of the data types for the given fields.

From my point of view, one wrong decision is to use the af:inputNumberSpinbox for Integer and BigInteger data types. Setting a bigger number using the spin boxes isn’t working for most people, at least not for me.

The spin buttons are of no real use in most cases. In some versions of JDev the buttons are skinned too small so that it’s hard to use them at all. There are some cases, when the range of numbers is minimal, where using the spin buttons is OK.

What I like about the component is the build in error handling if I try to input anything but a number

without doing anything to the component. This is the code used for the above image

<af:inputNumberSpinbox label="Spinnumber" id="ins2"
    value="#{bindings.myNumber21.inputValue}"/>

As you see there in nothing but the component, still we get the right error message.

You can get the same result by using a normal af:inputText with an included af:numberConverter, but you need to know how to do this:

<af:inputText label="Number in af:inputText" id="it1"
    <af:convertNumber type="number" id="nc1" pattern="0"/>
</af:inputText>

This doesn’t look identical but close enough. One difference to note is that the af:inputText starts the input on the left whereas the af:inputNumberSpinbox aligns the numbers to the right. You can change this too with setting more properties on the component.

For this I like to use the af:inputNumberSpinbox without the spin buttons.

To make the af:inputNumberSpinbox usable I get rid of the spin buttons:

The component works like hte one with the spin buttons but look like a normal inputText

This can be done by changing the skin. If you like it can be done globally or you define a custom skin class and add this class where you don’t want to see the spin butons:

.nospin af|inputNumberSpinbox::incrementor-icon-style {
    display: none;
}

.nospin af|inputNumberSpinbox::decrementor-icon-style {
    display: none;
}

The ‘.nospin‘ is the name of the custom style class you can use on the af:inputNumberSpinbox to turn the spin buttons off.

Here is the part of the page

<af:panelGroupLayout id="pgl2" layout="vertical" inlineStyle="padding-left:20px;">
    <af:inputNumberSpinbox label="Number" id="ins1" value="#{bindings.myNumber1.inputValue}" styleClass="nospin"/>
    <af:spacer width="10" height="30" id="s1"/>
    <af:inputNumberSpinbox label="Spinnumber" id="ins2"
        value="#{bindings.myNumber21.inputValue}"/>
    <af:spacer width="10" height="30" id="s2"/>
    <af:inputText label="Number in af:inputText" id="it1">
       <af:convertNumber type="number" id="nc1" pattern="0"/>
    </af:inputText>
    <af:spacer width="10" height="30" id="s3"/>
    <af:button text="Submit" id="b1"/>
</af:panelGroupLayout>

You can download the sample from GitHub BlogInputSpinBoxWithoutSpin. The sample was built with JDeveloper 12.2.1.1.0 but should work with other versions too. There is no DB used or needed to run the sample.

Jdev 12c: Implementing SQL IN Clause in an ADF ViewObject Query or ViewCriteria (Part 2)

In part one, showed how to implement a SQL IN clause in ADF. Now I show how to use this technique in a ViewCriteria or directly in a query of a view object.

We have to solve a couple of problems before we can really use the technique from part one in a ViewCriteria. As you know, when using a ViewCriteria, you select an operator which in turn is translated into SQL code. So, we have to find a way to create a new operator which will then be used to create the needed SQL code.

The technique to do this comes from an older post. Please look at Extending ViewCriteria to use SQL CONTAINS where I showed the basics on how to do this. The older post was designed for JDeveloper 11.1.2.1.0. Using the current JDeveloper version 12.2.1.3 give some ways for improvement of the earlier code.

The first improvement is that JDeveloper 12.2.1.3 allows us to introduce custom operators to view criteria. In the older sample, I had to use the description field of the ViewCriteria to pass information which SQL to generate. Now we can define an operator named ‘IN’ and use it in the ViewCriteria like any other default operator.

The next problem is how to generate the SQL shown in part one when the new custom operator ‘IN’ should be used. One of the big advantages of ADF is reusability. We use a base class which extends from ViewObjectImpl and use this new base class in the project.

The base class is named BaseViewObjectForSqlInClause were we implement the needed method

public String getCriteriaItemClause(ViewCriteriaItem aVCI)

which gets called for each part or item of a ViewCriteria. See the code of hte base class below.

Base Class

public class BaseViewObjectForSqlInClause extends ViewObjectImpl {
    private static ADFLogger _logger = ADFLogger.createADFLogger(BaseViewObjectForSqlInClause.class);

    // comma-separated list of custom operators. Each custom operator muast have a ',' at the end as delimeter!
    private static final String CUSTOM_OPERATORS = "IN,";

    public BaseViewObjectForSqlInClause(String string, ViewDefImpl viewDefImpl) {
        super(string, viewDefImpl);
    }

    public BaseViewObjectForSqlInClause() {
        super();
    }

    /**
     * Check if a given criteria item tries to use an 'IN' operator using a bind parameter (comma seperated list of strings).
     * Create special SQL clause for 'IN' operator
     * @param aVCI Criteria item
     * @return where clause part for the criteria item
     */
    @Override
    public String getCriteriaItemClause(ViewCriteriaItem aVCI) {
        // we only handle the SQL 'IN' operator
        String sqloperator = aVCI.getOperator();
        // add comma to operator as delimiter
        boolean customOp = CUSTOM_OPERATORS.indexOf(sqloperator.concat(",")) >= 0;
        customOp |= sqloperator.indexOf("NVL") >= 0;
        if (customOp) {
            ArrayList<ViewCriteriaItemValue> lArrayList = aVCI.getValues();
            if (lArrayList != null && !lArrayList.isEmpty()) {
                // check if the criteria item has bind parameters (only the first if of interest here as the IN clause onlyallows one parameter)
                ViewCriteriaItemValue itemValue = (ViewCriteriaItemValue) lArrayList.get(0);
                if (itemValue.getIsBindVar()) {
                    // get variable and check if null values should be ignored for bind parameters
                    Variable lBindVariable = itemValue.getBindVariable();
                    Object obj = ensureVariableManager().getVariableValue(lBindVariable.getName());
                    boolean b = aVCI.isGenerateIsNullClauseForBindVariables();
                    if (b && obj == null) {
                        // if null values for bind variables should be ignored, use the default getCriteriaItemClause
                        return super.getCriteriaItemClause(aVCI);
                    }

                    try {
                        // we only handle strings data types for bind variables
                        String val = (String) obj;
                    } catch (Exception e) {
                        // the bind variabel has the wrong type! Only Strings are allowed
                        _logger.warning("Bind variabel for SQL " + sqloperator +
                                        " clause is not of type String! -> No custom SQL clause created! (Class: " +
                                        obj.getClass() + ", Content: " + obj + ", Variable: " +
                                        lBindVariable.getName() + ", View: " + this.getName() + ")");
                        String s = ":" + lBindVariable.getName() + " = :" + lBindVariable.getName();
                        return s;
                    }

                    // only handle queries send to the db
                    if (aVCI.getViewCriteria()
                            .getRootViewCriteria()
                            .isCriteriaForQuery()) {
                        String sql_clause = null;
                        switch (sqloperator) {
                        case "IN":
                            sql_clause = createINClause(aVCI, lBindVariable);
                            break;
                        default:
                            _logger.severe("Unknown custom operator '" + sqloperator + "' found! -> do nothing!");
                            break;
                        }

                        return sql_clause;
                    } else {
                        // bind variable not set or
                        // for in memory we don't need to anything so just return '1=1'
                        return "1=1";
                    }
                }
            }
        }

        return super.getCriteriaItemClause(aVCI);
    }

    private String createINClause(ViewCriteriaItem aVCI, Variable lBindVariable) {
        // start build the sql 'IN' where clause (COLUMN is the name of the column, bindParam the name of the bind variable):
        // COLUMN IN (SELECT regexp_substr(:bindParam,'[^,]+',1,level) FROM dual CONNECT BY regexp_substr(:bindParam,'[^,]+',1,level) IS NOT NULL
        // get flagg to create an sql where clause which ignores the case of the bind parameter
        boolean upper = aVCI.isUpperColumns();
        String sql_in_clause = null;
        StringBuilder sql = new StringBuilder();
        if (upper) {
            sql.append("UPPER(");
        }
        sql.append(aVCI.getColumnNameForQuery());
        if (upper) {
            sql.append(")");
        }
        sql.append(" ").append(aVCI.getOperator());
        sql.append(" (select regexp_substr(");
        if (upper) {
            sql.append("UPPER(");
        }
        sql.append(":");
        sql.append(lBindVariable.getName());
        if (upper) {
            sql.append(")");
        }
        sql.append(",'[^,]+', 1, level) from dual connect by regexp_substr(");
        if (upper) {
            sql.append("UPPER(");
        }
        sql.append(":").append(lBindVariable.getName());
        if (upper) {
            sql.append(")");
        }
        sql.append(", '[^,]+', 1, level) is not null)");
        sql_in_clause = sql.toString();

        _logger.finest("generated SQL-IN clause: " + sql_in_clause);

        return sql_in_clause;
    }
}

Using Base Class in Project

To use the base class in all new created ViewObjects of the project, we change the models project properties

Now, whenever you create a new ViewObject, the new base class is used and the SQL IN operator can be used in the VOs view criteria.

You can change any existing ViewObject to use the BaseViewObjectForSqlInClause by changing the extends clause in the class definition by hand.

Creating a ViewCriteria Using the Custom IN Operator

All pieces are in place and using the IN operator is pretty easy. We start by creating a new ViewObject named EmployeesOfDepartmentsViewCriteria

Now we have a ViewObject based on an EntityObject for the Employees. We need to make one change. The DepartmentId is an Integer type attribute, the comma-separated list is of type String (containing numbers). This doesn’t match. We add another attribute to the ViewObject of type String which we calculate from the DepartmentId Integer attribute. We change the SQL query for this by selecting the ‘Query’ node first unselecting the checkbox ‘Calculate Department Query at Runtime (recommended)’, second select the checkbox ‘Write Custom SQL’ and third add the line ‘to_char(Employees.DEPARTMENT_ID) DEPARTMENT_ID_STR,’ to the query.

Once this new ViewObject has been created, we add a ViewCriteria to it

If you like, you can turn off the checkbox ‘Ignore Case’ as it is not needed. The numbers are always lower case.

Running the ApplicationModule in the Tester

At this stage, we can test run the application module in the Application Module Tester (see JDeveloper & ADF: Use the Application Module Tester (BC4J Tester) to Test all your BusinessLogic).

Click the binocular button to select the ViewCriteria we created and click ‘Find’

Which will open a dialog asking for the value of the bind variable

Clicking ‘OK’ will show the result as

Running the ViewCriteria on a Page

Finally, we can add the ViewCriteria to a page as af:query and test it there. I’ll spare the exact howto here and just show hte running application.

Or with different parameters and spaces

You can download the sample application from GitHub BlogSqlInClause.

The Sample was built using JDeveloper 12.2.1.3 (but it should work in all 12c versions) and uses the HR DB schema.

JDev 12c: Implementing SQL IN Clause in an ADF ViewObject Query or ViewCriteria (Part 1)

There have been numerous questions about how to implement a SQL IN clause in ADF using a viewCriteria since the begin of life of ADF. There are a couple of solutions e.g. using an SQL array type or a DB table to store the values of the IN clause.

I came up with another solution which was using Oracle DBs CATSEARCH function or even CONTAINS search index.

All those solutions are more or less complex and need some programming to implement.

The solution I present in this blog is easy and elegant. However, it has its limitations still. Anyway, for about 90% of the use cases, I know where you want to add an SQL IN clause it works perfectly.

Problem

The problem is that you can’t simply define an IN clause in a query or ViewCriteria like

Select * from Employees where employee_id in (:pListOfValues)

Using this select statement as a query for a view object will not throw an error, but it won’t get you the desired result.

If you use a String type parameter for ‘pListOfValues’, e.g. “100, 110, 200” the query in the VO would look like

Select * from Employees where employee_id in (“100,110,200”)

And return nothing as a result. It is easy to see why: the parameter is expanded as a string, not a list of numbers. If you think you could overcome this by converting the employee_id to a string it will still not work as “100” IN “100, 110, 200” still won’t work.

Solution

A very elegant way to solve this problem is to change the query or where clause to

SELECT * FROM TABLE
  WHERE COLUMN IN (
    SELECT regexp_substr(:pListOfValues,'[^,]+',1,level) FROM dual
      CONNECT BY regexp_substr(:pListOfValues,'[^,]+',1,level) 
        IS NOT NULL)

The work is done by the select statement in the IN clause, This statement will split a comma-separated string in a series of values like a sub-select. If you run

SELECT
    regexp_substr(:pListOfvalues, '[^,]+', 1, level)
FROM
    dual
CONNECT BY
    regexp_substr(:pListOfvalues, '[^,]+', 1, level) IS NOT NULL

In a SQL worksheet and pass ‘11,12,15,17’ as ‘pListOfValues’ you get

You can pass any comma-separated string e.g. “1, hello, 444, world” and get

The SQL builds an internal table and add the values delimited by a comma to it. This internal table can then be used in the IN clause of another SQL statement.

Finally, running a complete query we can search e.g. for the employees which have the ID 100 or 110, or 180 or 176

In part 2, I’ll show how to implement this kind of query in a ViewCriteria of a ViewObject by adding a custom operator.