The Git Experience (Part 3)

Almost a year ago I posted the second part of my git experience series (JDeveloper 11.1.1.6.0: The Git Experience (Part 2)). In this part I announced a third part which should handle working with remote git repositories.

I totally forgot about this. Only after a question on OTN JDeveloper and ADF forum I looked up the older blogs about git. Since the last post a new JDev version 12.1.3 has arrived so I decided to use this new version to continue the series.

In this part we are talking about setting up a remote repository. As remote git server I use GitHub which allows you to easily set up an account for non commercial use without any cost. Please read the all about the process on the GitHub Help web page.

Before we begin we have to talk about security accessing the remote repository. There are multiple possible connection strategies. The more common are ssh and https. Both are supported by GitHub, https is the recommended.

In this blog I use https to authenticate and work with GitHub, all you need to remember is your GitHub username and it’s password. I tested ssh too but like https better as you don’t have to handle the ssh keys on every pc you use (and I use up to 10 different machines).

I assume you have built yourself an account on GitHub which you can use. If you don’t want to create one yourself you can use the repository of this blog (link available at the end of the blog). Creating a new remote repository yourself can’t be done without an account. For those of you part 4 of the series which will be published in a couple of days will talk about how to do real work with remote repositories and introduce GitFlow as work template.

Let’s start with a typical workflow. We want to implement a new application using JDev 12.1.3 and want to use git as version control system. As the development is done on multiple locations, the repository should be accessible for all team members 24/7. This exclude a repository on a local pc which others might not be able to reach (e.g. if it’d down).

We generate a new ADF Fusion Web Application. I spare the walk through this process. As name of the new application workspace we choose BlogReadConfigFile, every other name you like is OK too.

Once the new application workspace is ready we like to put it under git control. We first do this locally by using the ‘Team->Version Application…’ menu. See the gallery below fro the whole process.

Notice the ‘.git’ folder which holds all information about the git versions of the application on your local pc. The fresh created local repository can be used already to make changes and commit them to the local repository. Other team members can clone the repository from your pc if they have access to it.

As the new workspace is under version control we can and should open a new window in JDev via menu ‘ Window->Team->Version’ to get a view of the version information available in JDev:

Open Version Window

Open Version Window

Expanded Version Window

Expanded Version Window

As you’ll notice all remote nodes in the tree are empty. The local master branch is the current branch we are working on, visible through the green badge. Now we want to make this local repository available to a remote git server GitHub. First problem is that JDev 12.1.3 (and all other version I know of) don’t support creating a remote repository. We can add existing remote repositories by right clicking to the ‘Remotes’ node and ‘add remote…’

Add a Remote Repository

Add a Remote Repository

Add Remote Dialog

Add Remote Dialog

As you see you can only add the URL to the remote repository but can’t create it. So we have to create the remote repository on GitHub first. Read ‘Setup a new Repository’ to find out how to do this. Make sure you make it a public repository and leave the check box to init the repository with a README.md file empty for the moment.

Create Repository in GitHub

Create Repository in GitHub

You can add default ‘.gitignore’ fie and a licence via this dialog too. We use a ‘.gitignore’ file from other JDev projects as the default java one doesn’t know about some of the artifacts used by JDev. After finishing the dialog you get the information from GitHub how to get data into the new repository.

How to add Data

How to add Data

If you decide to use ssh instead of https you can click the button on the left side of the URL and the info switches accordingly.

As we already have our local repository we use the second method ‘…or publish an existing repository from the command line’. For this we open a command shell and open the location of the workspace (‘/data/development/ENTW_12.1.3.0.0/QT/BlogReadConfigFile’ on my machine). Copy the commands from the second option into the command shell. You are asked for your credentials to login into GitHub, after that the local repository is pushed to GitHub.

Command Lines to Push Data to Repository

Command Lines to Push Data to Repository

When we look at the GitHub web page we see the data from the workspace

Finished Remote Repository

Finished Remote Repository

As mentioned at the button of the page we should add a README.md file. This can be done directly on the web page or you can add such a file on your local pc and push it to the repository. I like to do this through the web page as it copies the description from the creation of the repository into the file.

Add README.md

Add README.md

Now if we look at the Versions window in JDev we see that the remote repository is visible in JDev too.

Updated Version Window

Updated Version Window

Dev knows about the changes as they all in the ‘.git’ folder in the workspace. Right now command line git , JDev git and third party tools like SmartGit, SourceTree or mysygit (or Tortoise Git on windows) play well together. You can use them interchangeably.

As we have added the README.md file using the web page, out first action is to refresh the workspace to get the README.md file into our local repository. For this we use the menu ‘Team->Git->Pull…’, work through the wizard and check the file system after the work has finished.

This concludes this part 3 of the series. You can clone the repository from GitHub. Stay tuned for the next part where we introduce GitFlow and start working on the repository.

Handling images/files in ADF (Part 5)

I received a couple of questions regarding the handling of the images directly after upload for the sample application done in part 1-4.

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

The sample application finished in part 3 (part 4 is a backport to JDev 11gR1 only) has one minor glitch: it doesn’t display an uploaded image directly to the user after uploading it. The user has to commit the data after insert or update of an image before the image becomes visible. Users like to see the newly uploaded image before committing the row. This allows the users to cancel the change or select and upload another image. In this 5th part of the series we implement this.

Before we start to implement let’s talk about how to implement this enhancement. Why isn’t it possible to upload the image data into the blob and then just show the image from the blob via the servlet (see part 3)?
The problem is that the BlobDomain uses a stream to read the data uploaded from the user. This stream can only be read after the BlobDomain is saved, meaning after the commit.

The solution we implement in this part stores the uploaded data (inserted or updated) in a temporary file on the server. Then the server uses the image data from the temporary file to visualize the data. This sounds easy enough, however there is some house keeping to do to make it work.

First we have have to find a place (folder) where we can store the uploaded data until it’s stored in the db or the operation is canceled. Then we need to distinguish which data to show from the servlet (file or blob). Finally we have to clean up the temporary file when we are done.

Lets dive into the implementation. We start from the application at the end of part 3. As the current JDeveloper version is 12.1.3 we do the implementation in this version. The first task is to migrate the old application to 12.1.3. This is done automatically when opening the old work space in JDev 12.1.3 by answering the ‘OK’ to the migration popup. Nothing need to be done here. However, when you download  the work space you’ll notice some clean up I did, like changing the old af:commandButton to the new af:button.

One thing to notice is that the Apache Commons-IO version is updated to 2.4. This update made one other change necessary in the weblogic-application.xml file.

  <prefer-application-packages>
    <package-name>org.apache.commons.io</package-name>
  </prefer-application-packages>

This entry allows the application to use the included commons-io jar to be loaded before the already available commons-io jar, of an older version, in WebLogic server 12.1.3.

Here are the steps we take to implement the tasks:
1) Save the uploaded data to a temporary file as well as to the blob. This is done for convenience. It’S possible to store the data first in the temporary file and only copy it to the BlobDomain when the user commits the changes.
We implement a new java class UploadBlob which holds the BlogDomain and the path to the temporary file. This class also allows to test if a temporary is available.

package de.hahn.blog.uldl.view.types;

import oracle.jbo.domain.BlobDomain;

/**
 * This type class holds the BlogDomain and a path to a temporary file holding the uploaded image data
 */
public class UploadBlob {
    /**
     * Holds the uploaded data
     */
    BlobDomain dataBlob;

    /**
     * Path to the temporary file if availabe
     */
    String tempFile;

    /**
     * C'tor.
     */
    public UploadBlob() {
        super();
        tempFile = null;
        dataBlob = null;
    }

    /**
     * Gets the status of the temporary file
     *
     * @return true if a temporary file is available, false otherwise
     */
    public Boolean getTempFileAvailabe() {
        return (tempFile != null ? Boolean.TRUE : Boolean.FALSE);
    }

    /**
     * @param inageBlob
     */
    public void setInageBlob(BlobDomain dataBlob) {
        this.dataBlob = dataBlob;
    }

    /**
     * Gets the BlobDomain holding the uploaded data
     * @return
     */
    public BlobDomain getDataBlob() {
        return dataBlob;
    }

    /**
     * Sete the path to the temporary file holding the uploaded data
     * @param tempFile path to the temporary file
     */
    public void setTempFile(String tempFile) {
        this.tempFile = tempFile;
    }

    /**
     * Getter for path to temp file holding the data of the uploaded data
     * @return path to the temporary file holding the uploaded data
     */
    public String getTempFile() {
        return tempFile;
    }
}

2) Use this class in the ImageBean.java class where the uploaded data is read. This happens in the valueChangeListener uploadFileValueChangeEvent(ValueChangeEvent valueChangeEvent).

    /**
     * @param valueChangeEvent
     */
    public void uploadFileValueChangeEvent(ValueChangeEvent valueChangeEvent) {
        // The event give access to an Uploade dFile which contains data about the file and its content
        UploadedFile file = (UploadedFile) valueChangeEvent.getNewValue();
        // Get the original file name
        String fileName = file.getFilename();
        // get the mime type
        String contentType = ContentTypes.get(fileName);
        // get the current roew from the ImagesView2Iterator via the binding
        DCBindingContainer lBindingContainer = (DCBindingContainer) BindingContext.getCurrent().getCurrentBindingsEntry();
        DCIteratorBinding lBinding = lBindingContainer.findIteratorBinding("ImagesView2Iterator");
        Row newRow = lBinding.getCurrentRow();
        // set the file name
        newRow.setAttribute("ImageName", fileName);
        // create the BlobDomain and set it into the row
        UploadBlob blob = createBlobDomain(file, Boolean.TRUE);
        newRow.setAttribute("ImageData", blob.getDataBlob());
        // set the mime type
        newRow.setAttribute("ContentType", contentType);
        String tmp = (blob.getTempFileAvailabe() ? blob.getTempFile() : null);
        setTemporaryFileVar(tmp);
        UIComponent ui = (UIComponent) valueChangeEvent.getSource();
        // PPR refresh a jsf component
        ui = ui.getParent();
        AdfFacesContext.getCurrentInstance().addPartialTarget(ui);

    }

Instead of reading the data into the BlobDomain a changed method createBlobDomain is called (line 18). the method now returns an instance of the new class UploadBlob. Below is the code of the new method:

    private UploadBlob createBlobDomain(UploadedFile file, Boolean createTempFile) {
        // init the internal variables
        InputStream in = null;
        OutputStream outTmp = null;
        UploadBlob blobDomain = null;
        OutputStream out = null;
        File tempfile = null;
        logger.info("Starting to create UploadBlog from data...");
        try {
            logger.info("... create BlobDomain...");
            blobDomain = new UploadBlob();
            // Get the input stream representing the data from the client
            in = file.getInputStream();
            // if a temporary file should be created , we do this first as we can't get
            // data data back from the blob until we commit the row. in the next step we
            // write the upload data to a temp file and then copy it into the blob
            if (createTempFile) {
                logger.info("... Creating temporary file...");
                File tempdir = FileUtils.getTempDirectory();
                String ext = FilenameUtils.getExtension(file.getFilename());
                if (!ext.isEmpty()) {
                    ext = "." + ext;
                }
                logger.info("... set extension to " + ext + "...");
                tempfile = File.createTempFile("upl", ext, tempdir);
                logger.info("... " + tempfile.getAbsolutePath() + "...");
                // set path to temporary file
                blobDomain.setTempFile(tempfile.getAbsolutePath());
                FileOutputStream fileOutputStream = FileUtils.openOutputStream(tempfile);
                logger.info("... copy data to temporary file...");
                IOUtils.copy(in, fileOutputStream);
                in = FileUtils.openInputStream(tempfile);
                logger.info("... set inputstream for blog to temporary file...");
            }
            // create the BlobDomain datatype to store the data in the db
            blobDomain.setInageBlob(new BlobDomain());
            // get the outputStream for hte BlobDomain
            out = blobDomain.getDataBlob().getBinaryOutputStream();
            // copy the input stream into the output stream
            logger.info("... copy data to BlobDomain ...");
            /*
             * IOUtils is a class from the Apache Commons IO Package (http://www.apache.org/)
             * Here version 2.0.1 is used
             * please download it directly from http://projects.apache.org/projects/commons_io.html
             */
            IOUtils.copy(in, out);
            logger.info("... Finished OK");
        } catch (Exception e) {
            logger.severe("Error!", e);
            if (tempfile != null) {
                // delete temp file on exception but don'T throw one if there is another exception
                logger.info("Deleted temporary file " + tempfile.getAbsolutePath());
                FileUtils.deleteQuietly(tempfile);
            }
        }
        // return the filled BlobDomain
        return blobDomain;
    }

Depending on the new boolean parameter passed to the method a temporary file is created and the uploaded data is first saved to the temporary file. After that the data is copied from the temporary file into the BlobDomain. At this point the path to the temporary file is saved in the new class for later reference. In case of an exception the temporary file is removed.
Finally in line 22 and 23 of the value change listener we check if a temporary file was generated and we set the path to it to a pageDef variable (see Creating Variables and Attribute Bindings to Store Values Temporarily in the PageDef). For this we use the code below.

    /**
     * Set the temporary file name into a page variable for later use
     * @param name
     */
    private void setTemporaryFileVar(String name) {
        // set pathto temporary file to page variable
        BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
        // get an ADF attributevalue from the ADF page definitions
        AttributeBinding attr = (AttributeBinding) bindings.getControlBinding("TemporaryFile1");
        if (attr != null) {
            attr.setInputValue(name);
        }
    }

The variable is used in the af:image component in the editImage.jsff fragment

                   <af:image source="/render_image?id=#{bindings.ImageId.inputValue}&tmp=#{bindings.TemporaryFile1.inputValue}" id="i1"
                              shortDesc="#{bindings.ImageName.hints.tooltip}" inlineStyle="width:200px;" partialTriggers="cb3" visible="true"/>

here the path to the temporary file is passed to the servlet as second parameter ‘tmp’. In lines 24-27 of the value change listener we send a ppr to the parent component of the af:image to show the now uploaded image.

Another thing to do is to cleanup after the user either cancel or commit the changes. This is done in the cancel_action() or the commit_action() in the ImageBean. Here we call the deleteTemporaryFile() method which checks the existence of a temporary file and deletes it.

    /**
     * delete the temporary file if is present
     */
    public void deleteTemporaryFile() {
        String tempfile = getTemporaryFileVar();
        removeTemporaryFile(tempfile);
        setTemporaryFileVar(null);
    }

3) The final part of the implementation is done in the servlet which is used to get the data back to the client. This is simple as we read the second parameter passed to the servlet. If it’s not empty we always read the image data from the temporary file. If the parameter is empty the servlet gets the data by reading the row from the DB and read the data from the blob. Here are the relevant parts from the servlet:

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        StringBuilder sb = new StringBuilder(100);
        String appModuleName = "de.hahn.blog.uldl.model.facade.ULDLAppModule";

        sb.append("ImageServlet ").append(appModuleName);

        try {
            // get parameter from request
            Map paramMap = request.getParameterMap();
            oracle.jbo.domain.Number id = null;
            String tmporaryFilePath = "";
            if (paramMap.containsKey("id")) {
                String[] pVal = (String[]) paramMap.get("id");
                id = new oracle.jbo.domain.Number(pVal[0]);
                sb.append(" id=").append(pVal[0]);
            }
            // check if we find a temporary file name. In this case we allways use this!
            if (paramMap.containsKey("tmp")) {
                String[] pVal = (String[]) paramMap.get("tmp");
                tmporaryFilePath = pVal[0];
                sb.append(" tmp=").append(pVal[0]);
            }

            OutputStream outputStream = response.getOutputStream();
            InputStream inputStream = null;
            BlobDomain image = null;
            String mimeType = null;
            // no temporary file path given, read everything from DB
            if (tmporaryFilePath.isEmpty()) {
                // get method action from pagedef
                BindingContext bindingContext = BindingContext.getCurrent();
                DCBindingContainer amx = bindingContext.findBindingContainer("de_hahn_blog_uldl_view_image_dummyPageDef");
                JUCtrlActionBinding lBinding = (JUCtrlActionBinding) amx.findCtrlBinding("getImageById");
                // set parameter
                lBinding.getParamsMap().put("aId", id);
                // execute method
                lBinding.invoke();
                // get result
                Object obj = lBinding.getResult();
                ImageAccessViewRow imageRow = (ImageAccessViewRow) obj;

                // Check if a row has been found
                if (imageRow != null) {
                    // Get the blob data
                    image = imageRow.getImageData();
                    mimeType = imageRow.getContentType();
                    // if no image data can be found and no temporary file is present then return and do nothing
                    if (image == null) {
                        mLogger.info("No data found !!! (id = " + id + ")");
                        return;
                    }
                    inputStream = image.getInputStream();
                } else {
                    mLogger.warning("No row found to get image from !!! (id = " + id + ")");
                    return;
                }
                sb.append(" ").append(mimeType).append(" ...");
                mLogger.info(sb.toString());
            } else {
                // read everything from temporary file path
                mimeType = ContentTypes.get(tmporaryFilePath);
                File file = FileUtils.getFile(tmporaryFilePath);
                FileInputStream fileInputStream = FileUtils.openInputStream(file);
                inputStream = fileInputStream;
            }

            // Set the content-type. Only images are taken into account
            response.setContentType(mimeType + "; charset=utf8");
            IOUtils.copy(inputStream, outputStream);
            if (tmporaryFilePath.isEmpty()) {
                // cloase the blob to release the recources
                image.closeInputStream();
            }
            inputStream.close();
            // flush the outout stream
            outputStream.flush();
        } catch (Exception e) {
            mLogger.log(Level.WARNING, "Fehler bei der Ausführung: " + e.getMessage(), e);
        } finally {
            mLogger.info("...done!");
        }
    }

The gallery below shows the new work flow.

The work space for part 5 can be downloaded from the ADF EMG Sample side BlogUploadDownload_12.1.3V4.zip.
Or if you are in GIT you can get the work space from GitHub BlogUploadDownload_12.1.3V4

Lazy Initalizing Beans

A questions on the OTN JDeveloper & ADF forum asked how to initialize attributes with data in a bean before showing the attributes.
The first solution which comes to mind is to initialize attributes and data in the constructor of the bean. This however is not a wise thing to do as you can’t predict when the constructor is called and in which phase of the life cycle you are (as it depends on the scope of the bean).
Here you can use a solution I call lazy initializing the data of a bean. It’s based on the assumption that once the page tries to get an attribute from the bean, the life cycle already has done all other initializations (like bindings). Now if we defer the init of the bean data until a (or the first) getter is called we are save.
To implement this we set an attribute to null and check if it’s null in the getter of the attribute. If we find it’s still null, we init the data and set the attribute to a not null value. Here is the sample code:

package de.hahn.blog.lazyinitbean.view.beans;

import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

import javax.servlet.ServletContext;

import oracle.adf.model.BindingContext;
import oracle.adf.share.logging.ADFLogger;

import oracle.binding.AttributeBinding;
import oracle.binding.BindingContainer;

public class LazyInitBean {
    private static transient final ADFLogger LOGGER = ADFLogger.createADFLogger(LazyInitBean.class);

    private String myName;

    public void setMyName(String myName) {
        LOGGER.info("set data: " + myName);
        this.myName = myName;
    }

    public String getMyName() {
        // lazy init the data only when it's null
        if (myName == null) {
            LOGGER.info("init data through layz init");
            initData();
        }
        return myName;
    }

    public LazyInitBean() {
        LOGGER.info("LazyInitBean: c'tor");
    }

    private void initData() {
        LOGGER.info("data intialized");
        // this method inits the beans attributes (only one here)!
        myName = "just init myself";
        //Get ServlerContexct
        FacesContext ctx = FacesContext.getCurrentInstance();
        ServletContext servletContext = (ServletContext) ctx.getExternalContext().getContext();
        // get the binding container
        BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();

        // get an ADF attributevalue from the ADF page definitions
        AttributeBinding attr = (AttributeBinding) bindings.getControlBinding("myTestValue1");
        if (attr != null) {
            String old = (String) attr.getInputValue();
            attr.setInputValue("NEW DEFAULT VALUE");
            LOGGER.info("LazyInitBean: setnew default value to 'NEW DEFAULT VALUE' old: " + old);
        } else {
            LOGGER.info("LazyInitBean: bindings not present!");
        }
    }

    /**
     * Force the init of the beans attributes
     */
    public void resetData() {
        LOGGER.info("LazyInitBean: reset!");
        // setting the myName to null causes a re initialization
        myName = null;
        // you can call initData() here too;
        initData();
    }

    public void buttonListener(ActionEvent actionEvent) {
        LOGGER.info("Action initData");
        resetData();
    }
}

Now, whenever in a page or fragment the getter to the bean parameter is called (this can be the getter to every property of a component which can have EL) the bean is checkes if the attribute is already initialized and if not calls the initData() method in the bean. If a value is present, the getter returns the value. The initData() method show that attributes in the binding layer can initialized this way too. This is shown with the myTestValue1 attribute which is defined as pageDef variable. You can overwrite the text in the inputText and when you click the button ‘Clear Data’ the data is initialized again removing the data from the inputText and setting it back to the initial values.
Below is a sample of a fragment which uses the bean to lazy initialize the attribute.

<?xml version='1.0' encoding='UTF-8'?>
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <af:panelGridLayout id="pgl1">
        <af:gridRow height="100%" id="gr1">
            <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc1">
                <!-- Content -->
                <af:panelGroupLayout id="pgl2" layout="vertical">
                    <af:outputText value="Lazy Region" id="ot1" inlineStyle="font-size:large;"/>
                    <af:inputText label="Name:" id="it1" value="#{backingBeanScope.LazyInitBean.myName}" partialTriggers="b1 b2"/>
                    <af:outputText value="Hello: #{backingBeanScope.LazyInitBean.myName}!" id="ot2" partialTriggers="b2 b1"/>
                    <af:button text="ShowMessage" id="b2"/>
                    <af:button text="Clear Data" id="b1" actionListener="#{backingBeanScope.LazyInitBean.buttonListener}"/>
                    <af:inputText label="MyTestValue" id="it2" value="#{bindings.myTestValue1.inputValue}" partialTriggers="b1"/>
                </af:panelGroupLayout>
            </af:gridCell>
        </af:gridRow>
    </af:panelGridLayout>
</ui:composition>

Be aware of the fact, that this solution depends on the component(s) which calls the getter which in due course init the data. As the getter methods of the components are called in the order they appear in the component tree you have be careful which getter you use. It should be the first if you want to setup everything up front, but use the last if you want to load data at the end of the page render cycle (e.g. to get more data from a web service but already see the page).

In a comment Aino Andriessen mentioned that you can use the @PostConstruct annotation on a method which then will be called whenever the bean is constructed after all other injections are done. That is the difference to lazy init method. The method is called every time after all other injections are done. If you only want to setup data if the data is really needed this is not possible this way. Nevertheless, to make the sample application show the @PostConstruct method too, I added a method postconstructMethod() in the bean, which writes a log message.

    @PostConstruct
    public void postconstructMethod() {
        LOGGER.info("PostConstruct Called!");
        // init everything here which can be done quickly and is needed to init UI components before showing them
        // or call initData() from here
    }

You can download the sample workspace from the ADF-EMG Sample Project page of get it from GitHub. The sample does not need a DB. The samples are updated to show the @PostConstruct technique.

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

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

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

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

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

Start Page with Outer Navigation

Start Page with Outer Navigation

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

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

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

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

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

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

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

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

Bounden Task Flow with Parent Action

Bounden Task Flow with Parent Action

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

Unbounded Task Flow with Start Page

Unbounded Task Flow with Start Page

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

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

The sample application can be downloaded form ADFEMG Sample Project.

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

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

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

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

Implementation

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

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


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

Resulting Application Module Data Model


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

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

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

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

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


The final inputText look like

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

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

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

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

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

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

This concludes the first implementation and we can run the application

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

JDeveloper 12.1.3: Using File Templates

Since JDeveloper 12.1.2 Oracle added a feature called ‘File Templates’. Only there was absolutely no documentation for this. We only had the ‘File Templates’ node in the preferences. I filed a bug (ADFEMG-150) for this.
In the current JDeveloper 12.1.3 release the ‘inital’ documentation for the ‘File Templated’ has been added.
To get to the ‘File Templates’ via the Tools->Preferences->’File Tempalates’ and see the below image.

File Templates in Preferences

File Templates in Preferences


Loading the extension you get
FileT emplates Inital Dialog

File Templates Inital Dialog


The only help I found can be reached by clicking the Help button in the dialog which will get you
File Templates Help

File Templates Help


This is not much to work with. I you find more help on using this feature, drop me a note, please. All I present in this blog was found out using try and error. There seams to be a difference if you use a Windows or Unix based machine. Later in the blog I’ll give more info on this.

Let’s start with my findings. Clicking through the tree shown in the image above you quickly get the intention of the file templates. This feature allows us to define template for files (e.g. java classes) which can be used to define repeating code and generate the files from it. The sample which is provided by Oracle builds a template for a java class containing copyright and licence information.


However, there is no button or menu to create your own template from scratch. The pencil icon in the top right corner is disabled for each of the files. The only way to create a new template file is to copy an existing one by using the ‘Copy’ button. The first time you click the copy button for one of the files in the you get a note that you can’t change the files in place:
Confirm Modify Template

Confirm Modify Template


confirming the dialog with yes, a copy the content of the sample template from the jar will be created into a file you can edit. All custom template files are written to system12.1.3.0.41.140521.1008\o.jdeveloper.filetemplate\ folder by default. While copying the files you can change the ID, description, the Name and the file name of the template.

Please not the properties in the dialog which allows to define where you later find the template in the gallery.
Once we have copied all files from the tree we can start to modify them. The file suffix of the templates is ‘ftl’. The copied files are automatically opened in the JDev editor. Here comes the first glitch in the extension. If you change *.ftl files in the JDev editor and hit the ‘Save All’ button, the files are not saved. The caption of the tabs remains italic, meaning that the content of the tab has changed and is not saved. You need to hit the ‘Save’ button to save each file by itself.
Once the templates are copied into an accessible path, you can use the ‘Pencil’ button to open them again in the JDev editor, or you can make small changes directly in the editor window in the dialog.
Looking at the content of the files you see that ‘copyright-hahn-java.ftl’ inludes ‘license-hahn-txt.ftl’ which then includes ‘copyright-hahn-content.ftl’. This is the first file we change to change the copyright notice
Changed Copyright Text

Changed Copyright Text


To make the licence ‘license-hahn-txt.ftl’ pick up the changes copyright text we have to change the include statement in the licence template
Change Include  of Copyright in Licence Template

Change Include of Copyright in Licence Template


and finally we have to change the ‘copyright-hahn-java.ftl’ to pickup the changes licence template
Change Include of Licence in Template

Change Include of Licence in Template


After saving all changes (remember to save each file separately) we can test the template. For this we select a package in a project, right click the package we want the file created in and select ‘New’->’From Gallery…’
Select 'New'->'From Gallery...'

Select ‘New’->’From Gallery…’


When we save the java call template we saved the information where the template shows up via the ‘Category’, ‘Folder’ and ‘Feature’ properties of the template dialog (these properties define where the template shown up in the gallery). Select ‘Java’ and you should see
Select the Template from the Gallery

Select the Template from the Gallery


Please notice the name which is the ID we chose for the template and the description which comes from the save dialog too. Select the template and you get the file save dialog
File Save Dialog

File Save Dialog


In the JDev editor you get the generated java class
Generated Class from Template

Generated Class from Template


Please note the error in line 26 of the generated java class

package de/hahn/blog/test1213/.model/adfbc/;

should be

package de.hahn.blog.test1213.model.adfbc;

This only happens on Windows machines, Unix machines generate the right package statement. You have to correct this yourself. I’ll file an bug this.

Now that we have seen this sample, let’s ask what’s missing.

    1) Documentation: if you look at the templates you seethe usage of variables in the templates like ‘${name}’ or ‘${package}’, however, I could not find if there any other valuables as the once you see in the sample. Same is true for the language behind the template mechanism. The sample show some kind of ternary operator, question is what other options do we have. The file suffix ‘.ftl’ and some other information I gathered for this blog post, suggests that the templates are using a tool named ‘FreeMarker’.
    2) Use cases: I would like to use templates at other points like java classes generated for ViewObjectImpl, EntityObjecImpl or ApplicationModuleImpl. This is not possible right now. It would be handy e.g. to generate files with ADFLogger directly implemented. You can insert the ADFLogger into the class using the template, but as this is only done for classes you create via the template most of the other generated classed need to be changed by hand.
    3) applying the template to an existing class is not possible. If you like a copyright statement in each class you can’t attach it from a template.

Problem Installing MAF on WIN 7 64bit System

Aside

The documentation states that installing JDeveloper 12.1.3 on a Windows 7 system (32 or 64) requires administrators rights (Select an Installation User). This is a known fact.
A side effect on my Windows box was, that installing ‘Mobile Application Framework’ (MAF) failed. When I tried installing MAF on my Win 7 laptop via ‘Check for updates’ and confirming the dialog which ask for a restart of JDeveloper, it never came back on. Using the task manager, it turned out that JDeveloper started but that there are other processes waiting for something.
Using some System Internal’s tools I found out that Jdev started opatch to install a needed patch before applying the MAF extension. As the processes are started from Jdev internally, it’s kind of hard to find out the real problem. Readings the doc and searching the file system found a log file, opatch writes into the opatch log folder. The information from this log is that opatch can’t lock or create a folder needed for internal use.

[04.07.2014 19:49:58]        OUI-67064:OPatchSession kann den Bestand für das angegebene Oracle-Standardverzeichnis nicht laden R:\JAVA\12130~1.0\ORACLE\MIDDLE~1. Mögliche Ursachen sind:
                                Keine Lese- oder Schreibberechtigung für ORACLE_HOME/.patch_storage
                                Zentrales Bestandsverzeichnis ist von einer anderen OUI Instance gesperrt
                                Keine Leseberechtigung für zentrales Bestandsverzeichnis
                                Die Lock-Datei ist in ORACLE_HOME/.patch_storage vorhanden
                                Das Oracle-Standardverzeichnis ist in dem zentralen Bestandsverzeichnis nicht vorhanden
[04.07.2014 19:49:58]        OPatch will clean up 'scratch,backup' directories.

Here is the english message

[04.07.2014 19:57:34]       OUI-67064:OPatchSession cannot load inventory for the given Oracle Home R:\JAVA\12130~1.0\ORACLE\MIDDLE~1. Possible causes are:
                                No read or write permission to ORACLE_HOME/.patch_storage
                                Central Inventory is locked by another OUI instance
                                No read permission to Central Inventory
                                The lock file exists in ORACLE_HOME/.patch_storage
                                The Oracle Home does not exist in Central Inventory

The problem is that installing Jdev as administrator put rights onto the file system (Understanding User Permissions) which prevents creating and locking the folder for the normal use running Jdev. This fact can be found in the doc too

When managing a product installation (for example, applying patches, or starting Managed Servers), you must use the same user ID as was used to perform the initial product installation.

To fix the problem start Jdev as administrator and the installation of the MAF extension should work.

JDev 12.1.3 Reorder Fields in Query Panel Clarification

Aside

Reading the ‘What’s new in this release’ document, I found

Reorder Fields in Query. Added ability to reorder fields in Query search panel.

I thought that we now can change the order of the fields in the af:query component at design time, like in an af:gridLayout, and started searching the documentation.
Sadly it turned out that this is not a design time option, as I thought, but a run time option!
This is documented at ‘33.2 Creating Query Search Forms’. Below you see the images from a query in advanced mode with the reorder button (down right) and the reorder dialog.


To reorder the fields at design time you can only use the technique blogged by Luc Bors ADF 11g: Change attribute order in query component

My Personal Bug Parade of JDev 12.1.2 fixed in JDev 12.1.3

Last week (June, 26th 2014) JDev 12.1.3 arrived, today (June, 30th 2014) the ADF Mobile Extension for 12.1.3 was published. You can get it via the menu Help->”Check for Updates…”
However, the first thing I did over the weekend was to check my personal bug parade.
In the list below you see the bugs and there status
UPDATE (July, 18th 2014): Added ADFEMG-118

Bug Status Info
ADFEMG-118
af:poll with interval set to 5000 and timeout set to 5001 doesn’t stop polling after on roundtrip
OPEN The behavior has changed but still not as expected. You still get refresh events after the timeout.
ADFEMG-120
JDeveloper 11.1.1.7.0: can’t create af:tree or af:treetable on a jspx page
OK
ADFEMG-149
JDeveloper 12.1.2.0.0: Completion Insight doesn’t show javadoc for ADF classes
OK without the ADF source code, which you can get filing a SR with support.oracle.com (payed support contract needed), you only see a notice that ‘No ducumentation is available’ instead the api java doc.
ADFEMG-150
JDeveloper 12.1.2.0.0: Missing documentation for usage of ‘File Templates’
OK
ADFEMG-154
JDeveloper 12.1.2.0.0: Completion Insight ‘ctrl-space’ doesn’t do a ‘Declaration Insert’
OK
ADFEMG-156
AttributeValues assigned to variables iterator loose their assigned iterator after clicking on other attribute values in the same pageDef
OK There are some hints that this bug is somehow not fixed for all circumstances. However, my test case works out OK. Whenever someone has a reproducible test case I reopen the bug. Please drop a note then.
ADFEMG-157
JDeveloper 12.1.2.0.0: Filter on af:table doesn’t show up if VO is only based on a SQL query
OK
ADFEMG-159
JDeveloper 12.1.2.0.0: ViewCriteria editor behavior differs from 11g and generated SQL from VC is suboptimal
OK
ADFEMG-160
JDeveloper 12.1.2.0.0: Property Inspector changes properties of not selected component
OK
ADFEMG-179
JDeveloper 12.1.2.0.0: ViewObject with hidden bind variable in where clause crashes application module tester
OK
ADFEMG-189
JDeveloper 12.1.2.0.0: af:inputFile with multiple files to upload duplicates files
OPEN

Good work! Only one bug didn’t make it into the first 12.1.3 release!

Working through all the test cases was kind of fun. My impression of the product is that it’s stable and easy to use. OK, I had no time playing around with the new features. This will be my task in the next weeks. Stay tuned for more on JDeveloper 12.1.3.