Exception-like error handling in Software AG’s Natural

Error handling in Software AG’s Natural can be done in a way that resembles Exception handling in object-oriented languages like Java.

throw

Instead of throwing an Exception, you raise an error simply by assigning a value to the system variable *ERROR-NR. As soon as a statement like the following is executed, the current program flow is interrupted and the nearest ON ERROR block is executed.

*ERROR-NR := 1234

In fact, we use exactly this feature for raising assertion errors in NatUnit.

catch

You can handle a Natural error in an ON ERROR block anywhere inside your code. Just like an Exception travels up through the call stack to get caught in the nearest try-catch block, a Natural error is handled in the nearest ON ERROR block.

Here’s an example of an ON ERROR block that exits the current module and marks the error as handled:

ON ERROR
    /* do something about it */
    ESCAPE MODULE
END-ERROR

catch (SpecificException e)

You can only define a single ON ERROR block in each Natural module. So if you need to handle specific errors in a different way, you need to have some kind of distinction logic like this:

ON ERROR
    IF *ERROR-NR EQ 1234
        /* do something about it */
        ESCAPE MODULE
    END-IF
END-ERROR

Or if you need to distinguish between multiple errors:

ON ERROR
    DECIDE ON FIRST VALUE OF *ERROR-NR
        VALUE 1234
            /* do something about it */
            ESCAPE MODULE
        VALUE 1235
            /* do something about it */
            ESCAPE MODULE
        NONE IGNORE
    END-DECIDE
END-ERROR

re-throw

If you can’t handle the error in an ON ERROR block, but you want to log it or do something else with it before letting the next ON ERROR block handle it, you don’t need to do anything at all, because that’s the default behaviour.

However, if you exit the ON ERROR block with any statement from the following list, the error is marked as handled and the normal control flow (in the calling module of the module containing the ON ERROR block) is continued. So be sure not to exit the block with any of these statements.

Exiting from an ON ERROR Block:
An ON ERROR block may be exited by using a FETCH, STOP, TERMINATE, RETRY, ESCAPE ROUTINE or ESCAPE MODULE statement. If the block is not exited using one of these statements, standard error message processing is performed and program execution is terminated.

Here’s an example of such a “re-throw”:

ON ERROR
    IF *ERROR-NR EQ 1234
        /* log the error */
        /* DON'T exit with `FETCH`, `STOP`, `TERMINATE`, `RETRY`, `ESCAPE ROUTINE` or `ESCAPE MODULE` */
    END-IF
END-ERROR

Checking which error occurred

Even if you “handle” the Natural error in an ON ERROR block, e.g. by using ESCAPE MODULE, the system variable *ERROR-NR isn’t reset to 0. You need to do that yourself, if you need to. If you don’t, the variable can be used in the calling module to check whether an error (that was handled) occured. By the way, the system variable *ERROR-LINE contains the line number of the statement that raised the error.

CALLER

CALLNAT 'CALLEE'
IF *ERROR-NR NE 0
    WRITE 'Error' *ERROR-NR 'occurred in line' *ERROR-LINE 'while calling CALLEE'
    /* prints: "Error     1234 occurred while calling CALLEE" */
END-IF
END

CALLEE

*ERROR-NR := 1234
ON ERROR
    ESCAPE MODULE
END-ERROR
END

If you don’t want any caller to know that an error occurred, simply reset *ERROR-NR:

ON ERROR
    RESET *ERROR-NR
    ESCAPE MODULE
END-ERROR

Global error handler (like a try-catch in main())

You can define a global error handler by setting the system variable *ERROR-TA to the name of a Natural module. In case of an error, Natural automatically calls this module (which has to be a program) and puts information about the error on the stack. The system variables *ERROR-NR and *ERROR-LINE will be reset at this point, so the error handler has to read the information from the stack with INPUT.

CALLER

*ERROR-TA := 'HANDLER'
CALLNAT 'CALLEE'
END

CALLEE

*ERROR-NR := 1234
END

HANDLER

DEFINE DATA LOCAL
1 #ERROR-NR           (N5)
1 #LINE               (N4)
1 #STATUS-CODE        (A1)
1 #PROGRAM            (A8)
1 #LEVEL              (N2)
1 #LEVELI4            (I4)
1 #POSITION-IN-LINE   (N3)
1 #LENGTH-OF-ITEM     (N3)
END-DEFINE

/* read error information from stack */
INPUT #ERROR-NR #LINE #STATUS-CODE #PROGRAM #LEVEL #LEVELI4

WRITE #ERROR-NR #LINE #STATUS-CODE #PROGRAM #LEVEL #LEVELI4 #STATUS-CODE
WRITE *ERROR-NR *ERROR-LINE

END

Output:

1234    10 O CALLEE     2           0 O
     0     0

For more information about the error information on the stack take a look at the section Using an Error Transaction Program in the Natural documentation.

Getting more information about errors programmatically

If you need to find out more about the current error, e.g. in your ON ERROR block, there are quite a few User Exits that deal with errors:

  • USR0040N: Get type of last error
  • USR1016N: Get error level for error in nested copycodes
  • USR2001N: Get information on last error
  • USR2006N: Get information from error message collector
  • USR2007N: Get or set data for RPC default server
  • USR2010N: Get error information on last database call
  • USR2026N: Get TECH information
  • USR2030N: Get dynamic error message parts from the last error
  • USR3320N: Find user short error message (including steplibs search)
  • USR4214N: Get program level information

Using Gradle wrapper behind a proxy server with self-signed SSL certificates

Today, I wanted to add a Gradle Wrapper to my Java project but had a few issues. I am behind a proxy and it changes the SSL certificates to be able to scan traffic for viruses.

My first attempt to start gradlew build resulted in:

    Exception in thread "main" java.net.UnknownHostException: services.gradle.org
            at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184)
            at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
            at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
            at java.net.Socket.connect(Socket.java:589)
            at sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:668)
            at sun.security.ssl.BaseSSLSocketImpl.connect(BaseSSLSocketImpl.java:173)
            ...

Gradle didn’t use the proxy server and tried to connect to the internet directly. This was solved by setting the proxy server in %GRADLE_USER_HOME%\gradle.properties (see Gradlew behind a proxy):

    systemProp.http.proxyHost=192.168.1.1
    systemProp.http.proxyPort=80
    systemProp.http.proxyUser=userid
    systemProp.http.proxyPassword=password
    systemProp.https.proxyHost=192.168.1.1
    systemProp.https.proxyPort=80
    systemProp.https.proxyUser=userid
    systemProp.https.proxyPassword=password

The next try lead to:

    Downloading https://services.gradle.org/distributions/gradle-2.11-bin.zip

    Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
            at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
            at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
            at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
            at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
            at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509)
            ....

The reason for the SSLHandshakeException were the proxy’s selft-signed certificates, that could not be validated. I had to add them to the Java keystore (see Java: Ignore/Trust an invalid SSL cert for https communication and Cacerts default password? -> the default password for the Java keystore is changeit):

    "%JAVA_HOME%\bin\keytool" -import -trustcacerts -alias MY_ALIAS -file MY_CERT.crt -keystore "%JAVA_HOME%\jre\lib\security\cacerts"

Now, Gradle was able to connect to gradle.org to download the distribution. However, the proxy server would not let the ZIP file through:

    Exception in thread "main" java.io.IOException: Server returned HTTP response code: 403 for URL: https://downloads.gradle.org/distributions/gradle-2.11-bin.zip
            at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1840)
            at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441)
            at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
            ...

So I configured Gradle to “download” the ZIP file from the local hard drive in %GRADLE_USER_HOME%\gradle.properties (see How to use gradle zip in local system without downloading when using gradle-wrapper):

    distributionBase=GRADLE_USER_HOME
    distributionPath=wrapper/dists
    zipStoreBase=GRADLE_USER_HOME
    zipStorePath=wrapper/dists
    distributionUrl=gradle-2.11-bin.zip

I manually downloaded the distribution file and put it into %GRADLE_USER_HOME%\wrapper\dists\gradle-2.11-bin\[SOME_HASH]\.

And finally the build was successful! 😀

    D:\MY_PROJECT>gradlew build
    Unzipping D:\GradleUserHome\wrapper\dists\gradle-2.11-bin\452syho4l32rlk2s8ivdjogs8\gradle-2.11-bin.zip to D:\GradleUserHome\wrapper\dists\gradle-2.11-bin\452syho4l32rlk2s8ivdjogs8
    Starting a new Gradle Daemon for this build (subsequent builds will be faster).
    Parallel execution with configuration on demand is an incubating feature.
    :compileJava UP-TO-DATE
    ...

JsonParseException when calling a REST service with curl on Windows

When I called a REST service (provided by webMethods Integration Server) with curl on my Windows machine, I got the following error:

org.codehaus.jackson.JsonParseException:Unexpected character (''' (code 39)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')\n at [Source: com.wm.net.HttpInputStream@74356f93; line: 1, column: 2]

The solution was quite simple: use double quotes instead of single quotes when providing JSON payload.

The curl call that led to the above message looked like this:

curl^
      -X POST^
      -H "Accept: application/json"^
      -H "Content-Type: application/json"^
      -d '{"Username":"Stefan","Password":"secret"}'^
      http://server/service

After changing the fifth line to -d "{\"Username\":\"Stefan\",\"Password\":\"secret\"}"^ everyhing worked as expected.

Return code 82 when running ftouch for a Natural FUser

Today we had a problem with one of our Natural FUsers. When trying to add new sources with ftouch, we got the following error message:

user@server ~ $ ftouch fuser=22,173 lib=ACC sm -b -d


        FTOUCH UTILITY V 6.3.13 PL 0   Software AG 2012

Error  : Mass update could not be started.
          Return code 82 received.

As the return code didn’t help with finding a solution, I kicked off strace and followed the output until the error message was shown:

strace -f -v -s 2014 -o /tmp/stracelog.txt ftouch fuser=22,173 lib=ACC sm -b -d
  • -f: Trace child processes as they are created by currently traced processes as a result of the fork(2) system call.
  • -v: Print unabbreviated versions of environment, stat, termios, etc. calls.
  • -s strsize: Specify the maximum string size to print (the default is 32).
  • -o filename: Write the trace output to the file filename rather than to stderr.

Here comes the interesting part:

stat("/home/macke/fuser", {st_dev=makedev(253, 2), st_ino=2007056, st_mode=S_IFDIR|S_ISGID|0775, st_nlink=4, st_uid=1000, st_gid=1000, st_blksize=4096, st_blocks=8, st_size=4096, st_atime=2015/06/02-12:14:40, st_mtime=2015/06/02-12:14:33, st_ctime=2015/06/02-12:14:39}) = 0
open("/tmp/NCFD00b30016.LCK", O_RDONLY) = 3
read(3, "B24B\0\0\0\0\1\0\0\0FD00b30016\0\0006\200\34\0\0\0\0\0", 32) = 32
close(3)                          = 0
semctl(1867830, 0, GETVAL, 0)     = 0
semctl(1867830, 1, GETVAL, 0)     = 9999
unlink("/home/macke/fuser/ACC/FILEDIR.SAG") = -1 ENOENT (No such file or directory)
semop(1867830, 0x7ffdbcb66ab0, 1) = -1 EACCES (Permission denied)
write(1, "Error  : Mass update could not be started.\n", 43) = 43
write(1, "          Return code 82 received.\n", 35) = 35

Apparently, after opening some kind of temporary file under /tmp, a system call to semop couldn’t be executed (see EACCES (Permission denied)).

Without searching for the cause any longer, I simply deleted all the temporary files under /tmp/NCFD* (who cares for temporary files, anyway?) and ftouch ran successfully immediately:

user@server ~ $ ftouch fuser=22,173 lib=ACC sm -b -d


        FTOUCH UTILITY V 6.3.13 PL 0   Software AG 2012

Ftouch request executed with success.

Setting up the JDBC adapter in webMethods’ Integration Server to connect to an Oracle database

Today, I wanted to use the JDBC adapter in webMethod’s Integration Server to connect to a database from a Java service. As it turns out, the configuration was quite frustrating for me. Here’s a history of the problems that appeared while trying to get a database connection running:

Error encountered
[ART.118.5042] Adapter Runtime (Connection): Unable to enable connection resource ***
[ART.118.5036] Adapter Runtime (Connection): Unable to configure connection manager.
[ADA.1.200] The JDBC DataSource class "oracle.jdbc.pool.OracleDataSource " cannot be located.
oracle.jdbc.pool.OracleDataSource

The problem was a trailing space at the end of the DataSource class name (take a good look at the above message), although I have no idea how it got there.

Solution: Remove the trailing space.

[ADA.1.200] The JDBC DataSource class "oracle.jdbc.pool.OracleDataSource" cannot be located.
oracle.jdbc.pool.OracleDataSource

This was a “real” problem, because IS or the JDBC adapter could not find the desired class. You need to add them to IS to make it work, e.g. by taking ojdbc6.jar from the installation directory of your local oracle client or downloading the JDBC drivers from Oracle’s website.

Solution: Copy the JAR file containing the needed classes to IS_DIR\packages\WmJDBCAdapter\code\jars and restart IS.

[ADA.1.204] Cannot connect to the database with DataSource class "oracle.jdbc.pool.OracleDataSource".
Ungültiger Oracle-URL angegeben/Invalid Oracle URL specified: OracleDataSource.makeURL

Apparently, the Oracle driver had a problem creating the correct URL for connecting to the database, although I provided all the needed information like server, username etc. correctly.

Solution: Add driverType=oci under “Other Properties” in the connection’s properties.

no ocijdbc11 in java.library.path

Apparently, Oracle was missing some libraries I could have copied from my local Oracle installation. But instead, I switched over to using Oracle’s thin driver and it worked instantly.

Solution: Add driverType=thin under “Other Properties” in the connection’s properties.

So, to sum up my configuration to get an Oracle connection running in IS’s JDBC adapter:

  • Copy Oracle’s JDBC drivers (e.g. ojdbc6.jar) to IS_DIR\packages\WmJDBCAdapter\code\jars.
  • Restart IS.
  • Add driverType=thin under “Other Properties” in the connection’s properties.
  • Enable the connection.

Error ISS.0088.9163 Could not retrieve WSDL for service in webMethods Integration Server

The following problem with webMethods Integration Server already occured twice in our environment, so I think it’s time to document the fix. When opening the generated WSD for a web service, I got the following error:

[ISS.0088.9163] Could not retrieve WSDL for service [...], WSD not found.

The problem was the URL generated by the web service index page (/ws/):

[ISS.0088.9163] Could not retrieve WSDL for service ..., WSD not found.

The colon – which is part of the service’s namespace – was encoded as an HTML entity and Integration Server could not find the correct WSD. This seems like a small problem, but some of our clients used this URL and weren’t able to call the service anymore. The solution is to set an extended setting in Integration Server:

watt.server.http.preserveUriReservedChars=false

After a restart of Integration Server, the URLs generated by the index page were correct again and both versions of the URL – whether with colon or entity – worked fine.

This problem occured once after installing Integration Server 9.5 Fix4 as described here: WSDL links are no longer working after installing 9.5 Fix4. And it also occured after upgrading Integration Server 9.5 to 9.7.

Error ISS.0141.9208 Could not deploy the Web service descriptor in webMethods Integration Server

During the startup of webMethods Integration Server I found quite a few entries like this in server.log:

[ISS.0141.9998E] Exception --> [ISS.0141.9208] Could not deploy the Web service descriptor [...]. Cause: [ISC.0081.9164] Exception occurred during generation of WSDL for service [...]: [ISC.0124.9011] Document to XSD error: Simple type [...] does not exist

None of the mentioned web services were operational after a restart of Integration Server. A simple solution was to manually reload the corresponding packages, but I would need to do this every time I restarted Integration Server.

The root cause of this problem was the usage of documents from another package in the package for which the deployment of the WSD failed. For example, a web service in package Business uses a document defined in package Data Model. When Integration Server loads the packages – by default in alphabetical order – Data Model has not yet been loaded when the web service in Business gets deployed.

The solution is to configure the packages’ dependencies: package Business depends on package Data Model. Integration Server then loads the packages in the correct order and is able to deploy the WSD.

You can configure the dependencies in the package’s properties in Software AG Designer:

Configure Package Dependencies in Integration Server