/ Development  

Take the first steps toward mastering the AWP SDK with these essential tips

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

This week, we dive into Java documentation which relates to the AppWorks platform SDK. SDK? Yes, just a bunch of libraries that makes your life easy when doing fancy stuff on the platform. It’s well documented, but still unclear from my experience…I hope this post will change my mind how this SDK is usable when we play with it.

Let’s start with the questions what this SDK is not from what I experienced? Well, this SDK is not your entry point to build your own client tool to communicate with the AppWorks platform and triggering services, BPMs, or entity related stuff. Hmmmm…So what can we do with it? Well, we will cover the answer on that question in this post with nice samples for your own usage.


Let get right into it…

Where to start? Well, what is an SDK without documentation!? For Java, we use JavaDoc and for our beloved platform, we can find the ‘AppWorksPlatform-22.4-JavaDocs’ here. It’s a ZIP file for you to download and after extraction, you can open the index.html from your favorite browser with a view like this:

sdk_001

Make yourself familiar with the documentation and click around on your own interests…Take your time; don’t rush; no hurry; no pressure; think; think again;

You will see ‘deprecated’ functions passing by; for explained reasons. This JavaDoc is available since the earliest version of the platform, but still usable for some interesting parts.

Are you ready for the next level? Let’s start opening your favorite IDE…In my case IntelliJ, but you can use whatever you like; If you can create maven dependency type of Java projects, you should be fine in this post!

Wait, do one step back and just start creating a project with Maven, and open it with IntelliJ! Make sure you have Maven available on your local machine; Together with the Java SDK…Dûh! Start with this set of commands from a new PowerShell prompt and call Maven archetype to generate a fancy Maven project:

1
2
3
4
5
6
7
8
9
10
11
12
mvn -version
# Maven home: C:\Program Files\JetBrains\IntelliJ IDEA 2021.1\plugins\maven\lib\maven3\bin\..
# Java version: 11.0.8, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk-11.0.8
cd c:\Temp
mvn archetype:generate
# Filter: org.apache.maven.archetypes:maven-archetype-quickstart
# Apply filter: `1`
# Version 1.4: `8`
# groupId: `nl.bos`
# artifactId: `awp_sdk`
# version: `1.0-SNAPSHOT`
# package: `nl.bos`

FYI: The Java version matters! You want to match with the Java version supported for the current running AppWorks platform. This prevents version conflict on Java classes after deployment! Like this UnsupportedClassVersionError: Unsupported major.minor version 51.0 or this class file has wrong version 55.0, should be 52.0…Find some background info here on those version numbers.

The above “Interactive” CLI created for us a so-called pom.xml with a first Java project structure. Now open the pom.xml file with IntelliJ with a first view like this:

sdk_002

Hit that play-button to see a first valid test! 😅 Now what? Well, how about grabbing a copy of our beloved platform dependencies! Start your AWP VM and make sure to make a copy of the following folder structure (in the root of the AppWorks installation location) with those two JAR-files locally on your machine:

1
2
3
4
5
6
7
8
- certificates
- components
- config
- crosscontext
- ext
- lib
- applicationservercp.jar
- cordyscp.jar

Note: The lib directory contains .so files (Shared Object in Unix!), but in Windows we have the equivalent with .dll files (Dynamic Link Library). You don’t have these if you only have Unix installation files, but you can extract them from the Windows installation files of the platform. See here too!

The next step is making the classes available in our Maven project as dependencies in the pom.xml file! We have some options to choose from:

  1. The “I-do-not-understand-maven-option”; This option is adding the dependencies directly to the classpath of the project as external library. You can make this configuration in the ‘Project Structure’ under the File menu item of IntelliJ. The big disadvantage is that this is not saved with the project in a commit, so other developers can benefit from it. One thing in life I learned is using a dependency management system for any Java related project.
    sdk_003
  2. The “not-recommended-but-quick-way”; Pointing the dependencies directly to the system filepath. We use this option for this post for quick initialization of our blog post. You will see examples below.
  3. The “common-regular-recommended-way”; Install the dependent libraries to your local repository with a command like this:
    1
    mvn install:install-file -Dfile=C:\CORDYS_22_4\components\eibxml\eibxml.jar -DgroupId=com.opentext -DartifactId=eibxml -Dversion=22.4 -Dpackaging=jar`
    After this installation, you can use the new dependency in your pom.xml file directly.
  4. The “pro-way-if-available”; This is the use of an artifact manager (like Nexus or Artifactory) where you can use the mvn deploy command to upload deliveries to a central repository to consume from. Read this article for more background information.

We go for option 2 because I’m the only developer for this project and want to make progression as fast as possible. For multiple developers, I would have gone for option 3! At the customer, I would always recommend option 4.

So, option 2; Open the pom.xml and modify the dependencies-XML part:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.23.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.opentext</groupId>
<artifactId>eibxml</artifactId>
<version>22.4</version>
<scope>system</scope>
<systemPath>C:/CORDYS_22_4/components/eibxml/eibxml.jar</systemPath>
</dependency>
<dependency>
<groupId>com.opentext</groupId>
<artifactId>basicutil</artifactId>
<version>22.4</version>
<scope>system</scope>
<systemPath>C:/CORDYS_22_4/components/basicutil/basicutil.jar</systemPath>
</dependency>
</dependencies>

I upgraded the JUnit 4 dependency to JUnit 5 dependency and included another interesting dependency AssertJ…Why? Well, here’s why (see the difference in readability):

1
2
3
4
5
6
7
8
9
10
11
12
package nl.bos;

import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

class AppTest {

@Test
void shouldAnswerWithTrue() {
assertThat(true).isTrue();
}
}

How nice! 🤓 Right, but why the specific eibxml.jar? Well, did you open it with JD-GUI already?

sdk_004

Do you see those interesting classes? In the com.eibus.xml.nom and com.eibus.xml.xpath package? So, we can start programming now…right? Yes, we can…

FYI: The basicutil.jar is an extra library dependent to the eibxml.jar!


Our first lines of (test driven!) development

Let’s start creating a new test readXMLData which we read with the Document object of the eibxml dependency. Just like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private String xmlInput = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<note>" +
"<to>Tove</to>" +
"<from>Jani</from>" +
"<heading>Reminder</heading>" +
"<body>Don't forget me this weekend!</body>" +
"</note>";

@Test
void readXMLData() throws XMLException {
Document document = new Document();
int xmlResponse = document.load(xmlInput.getBytes());
assertThat(xmlResponse).isNotZero();
}

Since Java 15, there is a better way for writing down the ‘xmlInput’ variable; It’s called “Text block literals”, but we’re on version 11 currently with our platform. If you have upgraded to the supported Java 17 with AWP 22.4, you can try it out! Nice stuff…

Hit the play button for that test and make your conclusion on a message java.lang.UnsatisfiedLinkError: no xmlForJava in java.library.path! Where did we see this before? Have a look here

To fix this for our Maven project, I make an update in the pom.xml for one of the available plugins:

1
2
3
4
5
6
7
8
9
10
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<argLine>-Djava.library.path="C:\CORDYS_22_4\lib"</argLine>
<additionalClasspathElements>
<additionalClasspathElement>C:\CORDYS_22_4\cordyscp.jar</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>

A rerun will give a new familiar error: java.lang.UnsatisfiedLinkError: C:\CORDYS_22_4\lib\xmlForJava.dll: Can't find dependent libraries. We can fix this with a path variable on our local system: set PATH=%PATH%;C:\CORDYS_22_4\lib

After an IntelliJ restart (to pick up the PATH) and another rerun, I see a green checkmark:

sdk_005


Playground is open with for TDD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Test
void writeXMLData() throws XMLException {
Document document = new Document();
int xmlResponse = document.load(xmlInput.getBytes());
String xmlString = Node.writeToString(xmlResponse, true);
System.out.println(xmlString);
assertThat(xmlString).isNotEmpty();
}

@Test
void getMatchingNodes() throws XMLException {
Document document = new Document();
int xmlResponse = document.load(xmlInput.getBytes());
int[] xPathResult = XPath.getMatchingNodes("//note/body/text()", null, xmlResponse);
for (int note : xPathResult) {
String actual = Node.getData(note);
assertThat(actual).isEqualTo("Don't forget me this weekend!");
}
}

@Test
void buildingXML() throws XMLException {
String expected = "folderContent";

Document document = new Document();
int rootElement = document.load("<root xmlns:h=\"appworks-tips.com\"></root>".getBytes());
int folder1Element = Node.createElement("folder_1", rootElement);
int folder2Element = Node.createElement("folder_2", rootElement);
int folder3Element = Node.createElementNS("folder_3", expected, null, "appworks-tips.com", rootElement);
Node.createElement("document_1", folder1Element);
Node.createElement("document_2", folder1Element);
Node.createElement("document_3", folder2Element);
Node.createElement("document_4", folder2Element);
Node.createElement("document_5", folder3Element);

System.out.println(Node.writeToString(rootElement, true));

int firstMatch1 = XPath.getFirstMatch("//root/folder_1", null, rootElement);
System.out.println(Node.writeToString(firstMatch1, true));

XPathMetaInfo metaInfo = new XPathMetaInfo();
metaInfo.addNamespaceBinding("", "");
int firstMatch2 = XPath.getFirstMatch("//root/folder_3", metaInfo, rootElement);
Assertions.assertThat(Node.getData(firstMatch2)).isEqualTo(expected);
}

We now use the System.out.println() to show the details in the console, but I normally recommend using a logger object for this; like these examples:

  • CordysLogger.getCordysLogger(AppTest.class);; This is part of the managementlib dependency!
  • Log.createLogger(AppTest.class);; This is part of the basicutil dependency!

What else?

What about reading platform specific properties, converting information, and calling utility methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
@Test
void readProperties() {
Properties properties = EIBProperties.getProperties();
properties.forEach((key, value) -> {
System.out.printf("key: %s; value: %s%n", key, value);
});

System.setProperty("CORDYS_INSTALL_DIR", "");
String expected = String.format("%s\\%s\\%s",
Paths.get("").toAbsolutePath(),
EIBProperties.CONFIG_DIRECTORY_NAME,
EIBProperties.PROPERTIES_FILE);
Assertions.assertThat(EIBProperties.getDefaultPropertiesFileName()).isEqualTo(expected);

SystemInformation systemInformation = new SystemInformation();
System.out.printf("OS: %s, FQDN: %s%n",
systemInformation.getOSVersion(),
SystemInformation.getFullyQualifiedHostName());

System.out.printf("CP: %s, convert: %s%n",
Native.getBaseCordysClassPath(),
Native.convertUTCStringToTimestamp("2022-12-31T00:00:00.0000000000"));
}

@Test
void nomToDom() throws XMLException, TransformerException {
int xmlResponse = document.load(xmlInput.getBytes());
org.w3c.dom.Document domDocument = DOMFactory.wrapDocument(xmlResponse);
printDocument(domDocument, System.out);
Assertions.assertThat(Node.getName(xmlResponse)).isEqualTo(domDocument.getDocumentElement().getNodeName());
}

private void printDocument(org.w3c.dom.Document doc, OutputStream out) throws TransformerException {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.transform(new DOMSource(doc), new StreamResult(new OutputStreamWriter(out, StandardCharsets.UTF_8)));
}

@Test
void utilitiesCommandLine() throws CommandLine.ParseException {
String[] args = {"-ffile1", "-bbatch1", "param1", "param2"};
//Parse a command line using short options only!
CommandLine commandLine = new CommandLine(args, "", "-f-b");
CommandLine.ParseResult parseResult = commandLine.parseResult();

List<String> parameters = parseResult.getParameters();
StringBuilder actualParams = new StringBuilder();
parameters.forEach(actualParams::append);
Assertions.assertThat(actualParams.toString()).hasToString("param1param2");

Map<String, List<String>> allOptions = parseResult.getAllOptions();
StringBuilder actualOptions = new StringBuilder();
allOptions.forEach((key, value) -> {
actualOptions.append("key: ").append(key).append(", ").append("value: ").append(value);
});
Assertions.assertThat(actualOptions).hasToString("key: b, value: [batch1]key: f, value: [file1]");
}

@Test
void utilitiesLinkedList() {
LinkedList linkedList = new LinkedList();
linkedList.add("Hello World");
linkedList.add(Boolean.TRUE);
linkedList.add(Integer.MAX_VALUE);
linkedList.elements().asIterator().forEachRemaining(item -> {
System.out.printf("LinkedItem: %s%n", item);
});

Iterator<Object> objectIterator = linkedList.elements().asIterator();
Assertions.assertThat(objectIterator.next()).isInstanceOf(String.class);
Assertions.assertThat(objectIterator.next()).isInstanceOf(Boolean.class);
Assertions.assertThat(objectIterator.next()).isInstanceOf(Integer.class);
}

@Test
void utilitiesQueue() {
Queue queue = new Queue(3);
queue.put("Hello World");
queue.put(Boolean.FALSE);
queue.put(Integer.MIN_VALUE);
//queue.put(Integer.MIN_VALUE); will hold till queue.get() is triggered!
StringBuilder actual = new StringBuilder();
for (int i = 0; i < queue.getMaxSize(); i++) {
actual.append(queue.get());
}
Assertions.assertThat(actual.toString()).hasToString("Hello Worldfalse-2147483648");
}

@Test
void utilitiesRandom() {
Integer[] source = {1, 2, 3, 4};
Integer[] target = new Integer[source.length];
Random.swapRandom(source, target);
Arrays.stream(target).forEach(integer -> System.out.printf("%s ", integer));
Assertions.assertThat(target)
.containsAnyOf(1, 2, 3, 4)
.doesNotContain(0)
.doesNotHaveDuplicates()
.hasSameSizeAs(source);
}

@Test
void utilitiesStringSorter() {
Vector<String> list = new Vector<>();
list.add("a");
list.add("z");
list.add("c");
list.add("Z");
list.add("C");
list.add("A");
Enumeration<?> sort = StringSorter.sort(list.elements());
Iterator<?> iterator = sort.asIterator();

StringBuilder actual = new StringBuilder();
iterator.forEachRemaining(item -> {
actual.append(item).append(" ");
});
assertThat(actual.toString().trim()).hasToString("A C Z a c z");
}

@Test
void classInformation() {
ClassInfo classInfo = new ClassInfo(Node.class);
Assertions.assertThat(classInfo.getPersistence()).isEqualTo(ClassInfo.Persistence.UNDEFINED);
}

@Test
void dataConversion() {
Assertions.assertThat(DataConverter.String2int("100")).isEqualTo(100);
Assertions.assertThat(DataConverter.Object2String(null)).isEmpty();
Assertions.assertThat(DataConverter.String2String("dummy")).hasToString("dummy");
Assertions.assertThat(DataConverter.String2byte("2")).isEqualTo((byte)2);
}

To make it all run smoothly (also for the classInformation and dataConversion tests), I needed to update my pom.xml with extended dependencies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<dependency>
<groupId>com.opentext</groupId>
<artifactId>wsappserver</artifactId>
<version>22.4</version>
<scope>system</scope>
<systemPath>C:/CORDYS_22_4/components/wsappserver/wsappserver.jar</systemPath>
</dependency>
<!-- https://mvnrepository.com/artifact/org.eclipse.microprofile.config/microprofile-config-api -->
<dependency>
<groupId>org.eclipse.microprofile.config</groupId>
<artifactId>microprofile-config-api</artifactId>
<version>3.0.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.geronimo.config/geronimo-config-impl -->
<dependency>
<groupId>org.apache.geronimo.config</groupId>
<artifactId>geronimo-config-impl</artifactId>
<version>1.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.transaction/jta -->
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>

Interesting! But what about real AWP calls!?!? Well, I tried out these classes for you, but dramatically failed calling them from my local laptop where my AWP is running in a VM. That’s why I annotated the tests with @Disabled (in the download below!); I leave them in for my own future reference as they do provide interesting insights to share.

  • com.cordys.cap.utility.DeploymentUtility
  • com.eibus.applicationconnector.sql.DBConnectionPool
  • com.cordys.cpc.bsf.event.IInitializeListener
  • com.cordys.cpc.bsf.soap.SOAPRequestObject

You can download the full test class here as well as my pom.xml where you also see the extra dependencies passing by for the last 4 objects in the disabled tests.

With that said…It’s time to continue our lives, but not before I present you a proper overview of the packages documented in the SDK…


Packages overview

package description
com.cordys.bre.engine Part of business rule documents with decision tables in BPMs; Replaced with the entity modeling ‘Rule’ BB.
com.cordys.bre.repository The API for managing rule documents
com.cordys.cap.utility API for deploying CAP packages and getting package info! Check the samples in this post…
com.cordys.cpc.bsf.busobject The data inside a busobject equals an XML/NOM document; Contains CustomBusObject, QueryObject, DML, and DDL
com.cordys.cpc.bsf.classinfo Getting your info on class definitions and related fields
com.cordys.cpc.bsf.connector Internal usage of the WsAppserver service container
com.cordys.cpc.bsf.event.* Package for event listeners; like onCreate, onDelete, onChange; Does it ring a bell?
com.cordys.cpc.bsf.query.* Map BusObject-data to XML, required by XQY
com.cordys.cpc.bsf.soap API class to call soap methods
com.cordys.cpc.bsf.types ‘UnsignedByte’; “Represents an unsigned integer number in the range {1, 255} inclusive”
com.cordys.cpc.bsf.util Datatype converters, ObjectHelper, and TupleHandling in <tuple><old> format
com.cordys.cpc.coboc Deprecated! CoBOC ‘smells’ like the old way of the new entity modeling principles.
com.cordys.cpc.coboc.modeler Deprecated!
com.cordys.cpc.coboc.repository Deprecated!
com.cordys.cpc.coboc.scheduler Looks like this represents the schedule documents created for a solution; managed by the schedule manager.
com.cordys.cpc.notification.runtime Deprecated!
com.cordys.datagovernance.registry.idgenerators Generate registry identifiers for MDM models; I never use it (I guess!?)
com.cordys.documentstore Partly covered in this post
com.cordys.documentstore.otcs Specific ‘Category’ classes for OpenText Content Server
com.cordys.documentstore.search Interfaces for the documentstore search responses
com.cordys.logger Quoted: “Allows the users to add logger context for the current thread.”; I never use it (I guess!?)
com.cordys.mdm.* MDM is part of the ‘old’ Cordys implementation for the platform; It’s overruled with new techniques.
com.cordys.notification.* A package for task assignment (inbox). The ReST layer of the platform has a new implementation.
com.cordys.task.cap.util I don’t know; I don’t want to know; I never use it (I guess!?)
com.cordys.xml.dom Convert org.w3c.dom nodes to com.eibus.xml.nom nodes…See samples in this post.
com.eibus.applicationconnector.sql API to access XQY layer to query XML data
com.eibus.applicationconnector.uddi Partly covered in this post
com.eibus.connector.nom Package to connect to Cordys ESB and listening for XML Soap messages
com.eibus.contentmanagement Entrance for managing ISV packages; I never use it (I guess!?)
com.eibus.directory.soap LDAP Directory Connection (CARS), operations and LDAPEntry object attributes.
com.eibus.management.* Interfaces for managing AWP (incl. counters); Like alerts, JMX, problems, and settings.
com.eibus.security.* ‘Authenticator’, ‘Identity’ (an organization user), and ‘Credentials’ classes
com.eibus.soap.* Building a custom Application Connector with body, method definitions and processor
com.eibus.transport Handle incoming messages from the Cordys ESB
com.eibus.util Utilities for you to use; Like CommandLine parser, Cordys Constants, (Blocking) Queue, Random int, ENUM sorter
com.eibus.util.cache Quoted: “This class acts as generic superclass for thread safe caches of for example SOAP call results.”
com.eibus.util.logger Instantiate CordysLogger for logging
com.eibus.util.system See the sample readProperties in this post for examples
com.eibus.xforms Creating dynamic xforms; I never use it (I guess!?)
com.eibus.xml.nom Working with XML nodes as Java integers as shown in this post examples
com.eibus.xml.xpath Using xPath expressions for finding XML nodes
com.opentext.applicationconnector.httpconnector The interface classes for the next implementation package
com.opentext.applicationconnector.httpconnector.impl Partly covered in this post

NOTE: I placed the exception packages out of scope for this post as they only implement custom exception handling for that related package.

Abbreviations:

  • BRE = Business Rule Engine
  • BSF = Bus Service Framework
  • CoBOC = Collaborative Business Object Cache
  • CPC = Cordys Platform Component
  • DDL = Data Definition Language
  • DML = Data Manipulation Language
  • DOM = Document Object Model
  • ESB = Enterprise Service Bus
  • ISV = Independent Software Vendor
  • MDM = Master Data Management
  • NOM = Native Object Model
  • SQL = Structured Query Language
  • UDDI = Universal Description, Discovery, and Integration
  • XQY = XML QuerY

I give it a well “DONE” for this post where we definitely got a deeper dive then expected on the AWP SDK. Let’s recap the question on what the SDK is not? It’s indeed not a tool to build a client around it…I guess!? (I saw something interesting passing by during my journey for next week!). The SDK for sure has interesting utility classes to use for future AppWorks development, but I also see the platform contains more classes beyond the description of this SDK!…Have a search for implements in the advanced developer guide for interesting topics! 🤠 Have a great TDD-weekend; I CU in the next post, next week!

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?