Friday, 14 September 2018

Upgrade SOA 11g to 12c: Invalid Composite File

You might not get it, but not every customer already moved from SOA Suite 11g to 12c. My current customer isn't for instance. Because we're in a bit of a lee period, I'm looking into upgrading their composite projects.

Solve invalid composite files

One thing I ran into quite immediately is that for several projects the composite.xml was invalid. It turned out that it wasn't even upgraded.

I found this support.oracle.com article, DocID 2333742.1. It says that it's not a bug. Because the problem is in the 11g project. Now, we could discus about it, since the 11g project works, and might have been upgraded from an earlier version (10g or early 11g patchset). So, the upgrade process could have been improved. Well the solution is however quite simple.

According to the support note, the .jpr file lacks several elements. A closer look brought me the idea that it could be narrowed down to only one element:
<hash n="oracle.ide.model.TechnologyScopeConfiguration">
  <list n="technologyScope">
    <string v="SOA"/>
  </list>
</hash>
In other words, the project lacks the Techonlogy scope SOA. Apparently Oracle changed the Integration technology in SOA down the product evolution (No bug, but it would be nice that the upgrade process would take this into account). Because of this, the composite is not upgraded.

Changing every one of the tens or hundreds of composite projects can be a tedious job. And I figured that it wouldn't be the only one problem I will run into.

Luckily, the .jpr file is an XML file. So, with an xslt file we could pre-upgrade the .jpr file. So, based on this example, I created the following prepareJpr.xsl stylesheet:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- Update JPR 
  @Author: M. van den Akker, Darwin-IT Professionals, 2018-09-12
  Based on: https://stackoverflow.com/questions/5876382/using-xslt-to-copy-all-nodes-in-xml-with-support-for-special-cases#5877772
  -->
  <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
  <!-- Check Existence of SOA technology, because of Invalid composite file exceptions. MOS DocId 2333742.1-->
  <xsl:template match="hash[@n='oracle.ide.model.TechnologyScopeConfiguration']/list[@n='technologyScope']">  
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
            <!--If SOA doesn't exist then add it -->
            <xsl:choose>
        <xsl:when test="count(./string[@v='SOA'])=0">
            <xsl:comment>Add SOA technology</xsl:comment> 
            <string v="SOA"/>
            </xsl:when>
        <xsl:otherwise><xsl:comment>SOA technology already present </xsl:comment> 
            </xsl:otherwise>
      </xsl:choose>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

If the technology scope does not exist, it adds it with a comment to denote it was added by this utility. If it does not exist, I add a comment to denote that, to be sure that the xslt works and that the project have been handled, but considered ok.

Then I created an prepareSOA11gApplication.xml ANT project that loops over the projects in the target application folder and runs the XSLT over every .jpr file in the application.

It looks like something as follows (since I added some other functionality that I removed for this article):
<?xml version="1.0" encoding="windows-1252" ?>
<!--Ant buildfile generated by Oracle JDeveloper-->
<!--Generated Sep 12, 2018 3:06:25 PM-->
<project xmlns="antlib:org.apache.tools.ant" name="Prepare11gProjects" default="prepareApplication" basedir=".">
  <property file="build.properties"/>
  <taskdef resource="net/sf/antcontrib/antlib.xml">
    <classpath>
      <pathelement location="${ant-contrib.jar}"/>
    </classpath>
  </taskdef>
  <target name="prepareApplication" description="Refactor SOA 11g Project pre upgrade to 12c." depends="">
    <echo>Prepare ${SOA11gAppName} in ${SOA11gAppFolder} </echo>
    <echo>. Prepare projects</echo>
    <foreach target="PrepareProjectFile" param="projectFile">
      <fileset dir="${SOA11gAppFolder}" casesensitive="yes">
        <include name="**/*.jpr"/>
      </fileset>
    </foreach>
  </target>
  <target name="PrepareProjectFile">
    <echo message=".. Prepare ${projectFile}"></echo>
    <property name="projectFileOrg" value="${projectFile}.org"/>
    <echo message="... backup ${projectFile} to ${projectFileOrg}"></echo>
    <move file="${projectFile}" tofile="${projectFileOrg}" overwrite="false"/>
    <echo message="... transform ${projectFileOrg} to ${projectFile} using ${prepareJprXsl}"></echo>
    <xslt style="${prepareJprXsl}" in="${projectFile}.org" out="${projectFile}"/>
  </target>
</project>
I also added functionality to add the projects in a emptied .jws file, and to add a adf-config.xml file.
That way, I get a prepared pre-upgraded workspace that contains only the projects I want to upgrade at that time.

Adapt .jca files

Another type of files that I encountered to be invalid after upgrade, are .jca files. Some projects showed invalid JCA adapters in the composite.
It turns out that Oracle also changed the adapter names in the jca files. A .jca  file starts with:
<adapter-config name="dbDatabaseAdapterService" adapter="db" wsdlLocation="../WSDLs/dbDatabaseAdapterService.wsdl" xmlns="http://platform.integration.oracle/blocks/adapter/fw/metadata">

But in some files this line reads:
<adapter-config name="dbDatabaseAdapterService" adapter="DB Adapter" wsdlLocation="../WSDLs/dbDatabaseAdapterService.wsdl" xmlns="http://platform.integration.oracle/blocks/adapter/fw/metadata">

So, I need a similar XSLT file, that adapts these attributes. For other adapters changes would be:
  • AQ Adapter: aq
  • Apps Adapter: apps
  • etc.
I would rule out that I need other adaptions too.


Tuesday, 11 September 2018

FMW 12c Topology Suggestions

A year or two, maybe three ago  I found this teriffic article on Fusion Middleware 12c topology suggestions.

I find my self searching for this one from time to time. So it's time to write this little note.

It explains which combinations of FMW products in a domain makes sense and especially what the different Server Groups mean.

Wednesday, 5 September 2018

Zipping is easy in Java/Spring/SOASuite

Why using Spring in SOASuite?

This post is actually about Spring in SOASuite. Last few weeks I've come around several questions and implementations using Embedded Java in BPEL. The Java activity in Oracle BPEL is ideal for very small java snippets, to do a simple Base64 encode/decode for example. However, in the situations I encountered these weeks, the code samples were more complex. And my very first recommendation in these situations is to use the SpringContext component, a technique introduced in SOASuite 11g, that I wrote about in 2012 already. I called it 'Forget about WSIF: welcome Spring', but really: forget about Embedded Java in most cases too! So, with this article, I want to showcase the SpringContext component in SOASuite.

A few disadvantages of the Embedded Java activity:
  • To get to, and set or manipulate, your BPEL data, you'll need to compose getVariableData() and setVariableData() functions with xpath-references. You can use temporary Assign activities with copy rules, from where you can extract the actual variable and xpath references, to copy and paste into the getVariableData() functions. But best workaround, to me is to declare a few xsd:string based variables and use Assigns before and after to exchange the data with the java snippet.
  • You can't test/debug the java snippets properly.
  • Catching and handling exceptions may not behave as you might want.
  • You can introspect variables that are referenced in your java snippet from the flow trace. But you must rely on the visual code compare between bpel and java snippet.
The advantages of using a SpringContext component:
  • You can test your java code standalone, and just reuse the methods as is.
  • Wiring your Spring Component to your BPEL will result in a WSDL and a PartnerLink. From BPEL you just invoke it like any other service. 
  • In the flowtrace you will see the input and output variables as is (provided that you set the audit level on Development). And you can just assign the data to and fro the variables based on the wsdl message types.
  • It's all declarative.
A disadvantage might be that it is a bit more complex. It might feel like a hazzle, so you wouldn't use it for 2 lines of Java code, probably. At the other hand, try it out, I guess you might experience it as so much easier and more reliable.

The case at hand

In the case at hand, as referred to in the title,  we need to read a zip file. We get files from Oracle B2B, that are saved on the filesystem with a system generated filename. That is different from the file as send, and provided using the context-disposition MIME header. The client wants to have the file moved and saved using the originating filename. We couldn't get it from B2B in an acceptable way. B2B saves the file using a filename like 'M1234987309891.2341921@myTPR1234_te2309098033.dat'. But it turns out to be a zip file that contains the file, with the name as we want it. So, we could just introspect the file to determine the name as which we want to move it. This means of course that we need to read the file, from Java. And to me it does not feel right to do that from a Java activity in BPEL. It's something more or less functional, so I want to abstract that in a service, based on a java class.

Unzip in Java

I found an unzip method in Java that does just about what I want here. It comes down to:
package nl.darwinit.ziputils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import nl.darwinit.ziputils.beans.ListZipEntriesRequest;
import nl.darwinit.ziputils.beans.ListZipEntriesResponse;
import nl.darwinit.ziputils.beans.ZipEntryFile;

public class Unzip implements IUnzip {

    public static final String className = "nl.darwinit.ziputils.Unzip";

    public Unzip() {
        super();
    }
...

...
    private static void unzip(String zipFilePath, String destDir) {
        File dir = new File(destDir);
        // create output directory if it doesn't exist
        if (!dir.exists())
            dir.mkdirs();
        FileInputStream fis;
        //buffer for read and write data to file
        byte[] buffer = new byte[1024];
        try {
            fis = new FileInputStream(zipFilePath);
            ZipInputStream zis = new ZipInputStream(fis);
            ZipEntry ze = zis.getNextEntry();
            while (ze != null) {
                String fileName = ze.getName();
                File newFile = new File(destDir + File.separator + fileName);
                System.out.println("Unzipping to " + newFile.getAbsolutePath());
                //create directories for sub directories in zip
                new File(newFile.getParent()).mkdirs();
                FileOutputStream fos = new FileOutputStream(newFile);
                int len;
                while ((len = zis.read(buffer)) > 0) {
                    fos.write(buffer, 0, len);
                }
                fos.close();
                //close this ZipEntry
                zis.closeEntry();
                ze = zis.getNextEntry();
            }
            //close last ZipEntry
            zis.closeEntry();
            zis.close();
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Unzip unzip = new Unzip();
        int i = 0;
        for (String arg : args) {
            log("main", "arg[" + i++ + "]: " + arg);
        }
        ListZipEntriesRequest listZipEntriesRequest = new ListZipEntriesRequest();
        listZipEntriesRequest.setZipFileFolder(args[0]);
        listZipEntriesRequest.setZipFileName(args[1]);
        ListZipEntriesResponse listZipEntriesResponse = unzip.listZipEntries(listZipEntriesRequest);
        log("main", listZipEntriesResponse.toString());
    }
}


Besides reading all the ZipEntries in the zip file one by one, this does also save it using a FileOutputStream. But I do not want to output the files, but just get the filename. Another thing: this example shows a private static function. But for the SpringContext I need an instantiatable java class, so with a constructor, and a public method. So I created an Unzip class (with default constructor) and the following method:
    public ListZipEntriesResponse listZipEntries(ListZipEntriesRequest listZipEntriesRequest) {
        final String methodName = "ListZipEntriesResponse";
        logStart(methodName);
        ListZipEntriesResponse listZipEntriesResponse = new ListZipEntriesResponse();
        FileInputStream fileInputStream;
        //buffer for read and write data to file
        log(methodName, "List entries of " + listZipEntriesRequest.getZipFilePath());
        try {

            fileInputStream = new FileInputStream(listZipEntriesRequest.getZipFilePath());
            ZipInputStream zipInputStream = new ZipInputStream(fileInputStream);
            ZipEntry zipEntry = zipInputStream.getNextEntry();
            while (zipEntry != null) {
                String fileName = zipEntry.getName();
                log(methodName, "Entry: " + fileName);
                ZipEntryFile zipEntryFile = new ZipEntryFile();
                zipEntryFile.setFileName(fileName);
                listZipEntriesResponse.addZipEntryFile(zipEntryFile);
                //close this ZipEntry
                zipInputStream.closeEntry();
                zipEntry = zipInputStream.getNextEntry();
            }
            //close last ZipEntry
            zipInputStream.closeEntry();
            zipInputStream.close();
            fileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        logEnd(methodName);
        return listZipEntriesResponse;
    }

The logStart, logEnd and log methods are just wrappers around System.out.println(). As an input I have a bean with:
package nl.darwinit.ziputils.beans;

import java.io.File;


public class ListZipEntriesRequest implements IListZipEntriesRequest {
  
  private String zipFileFolder;
    private String zipFileName;

    public void setZipFileFolder(String zipFileFolder) {
        this.zipFileFolder = zipFileFolder;
    }

    public String getZipFileFolder() {
        return zipFileFolder;
    }

    public void setZipFileName(String zipFileName) {
        this.zipFileName = zipFileName;
    }

    public String getZipFileName() {
        return zipFileName;
    }

    public String getZipFilePath() {
        return getZipFileFolder() +File.separator+ getZipFileName();
    }    
}

Then as a response a bean with:
package nl.darwinit.ziputils.beans;

import java.util.ArrayList;
import java.util.List;

public class ListZipEntriesResponse implements IListZipEntriesResponse {
    private List zipEntryFiles;

    public void setZipEntryFiles(List zipEntryFiles) {
        this.zipEntryFiles = zipEntryFiles;
    }

    public List getZipEntryFiles() {
        return zipEntryFiles;
    }

    public void addZipEntryFile(ZipEntryFile zipEntryFile) {
        if (zipEntryFiles == null) {
            zipEntryFiles = new ArrayList();
        }
        zipEntryFiles.add(zipEntryFile);
    }
    
    public String toString(){
        StringBuffer strBuf = new StringBuffer("ListZipEntriesResponse\n");
        for (ZipEntryFile zipEntryFile : zipEntryFiles){
            strBuf.append("ZipEntryFile: "+zipEntryFile.toString()+"\n");
        }        
        return strBuf.toString();
    }
}

That uses a ZipEntryFile bean, as a List:
package nl.darwinit.ziputils.beans;


public class ZipEntryFile implements IZipEntryFile {
   private String fileName;

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getFileName() {
        return fileName;
    }
    
    public String toString(){
        return "fileName: "+getFileName()+"\n";
    }
}

Place your the source of your classes in the src of your SOA project. The classes are compiled in the the SOA/SCA-INF/classes folder (in 11g, you won't have the SOA subfolder). In 12c the sources can also be placed in the SOA/SCA-INF/src folder. But I found that this will cause a misterious nullpointer exception.

The spring context

My article 'Forget about WSIF: welcome Spring' neatly describes how to create a spring context in 11g. In 12c it does not differ much. You don't need to define separate Spring components per bean, though, but you actually can as shown in the article.  You do need to extract an Interface out of the main class. With only those methods you need in the interface. I actually extracted interfaces from all the beans, but you shouldn't have to.

In this case the Service xml would look like:
<?xml version="1.0" encoding="windows-1252" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util"
       xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sca="http://xmlns.oracle.com/weblogic/weblogic-sca" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://xmlns.oracle.com/weblogic/weblogic-sca META-INF/weblogic-sca.xsd">
  <!--Spring Bean definitions go here-->
  <sca:service name="Unzip" target="UnzipBean"
               type="nl.minvenj.ind.indigo.ziputils.IUnzip"/>
  <bean id="UnzipBean" class="nl.minvenj.ind.indigo.ziputils.Unzip"/>
  <bean id="ListZipEntriesRequestBean" class="nl.darwinit.ziputils.beans.ListZipEntriesRequest"/>
  <bean id="ListZipEntriesResponseBean" class="nl.darwinit.ziputils.beans.ListZipEntriesResponse"/>
  <bean id="ZipEntryFileBean" class="nl.darwinit.ziputils.beans.ZipEntryFile"/>
</beans>

Wire your Spring component to the BPEL component, and then you should have a dialog telling you that the WSDL is created, probably with an accompanying wrapper wsdl for the partnerlink roles.

And then in BPEL just invoke the code.

Happy Spring in SOA (better that than the otherway around if you speak Dutch...).


Monday, 3 September 2018

Docker on Oracle Linux

It occurred to me that if you want to start using Docker there are plenty examples that use Ubuntu as a base platform. I read a book called Learning Docker that assumes Ubuntu for the examples, for instance. I know I am quite stubborn, a "know-it-better" person, but I want to be able to do the same on Oracle Linux.

Docker on Oracle Linux turns out not too complicated. But I ran in to a caveat that I solved and want to share.

I use Vagrant to bring up an Oracle Linux Box, based on a Vagrantfile that prepares the box.With that as a starting point, I created a script that does the complete docker installation. In the following I'll build it up for you, step by step. I'll add my project to GitHub, and provide a link to the complete script in the end.

Init

First some initialization and a function to read the property file:
#!/bin/bash
SCRIPTPATH=$(dirname $0)
#
# Install docker on Oracle Linux.
# @author: Martien van den Akker, Darwin-IT Professionals.
#
function prop {
    grep "${1}" $SCRIPTPATH/makeDockerUser.properties|cut -d'=' -f2
}

#
DOCKER_USER=$(prop 'docker.user')
DOCKER_GROUP=docker

This sets the $SCRIPTPATH property as the folder where the script resides, so I can refer to other files relative to that one.
The function prop alows me to get a property from a property file. A smart function that I got from a colleague (thanks Rob). Based on this property file called makeDockerUser.properties:
docker.user=oracle
docker.password=welcome1

I set the DOCKER_USER and DOCKER_GROUP properties.
The DOCKER_GROUP property is hardcoded however, but that is the standard group that is created at installation of Docker to allow other users to use Docker.

Install Docker Engine

The first actual step is to install the docker engine. Now you can go for the community edition and I've seen that there are examples that pulls the docker-ce (docker Community Engine) for you. However, one of the reasons I am stubborn to stick with Oracle Linux (as you know a RedHat derivate) is that Oracle Linux is the flavor that is used with most of my customers. And if not, it is RedHat. And then I just want to rely on the standard repositories.

To install the docker engine, I have to add the ol7_addons and the ol7_optional_latest repositories. During my OL prepare script, I already added the ol7_developer_EPEL repository. Then the docker-engine package can simply be installed by yum:

#
echo 1. Install Docker Engine
echo . add ol7_addons and ol7_optional_latest repos.
sudo yum-config-manager --enable ol7_addons
sudo yum-config-manager --enable ol7_optional_latest
#
echo . install docker-engine
sudo yum install -q -y docker-engine

Install Curl

For most docker related actions, it is convenient to have  curl installed as well:
#
echo 2. Install curl
sudo yum install -q -y  curl 

Add docker group to docker user

After the docker installation, we need to add the docker group to the docker user (in my case the sort-of default oracle user):
#
echo 3. Add  ${DOCKER_GROUP} group to ${DOCKER_USER}
sudo usermod -aG ${DOCKER_GROUP} ${DOCKER_USER}

This allows the ${DOCKER_USER} (set in the initialization phase) to use the docker command.

Check the docker install

Now let's add a check if docker works:
#
echo 4. Check Docker install
docker --version
sudo systemctl start docker
sudo systemctl status docker
This lists the version of the installed docker, then starts the docker service and lists the status of the docker.

Change the location of the docker containers

When creating a docker container/image (I leave the difference for now), these are saved by default in the location /var/lib/docker. The thing is that this is on the root disk of your installation. And it can grow quite big. For installations of oracle software for instance, I create an extra disk that I mount on /app. It would be better to have a /data mount point as well, but for now I stick with the /app data. So, I want to have docker place my images on the secondary disk. One solution used by Tim Hall, see here, is to create a second disk, format it with BTRFS, and mount it simply to /var/lib/docker.
I rather reconfigure docker to use another disk. This is taken from this article.

To implement this, we first need to know which storage driver Docker uses. We get this from the command docker info, as follows:
echo 5. Change docker default folder
# According to oracle-base you should create a filesystem, preferably using BTRFS, for the container-home. https://oracle-base.com/articles/linux/docker-install-docker-on-oracle-linux-ol7. 
# But let's stick with ext4.
## Adapted from  https://sanenthusiast.com/change-default-image-container-location-docker/
echo 5.1. Find Storage Driver
GREP_STRG_DRVR=$(sudo docker info |grep "Storage Driver")
DOCKER_STORAGE_DRVR=${GREP_STRG_DRVR#*": "}
echo "Storage Driver: ${DOCKER_STORAGE_DRVR}"

This mentions me the overlay2 driver. Then we need to stop docker:
echo 5.2. Stop docker
sudo systemctl stop docker

And then create the folder where we want to store the images:
echo 5.3. Add reference to data folders for storage.
DOCKER_DATA_HOME=/app/docker/data
echo mkdir -p ${DOCKER_DATA_HOME}
sudo mkdir ${DOCKER_DATA_HOME}

Now I found a bit of a problem with my solution here. When I reconfigure docker to use my custom folder, it turns out that on my system the filesystem is not writable from the docker image. If you want to install software in your image, it of course wants to write the files. And this is prevented. After quite some searching, I came on this question on stackoverflow. It turns out that selinux enforces a policy that prevents writing of docker to a custom device. This can be simply circumvented by disabling the enforcing:
#
##https://stackoverflow.com/questions/30091681/why-does-docker-prompt-permission-denied-when-backing-up-the-data-volume
echo disable selinux enforcing
sudo setenforce 0

This disables, as said, the enforcing of selinux. I would say this should be a bit more nuanced. But I don't have that at hand. This however, solved my problem.
Now all is left to configure docker to use the custom folder. Docker is started using a script. In Oracle Linux this is quite conveniently setup. In the folder /etc/sysconfig you find a few config scripts, amongst others a script called: docker-storage. This is a proper to add options. When you set the DOCKER_STORAGE_OPTIONS variable, it is added to the command line. So we simply need to add the line:
DOCKER_STORAGE_OPTIONS = --graph="/app/docker/data" --storage-driver=overlay2

, to the file /etc/sysconfig/docker-storage. This can be done with the following snippet:
#
DOCKER_STORAGE_CFG=/etc/sysconfig/docker-storage
sudo sh -c "echo 'DOCKER_STORAGE_OPTIONS = --graph=\"${DOCKER_DATA_HOME}\" --storage-driver=${DOCKER_STORAGE_DRVR}' >> ${DOCKER_STORAGE_CFG}"

And then finish up with starting docker service again:
#
echo 5.4 Reload deamon
sudo systemctl daemon-reload 
echo 5.5 Start docker again
sudo systemctl start docker

Conclusion

Last week I was at the #PaaSSummerCamp in Lisbon. I did some labs with my docker installation, that resulted in the permission problem. As mentioned I resolved that and I could run the labs succesfully with docker containers from Oracle. So, I concluded that this script should suffice. You can download the complete script at my GitHub vagrant repo.

Tuesday, 28 August 2018

Add a CCA component to your VBCS Application

Today at the #PaaSSummercamp, VBCS is on the agenda. We did a few labs from the VBCS learning path. Nice thing is that you can do it yourself as well.

One of the main goals of the OracleJET labs yesterday was to introduce you to CCA components. Since VBCS is mostly based on OracleJET (For the UI that is) it should not be too hard to add a CCA component from our OracleJET application into our VBCS application. So actually, I wanted to try just that.

If you go to your OracleJET application project, the CCA components are in the ${OJETProjectFolder}\src\js\jet-composites\:
First create a zipfile out of the component, just zip the folder:
(I like TotalCommander for this).
Then in the VBCS Designer tab, browse to the very bottom to find the Custom Heading:
And click the plus icon.
This will bring up a dialog in which you can drag and drop your zip file:
Click import and then the component should appear in the list:
Now it's ready for use.

Since it's a form-component itself, you can't add it to an existing Form-component.

To navigate properly I opened the Structure pane and add the component just before my Form component in the Edit Expense Report page, as a new GridRow:

I want to have it as a new GridRow, just before the current Expense Report Form. Now, after releasing the compnent, it is added to the screen, and it turns out to be visible too:

You now just have to link the fields to variable elements.
So select the component, and the fields will be shown as properties, that can be linked to data-object-elements:

Click on the (x) icon to link the CCA-property to a page-variable-element. Now, since the only variable I have is the ExpenseReport row variable, I use that to populate my fields. They don't make sense functionally, but that is what I have to work with at the moment.
I link first-name to ExpenseReport.name:

Then hire-salary to amount and hire-date to startDate. And that's it. The fields are populated directly in design and live mode. But running the page will show:
As easy as that...





Monday, 27 August 2018

OracleJET at the #PaaSSummerCamp

Today the Oracle OPN #PaaSSummerCamp is started. And I have the privilege to join @GeertjanW to kick-off the Application Development and containers track with an OracleJET training.

OracleJET is Oracle's JavaScript Extension Toolkit, that bundles popular JavaScript frameworks in a toolkit (not a framework) that allows you to QuickStart a JavaScript project.

One of the advantages is that you don't have to bother about which Frameworks you should use and how to setup your project. Geertjan cooked up a workshop with a series of labs that introduces you to OracleJET up to the point that you have a working CRUD (Create, Retrieve, Update and Delete enabled) application.

It's all on GitHub and the nice thing is: you can do the labs too, even if you weren't able to come to the Beautiful Lisbon area and attend the #PaaSSummerCamp. You even don't need to go to Oracle OpenWorld to do this workshop, because it all can be found here. You'll learn how to quickly build a OracleJET 5.2.0 based application.

What do you need? Well, not much. A laptop or desktop computer will help of course. The lab will support you in installing:


  • npm 5.6.0
  • a json-server REST/JSON Mock server
  • ojet 5.2.0 command line interface
You can use any IDE or source editor you want, for instance Notepad or Notepad++, IntelliJ or JDeveloper if you like. But I found Netbeans 8.2 very convenient for OJET Development. Note that Oracle is handing over Netbeans to the Apache Foundation, where NetBeans 9.0 just have been released.

Wednesday, 22 August 2018

Weblogic 12c: Solving Invalid Template error

One of the labs in our Weblogic Advanced Administration 12c course is about using domain templates. When revising the particular lab, we created a domain in Weblogic 12c and then created a template based on the domain. On recreation of the domain based on the template we get an exception:
This we get regardless of if we provide nodemanager details at the initial creation of the domain.

We did some investigation and found for instance this forum,  that gave a hint, but not a solution or workaround.

One important hint is the message 'config-nodemanager.xml: failed to parse the template!(/home/rcma/rcma_domain_template.jar): Parsing the config-nodemanager.xml failed!'. So it relates to the nodemanager configuration and the contents of the config-nodemanager.xml file in the template.

By the way, my colleague found that the same issue can also be experienced during pack and unpack, as desribed in Oracle support note 2311027.1. Here you can find that the problem in the config-nodemanager.xml is about the password. If you have a nodemanager password set in the domain's config.xml, it is encrypted with the domain's seed/salt. It can't be decrypted and read by the domain configurator or unpack tools, because they don't have the salt of the source domain.

So, what is the work-around or solution? I see two:
  1. Following the before mentioned support note, you can replace the nodemanager password in the ${SourceDomainHome}/config/config.xml in the  <node-manager-password-encrypted>******</node-manager-password-encrypted> element with a clear text password. Do the same in the <nod:password>******</nod:password> element in the ${SourceDomainHome}/init-info/config-nodemanager.xml. 
  2. Open the templates jar file:
    Then Extract the config-nodemanager.xml from it and edit it:
    <?xml version="1.0" encoding="UTF-8"?>
    <nod:nodeManagerInfo xmlns:nod="http://xmlns.oracle.com/cie/nodemanager">
      <nod:userName>weblogic</nod:userName>
      <nod:password>{AES}6eu/SDW2GBcb4R2lZgBD1hJVzN7UXQw+w4o46wfvHlk=</nod:password>
      <nod:nodeManagerHome>/app/work/domains/AA2Domain/nodemanager</nod:nodeManagerHome>
      <nod:perDomainNodeManager>true</nod:perDomainNodeManager>
      <nod:customLocationNodeManager>false</nod:customLocationNodeManager>
      <nod:manualNodeManagerSetup>false</nod:manualNodeManagerSetup>
    </nod:nodeManagerInfo>
    replace the password with something readable, for instance:
    <?xml version="1.0" encoding="UTF-8"?>
    <nod:nodeManagerInfo xmlns:nod="http://xmlns.oracle.com/cie/nodemanager">
      <nod:userName>weblogic</nod:userName>
      <nod:password>DoesNotNecessarilyBeAMatchingPassword2018</nod:password>
      <nod:nodeManagerHome>/app/work/domains/AA2Domain/nodemanager</nod:nodeManagerHome>
      <nod:perDomainNodeManager>true</nod:perDomainNodeManager>
      <nod:customLocationNodeManager>false</nod:customLocationNodeManager>
      <nod:manualNodeManagerSetup>false</nod:manualNodeManagerSetup>
    </nod:nodeManagerInfo>
    The password does not necessarily be the actual password of the source environment (at least with the template, not sure in case of pack/unpack). Then re-package the file into the template.
I tested the second work-around, not the one suggested by the note. I actually prefer the second option, since the first option suggest updating the source domain, which I'd rather prevent to do.
Since you do need to do a change, why not change the template? This we tested succesfully.

Another issue solved. Well, actually not really. This is a workaround and the Weblogic tooling should prevent this from happening. You should be able to enter a nodemanager password that is added to the template, or you should be asked for a password at unpack or domain creation. In fact, at creation of a new domain based on the template, you are asked for a nodemanager password. So, why would the domain configurator even bother about trying to read the nodemanager password and/or fail at parsing the file?

If I'd be so honoured to have this blog be read to a product manager or developer of Weblogic, could you take a look into this? Thanks!