/ Development  

The magic of crafting a custom document store

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

Ready for a great day? Ready for some development action? Yes? Today we’ll dive into the development of a custom document store connector! Why? Because in my current project we discuss the capabilities and the effort for building such a thing! And because I’m also pretty curious how to implement a custom document store connector. This connector makes it possible to save uploaded content from our end-users from the solution to a self-defined location (outside the AppWorks platform). We saw already content (by default) being saved as Base64 string in the XML storage within the platform, but that might not always the most sufficient way.

You might already know that several storage connectors are available yet. Like a storage connector for ‘Content Server’, ‘Documentum’, any CMIS compliant repository and even for Jackrabbit from Apache. Next to these connectors there is also an ‘Others’ storage type and exactly that one will be used in this post…

cds_001


Let get right into it…

Where to start?

Well, first of all…spin up the machine and make sure you have a nice workspace with a solution from where you are also able to upload content via the ‘Contents’ building block. If you don’t know how to start with this I would recommend to look back some posts where we played around with the ‘File’ and ‘Contents’ building block to gain more knowledge on what is happening with all the content in our platform.

If you have that project in place it’s time to create a new ‘Document store’ service container and for that we need to open the ‘System Resource Manager’ artifact. Open it from your own organization space and hit that ‘New Service Group’.

cds_002

Select the ‘Document Store’ and hit that ‘Next >’ button.

cds_003

Give a nice name and select all the ‘Web Service Interfaces’. Hit ‘Next >’

cds_004

I leave it on ‘Manual’ start as we are ‘In development’, and I also don’t assign it to any ‘OS Process’. This makes sure the connector is running in its own JVM with its own settings and there is no need to stop the full application server (= TomEE) on the platform! The disadvantage is that it takes some more memory, but it is not the problem for our VM at the moment.

Hit ‘Next >’…Oh yeah…give it a ‘name’ 😜

cds_005

Provide the fields with some values (leave the rest empty for now):

  • Name: OthersStore

  • Type: Others

  • Implementation class: nl.bos.OthersDocumentStoreHandler

    • Although this class is in the ‘OthersStore-1.0-SNAPSHOT.jar’ (that we create later on) it directly found this class in the ‘crosscontext’ folder without defining any jar on any classpath! How do I know? Because after the changing the classpath of the next bullet to something totally different it was still able to find my class.
  • Classpath: crosscontext/OthersStore-1.0-SNAPSHOT.jar

    • It is a required field (but not necessary for the implementation class!). It is used to find the class defined in the ‘StoreConfigHandlerClass’ element of the next ‘External configuration’ XML part!
  • External configuration:

    1
    <ext-configuration xmlns=""><storeconfiguration><StoreConfigHandlerClass>com.cordys.documentstore.StoreConfiguration</StoreConfigHandlerClass></storeconfiguration></ext-configuration>

    I use the default ‘StoreConfigHandlerClass’ as I was unable to make my self-crafted nl.bos.OthersStoreConfiguration to work for some strange reason (see the end of the post). It kept complaining with this error in the startup of the document store service:

    1
    Caused by: java.lang.ClassCastException: class nl.bos.OthersStoreConfiguration cannot be cast to class com.cordys.documentstore.StoreConfiguration (nl.bos.OthersStoreConfiguration is in unnamed module of loader com.cordys.platformloader.PlatformClassLoader ...

Hit ‘Next >’

cds_006

And hit that ‘Finish’

Now you’re stuck with this:

cds_007

Well…where are you waiting for…Start that damn thing!

hmmm…that’s a red light for me…Probably for you too…

cds_008

Ok…some logging!

Let’s see it files are changed in the last minute on our server: sudo find /opt -type f -cmin 1

That gives me 2 interesting results:

1
2
/opt/opentext/AppWorksPlatform/defaultInst/Logs/Application_Server.xml
/opt/opentext/AppWorksPlatform/defaultInst/Logs/appworks_tips#othersdocstore#document_store.xml

Let’s do a tail on both those files (I just start 2 sessions with my MobaXTerm tool) and start the service container again…

tail -f /opt/opentext/AppWorksPlatform/defaultInst/Logs/Application_Server.xml

tail -f /opt/opentext/AppWorksPlatform/defaultInst/Logs/appworks_tips#othersdocstore#document_store.xml

I get clear messages like this:

  • Exiting DocumentStoreConnector open method with initialization status as failure
  • The Service Container cn=Document Store,cn=OthersDocStore,cn=soapnodes,o=appworks_tips,cn=cordys,cn=defaultInst,o=mydomain.com could not be started.
  • java.lang.NullPointerException at com.cordys.util.ClassLoaderUtils.getClassForName(...)
  • Caused by: com.cordys.documentstore.exceptions.DocumentStoreException: nl.bos.OthersDocumentStoreHandler

It’s clear to me that a class cannot be found during the startup of our connector. Well, might be as we didn’t do any implementation yet! 😅

Time for some real coding…Time to get wet and sweaty…


Class handler initialization (version 1.0)

I use IntelliJ as IDE for creating the class implementation of nl.bos.OthersDocumentStoreHandler. You can off-course use your own IDE as long as you are able to package the project with maven. If you don’t know how to start with this I would recommend doing the following this post where you build a maven HelloWorld application in IntelliJ.

Create a new maven project:

cds_009

I use OpenJDK version 11 because the AppWorks platform has support from OpenJDK version 11.

Hit ‘Next >’

cds_010

Fill in the fields and hit ‘Next >’

You are free to fill in whatever you want, but make sure you also need to update previous paths in the screenshots before!

You end-up with a new project where you can start you first packaging to a JAR file

cds_011

Let’s craft ourselves a new Java ‘class’ file!

Right click that blue ‘java’ folder and create a new class…

cds_012

Add a package definition to it and make sure it lands in the correct folder. You’ll see that the package definition will get red! With <Alt>+<Enter> you can fix that with this end result:

cds_013

Sometimes the IDE will start to complain that it can’t compile the code via a maven compile. For that we can update the pom.xml with a maven build element.

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>nl.bos</groupId>
<artifactId>OthersStore</artifactId>
<version>1.0-SNAPSHOT</version>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>

</project>

Next thing to do is implementing the IDocmentStore class

1
2
3
4
package nl.bos;

public class OthersDocumentStoreHandler implements IDocumentStore {
}

This will give a red error because you miss this class as a dependent library for our project. It’s in the package com.cordys.documentstore.IDocumentStore and can be found in the ‘documentstore.jar’ on this location on the server of our platform /opt/opentext/AppWorksPlatform/defaultInst/components/documentstore/.

You need to copy this file from the server to your local machine and make sure it’s available on the classpath of the project, but because we are using maven as library dependency system it’s better to add a dependency like this in the pom.xml:

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>com.cordys</groupId>
<artifactId>documentstore</artifactId>
<version>0.1</version>
</dependency>
</dependencies>

This dependency points to your local maven repository, but will not be found at the moment, because we didn’t install that dependency (yet!)

Location of that local repository (in Windows) would be (by default): C:\Users\{user_name}\.m2

To add a downloadable jar dependency to it, you need to do a maven install command like this: mvn install:install-file -Dfile="documentstore.jar" -DgroupId=com.cordys -DartifactId=documentstore -Dversion=0.1 -Dpackaging=jar

  • That ‘mvn’ command is available in this location within IntelliJ "c:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven3\bin\mvn"

  • The -Dfile points to your downloaded location like for example: "C:\Users\{user_name}\Downloads\libs\components\documentstore\documentstore.jar"

You see that I already downloaded the full lib directory from the platform as later on you’ll see we need more dependencies!

My full command looks like this:

1
"c:\Program Files\JetBrains\IntelliJ IDEA 2020.1\plugins\maven\lib\maven3\bin\mvn" install:install-file -Dfile="C:\Users\{user_name}\Downloads\libs\components\documentstore\documentstore.jar" -DgroupId=com.cordys -DartifactId=documentstore -Dversion=0.1 -Dpackaging=jar

After this dependency injection you should be able to make an import on import com.cordys.documentstore.IDocumentStore;

1
2
3
4
5
6
package nl.bos;

import com.cordys.documentstore.IDocumentStore;

public class OthersDocumentStoreHandler implements IDocumentStore {
}

Now you still get some error, but that is because we need to override the required methods on that interface!

Hit <Alt>+<Enter> to generate the methods to implement

cds_014

You get this screen, and you see all default CMS related operations passing by like ‘checkout’, ‘checkin’, ‘create’, ‘delete’, etc…

cds_015

Next you probably will see that the ‘DocumentStoreException’ can’t be found as an exception class. That is because it can’t find this dependency com.eibus.localization.exception.LocalizableException. It’s in the ‘basicutil.jar’…Also, on the server in that same ‘components’ directory.

Add it as maven dependency to the pom.xml:

1
2
3
4
5
<dependency>
<groupId>com.cordys</groupId>
<artifactId>basicutil</artifactId>
<version>0.1</version>
</dependency>

Install it in the local maven repository with: mvn install:install-file -Dfile="basicutil.jar" -DgroupId=com.cordys -DartifactId=basicutil -Dversion=0.1 -Dpackaging=jar

You get the trick now…correct!?

It should compile again…Let’s do a maven clean and package!

cds_016

That will be our version 1.0, downloadable from here…Coffee break! ☕


JAR deployment

Now we’ve ‘packaged’ our code to a JAR file it’s time to upload that file to the server. This is the location: /opt/opentext/AppWorksPlatform/defaultInst/crosscontext/

Make sure ‘tomcat’ is the owner: sudo chown tomcat:tomcat /opt/opentext/AppWorksPlatform/defaultInst/crosscontext/OthersStore-1.0-SNAPSHOT.jar

And set the permission like the other files: sudo chmod 775 /opt/opentext/AppWorksPlatform/defaultInst/crosscontext/OthersStore-1.0-SNAPSHOT.jar

Restart the ‘Document Store’ service container, and you will see your class will be found, but you get the next error: Caused by: java.lang.ClassNotFoundException: com.cordys.documentstore.IDocumentStore! This error can be corrected by adding our JAR file to the classpath of our service container!

Go back to that created, and restarted ‘Document Store’ service container in the ‘System Resource Manager’ and get the properties of the thing…

Update the classpath in the ‘JRE Configuration’ tab

cds_017

For you to copy: /opt/opentext/AppWorksPlatform/defaultInst/crosscontext/OthersStore-1.0-SNAPSHOT.jar

I also tried ?CORDYS_INSTALL_DIR?/crosscontext/OthersStore-1.0-SNAPSHOT.jar, but it still got me that same error!

After a restart, of only the service container (no need to restart TomEE) I got a green light! 😅

cds_018

Next…


Debugging

Ok…Enough off all the sweating! I want to tell you a small secret on how I got till this point…

Well, that all about enabling the correct debug settings and monitoring the logging.

Edit this file: vi /opt/opentext/AppWorksPlatform/defaultInst/config/Log4jConfiguration.xml

And enable these 2 categories:

1
2
3
4
5
6
<category name="nl.bos"> <!--Your custom code-->
<priority value="debug"/>
</category>
<category name="com.cordys.documentstore.applicationconnector"> <!--The opentext code from documentstore.jar-->
<priority value="debug"/>
</category>

When you restart your green lighted document store connector you will see these log entries in the log file /opt/opentext/AppWorksPlatform/defaultInst/Logs/appworks_tips#othersdocstore#document_store.xml

1
2
3
4
Entered DocumentStoreConnector open method
Connector configuration is : ...
Exiting DocumentStoreConnector open method with initialization status as success
Startup duration (ms): ...

Can we now call all the CMS operation for this brand-new document store?…HaHa…By far not!!

Now the fun is just starting…But most important! We now have a running service we can update with new functionality…


Class implementation (version 1.1)

Let’s first add some more details in the ‘initializeStore()’ method. First we define two global variables like this:

1
2
private static final CordysLogger LOGGER = CordysLogger.getCordysLogger(OthersDocumentStoreHandler.class);
protected boolean isStoreInitialized = false;

That first one requires another dependency in the pom.xml

1
2
3
4
5
<dependency>
<groupId>com.cordys</groupId>
<artifactId>managementlib</artifactId>
<version>0.1</version>
</dependency>

Use this ‘maven install’ command: mvn install:install-file -Dfile="managementlib.jar" -DgroupId=com.cordys -DartifactId=managementlib -Dversion=0.1 -Dpackaging=jar

After this dependency you can use this import: import com.eibus.util.logger.CordysLogger;

This is what the code will look like (with some debugging to check if we have an A-Z view!):

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean initializeStore(StoreConfiguration storeConfiguration) throws DocumentStoreException, DocumentstoreInitializationException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("INTO initializeStore: classPath=%s; implClass=%s; extConfigXML=%s", storeConfiguration.getClassPath(), storeConfiguration.getImplementationClass(), storeConfiguration.getExtConfigXML()));
}

if (this.isStoreInitialized) {
throw new DocumentStoreRuntimeException(OTCSMessages.STORE_WAS_ALREADY_INITIALIZED);
} else {
this.isStoreInitialized = true;
return true;
}
}

Let’s do a maven clean and package, and upload it to the server location again: /opt/opentext/AppWorksPlatform/defaultInst/crosscontext/

Restart the new document store service container and monitor the logging…

You will see this message passing by:

INTO initializeStore: classPath=crosscontext/OthersStore-1.0-SNAPSHOT.jar; implClass=nl.bos.OthersDocumentStoreHandler; extConfigXML=89936

1
2
3
4
5
<log4j:event logger="nl.bos.OthersDocumentStoreHandler" timestamp="1600086109632" time="2020-09-14T14:21:49.632+02:00" level="DEBUG" thread="startProcessorInThreadGroup cn=Document Store,cn=OthersDocStore,cn=soap nodes,o=appworks_tips,cn=cordys,cn=defaultInst,o=mydomain.com">
<log4j:message><![CDATA[INTO initializeStore: classPath=crosscontext/OthersStore-1.0-SNAPSHOT.jar; implClass=nl.bos.OthersDocumentStoreHandler; extConfigXML=89936]]></log4j:message>
<log4j:MDC><![CDATA[host=win-5skkmburvo7 processid=3764]]></log4j:MDC>
<log4j:locationInfo class="nl.bos.OthersDocumentStoreHandler" method="initializeStore" file="OthersDocumentStoreHandler.java" line="26"/>
</log4j:event>

Version 1.1 is born!, downloadable from here…Soda break! 🥤

Next…


Use the ‘Contents’ BB

Now we have our document store is up and running it is time to make use of it. We do this via the ‘Contents’ building block on an entity!

In my environment I have a ‘project’ entity available that will look like this and where we add that building block for uploading content:

cds_019

That ‘DefaultLayout’ for viewing the entity instance looks like this…Off-course we applied the ‘Contents’ panel to it, so we are able to upload document on to our entity instance.

cds_020

After deployment of this entity in runtime you will see this layout after the creation of a new instance of the ‘project’ entity:

cds_021

WAIT…Before you start uploading!!

#RTFM: ‘Run the following REST APIs after restarting the DocumentStore service container’

We’ll make sure the storage cache gets a clean with these 2 URL’s:

And…Maybe you already noticed the logging updates in ‘appworks_tips#othersdocstore#document_store.xml’ with these messages:

The service 'GetDocumentStoreSummary' is not supported by the OTHERS implementation of the Document Store.

We get these during that view of the runtime entity instance! Let’s fix those first before we continue…


Class implementation (version 1.2)

To get rid of those errors in the logging we’ll override an extra method called ‘getSupportedServices()’. A very small change, but it will make a big impact on the custom document connector that we try to build.

The code change will look like this:

1
2
3
4
@Override
public List<String> getSupportedServices() throws DocumentStoreException {
return Collections.emptyList();
}

With this change version 1.2 is born!, downloadable from here.

Build that new JAR file with a maven clean and package command; upload it to the server and restart the document store service container. After this your logging should look fine with an end-result like this in runtime where you are able to get that upload screen!

cds_022

No break this time…Let’s continue…!


Class implementation (version 1.3)

Now…let’s browse for a file in runtime and upload something (from that last screenshot)…shall we?

Well…off-course we’ll get a lot of errors in the log file, but that because we didn’t implement any upload functionality.

Errors that you see passing by look like this:

1
2
3
Error while processing soap request. null.
java.lang.NullPointerException
at com.cordys.documentstore.applicationconnector.CreateDocumentServiceCommand.prepareCreateDocumentResponse()

Let’s fix those errors with a new version of the code-base…Let’s program that document response!

For this we jump to the method ‘createDocument()’ that will send back a CreateDocumentResponseInfo object…

This is the first implementation with some logging, so we see that this method gets triggered:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public CreateDocumentResponseInfo createDocument(CreateDocumentRequestInfo createDocumentRequestInfo) throws DocumentStoreException {
Document doc = createDocumentRequestInfo.getDocument();

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("createDocument createDocumentRequestInfo = [PATH = %s, NAME = %s]", doc.getContentStreamUrl(), doc.getName()));
}

//TODO do something special with that document object here!

return new CreateDocumentResponse(doc.getContentStreamUrl(), "1", new VersionInfo(0, 1));
}

After packaging, deployment on the server, restarting the document store service container and uploading a new document in runtime you should see this message passing by in the logging:

1
createDocument createDocumentRequestInfo = [PATH = /opt/opentext/AppWorksPlatform/defaultInst/content/uploadcontent/AppWorksTipsAppWorks1295456369600920914.tmp, NAME = New Text Document.txt

This was my upload screen:

cds_023

We are on the right track, and you see that the content is (almost) uploaded to a ‘secret’ directory on the server…Well, you need to save it somewhere before handling the rest…And…We still end up with that same error! So, the response callback is still not valid (yet!)…

Let’s continue the grind….The next method update will be the ‘getDocument()’ method which is called after the upload is to get a ‘Document’ in the response…Yes…That is what we need…Is that why we get that annoying null pointer?…Indeed, correct!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public Document getDocument(NodeRequestInfo nodeRequestInfo) throws DocumentStoreException {
String url = nodeRequestInfo.getNodeUrl();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Grab the document from the 'others' store with url: %s", url));
}

Document doc = new Document();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new document object from 'others' store...");
}

String id = url.substring(url.lastIndexOf("AppWorksTipsAppWorks") + 20);

doc.setName(id);
doc.setId(id);
doc.setDocumentURL(url);
doc.setFolderUrl(url);
doc.setMimeType("text/plain");
doc.setLastModified(Calendar.getInstance());

return doc;
}

Well, you know the drill…packaging, deploying, restarting service container, and uploading a new document in runtime…Finally, some great logging:

1
2
Grab the document from the 'others' store with url: /opt/opentext/AppWorksPlatform/defaultInst/content/uploadcontent/AppWorksTipsAppWorks9638656261016126083.tmp
Creating a new document object from 'others' store...

With a nice end-result in runtime!

cds_024

Looks like version 1.3 is born…!


What else?

Hit those buttons

If you mark that just uploaded document you will make some action buttons available!

cds_025

Just hit them and see if they will work!?

  • Rename? That one is working fine without any code change for our connector
  • Download? Hmmm…Not working with a SOAP nullpointer message in the logging. The logging is telling me that the ‘getDocument()’ method gets a call, but it probably expects something more than just a Document object (check the latest version of the code base below!)
  • Upload? Works fine and reacts the same as the ‘create’ document feature
  • Open? Reacts the same as the download option with a SOAP nullpointer in the log file!…As expected I would say!
  • Delete document? Well, it works…The content removal from the instance is a fact, and you end up with a ‘contentless’ instance! That was unexpected as we didn’t implement anything in the ‘deleteDocument()’ method. But I must also admit the save of the content is not for real as you will see in the next section! Looks like it’s more a ‘state’ update on the content entity, but nice to see it works already out-of-the-box!
  • Delete? Works also fine…The document with the content is not there anymore, but again…it’s probably a state update in the database! Let’s find out later on…

What about the other content features?…Like checkout/check-in? We have the methods available in our code, but we don’t see the action buttons!? Well, read along and see if we can add those buttons too!

CMC tool double check

As we learned from previous posts we know that the default storage of the AppWorks platform is an internal XML repository where the saved content is in Base64 format. Let’s see if there is indeed no entry to be found for our content!?

Start CMC: sh /opt/opentext/AppWorksPlatform/defaultInst/bin/cmc.sh

Hit the ‘Repository Browser’ category and connect to the ‘Cordys System’ database…

cds_026

Well…Looks like a clean node to me…As expected!

Database double check

Let’s do the same check for our database as we know that the ‘Contents’ building block has its own tables available for our entity!

After making a connection to our PostgreSQL database (with for example the HeidiSQL tool) we can do these queries from our ‘project’ entity:

  • SELECT * FROM o2appworkstipsappworksprojectcontents;

    projectidbfa4962f44f4a896 contents_id contents_id1
    655362 655362 1
  • SELECT * FROM o2appworkstipsappworkscontents;

    id id1 parentfolder_id parentfolder_id1 s_content_type s_item_status s_filename s_filesize s_storage s_title
    655362 1 \N \N STATIC_FOLDER 1 \N \N \N Default
    655362 983043 655362 1 FILE 1 New Text Document.txt 14 { JSON } New Text Document.txt

This is the ‘{ JSON }’ part from the s_storage column for our document:

1
2
3
4
5
6
7
{
"documentURL":"/opt/opentext/AppWorksPlatform/defaultInst/content/uploadcontent/AppWorksTipsAppWorks13084856423209252343.tmp",
"documentName":"New Text Document.txt",
"documentId":"13084856423209252343.tmp",
"linked":false,
"businessWorkspaceId":null
}

And if you take a look at the code for our ‘getDocument()’ method you start to see some references…correct?

A big knuckle’s bump for you if you made it this far…👊

A little decompiling help

Something totally different…A question I always get with this kind of craftsmanship! How do I get these code example completed and working?

Well, with a little decompiling help off-course! We know that ‘default’ connectors are available, and I just use a tool called JD-GUI to decompile some java classes to see how the ‘connector trick’ is done!

For example, this document store connector makes use of the library ‘documentstore.jar’. If you open the jar file with JD-GUI you will see something like this where you can have a look on how the ‘CMIS’ implementation of the storage connector works:

cds_027

The package ‘com.cordys.documentstore.client.cmis’ has a ‘CMISDocumentStoreHandler’ that has the same methods implemented that we are implementing for our own ‘others’ connector!

cds_028

But…You don’t have this information from me! 😜

The only difference I want to make with this post is to make it as simple as possible for you to start with a new connector…Hopefully it’s clear enough!?

A great "thank you!” for support

For this post I also want to thank you all for the great support I got from OpenText this time! They provided me with valuable information as I got really stuck at some moments…Knowledge from the deepest level of development!

Time for cake before we’ll provide you with a 2.0 version of our storage connector with some extra features for you to play around with!

Version 2.0!
  • Supported features: Wouldn’t it be great to tell the people that want to use our connector what supported features are available by it when the service is starting?…Well, it’s in the final code base…For you to extend!

    Logging will have this output: supportedService OTHERS = [CreateDocument, GetDocument, GetDocumentStoreSummary]

  • Write content to external location: Wouldn’t it be great to really save the content to some other location…Well, check the final code base and for you to extend as we just write to another folder on the server as example!

    For our post we write the content to this location on the server: /opt/opentext/AppWorksPlatform/defaultInst/content/storage/

    You’ll also see we introduce a new class called ‘OthersDocumentStoreUtil’ that also needs a new dependency:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.cordys</groupId>
    <artifactId>eibxml</artifactId>
    <version>0.1</version>
    </dependency>

    mvn install:install-file -Dfile="eibxml.jar" -DgroupId=com.cordys -DartifactId=eibxml -Dversion=0.1 -Dpackaging=jar

    This makes it possible to import the package ‘com.eibus.xml.nom.Node’ which is required for that class as you will see!

    Logging will have this output for this feature: write content to /opt/opentext/AppWorksPlatform/defaultInst/content/storage/AppWorksTipsAppWorks16873356832124609348.tmp

    And you will start to see this content on your server:

    cds_029

    I just write it here for the demo, but if you have a back-end API you can also write to that location!

  • Fix for open/download content: Wouldn’t it be great to view the content we’ve uploaded!?…Yes…It’s in the latest code base!

    The ‘open’ request will show you the content in a preview panel (without an error!):

    cds_030

  • Fix for checkout/check-in: Wouldn’t it be great to be able to add extra features…Yes…You’re welcome sir!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Override
    public CheckInResponseInfo checkin(CheckInRequestInfo checkInRequestInfo) throws DocumentStoreException {
    Document = checkInRequestInfo.getDocument();
    return new CheckInResponse(document.getDocumentURL(), document.getVersionInfo(), document.getId());
    }

    @Override
    public CheckOutResponseInfo checkout(CheckOutRequestInfo checkOutRequestInfo) throws DocumentStoreException {
    if (LOGGER.isDebugEnabled()) {
    LOGGER.debug(String.format("checkout checkOutRequestInfo = [NodeURL = %s; fetchSetting = %s]", checkOutRequestInfo.getNodeUrl(), checkOutRequestInfo.getFetchSetting()));
    }
    ExtendedDocument document = new ExtendedDocument();
    document.setName("dummy");
    return new CheckOutResponse(document);
    }

    In runtime you will see these buttons available now…They give a nullpointer for now as we just implement some sample code to show the buttons, but for you to extend and add your own functionality!

    cds_031

  • Custom extended configuration class: Almost forgotten, but it’s (partly) the latest code!…Make use of a custom handler to read your own extended XML config like this:

    1
    2
    3
    4
    5
    6
    7
    8
    <ext-configuration xmlns="">
    <storeconfiguration>
    <StoreConfigHandlerClass>nl.bos.OthersDocumentStoreConfig</StoreConfigHandlerClass>
    <username>dmadmin</username>
    <password>admin</password>
    <api_url>http://test.nl/api</api_url>
    </storeconfiguration>
    </ext-configuration>

    Logging will have this output during startup of the service container: Successfully established a connection to the 'others' identified by API url http://test.nl/api and username dmadmin [admin] 💪

    BUT…When I enter runtime and try to upload content I get a nasty error in the catalina.out: java.lang.NoClassDefFoundError: com/cordys/documentstore/StoreConfiguration

    Placing the documentstore.jar in tomee/lib or in /opt/opentext/AppWorksPlatform/defaultInst/crosscontext/ did not solve it!

    Then I tried to create a FAT jar (with all maven dependencies…See the pom.xml) that was uploaded to the server, but that ended in another error I couldn’t get solved $#^%# (again in catalina.out):

    1
    2
    3
    4
    5
    6
    Caused by: java.lang.ClassCastException: class nl.bos.OthersDocumentStoreConfig 
    cannot be cast to class com.cordys.documentstore.StoreConfiguration
    (nl.bos.OthersDocumentStoreConfig is in unnamed module of loader
    com.cordys.platformloader.PlatformClassLoader @42383cb0;
    com.cordys.documentstore.StoreConfiguration is in unnamed module of loader
    com.cordys.platformloader.catalina.PlatformWebappClassLoader @6af69083)

    Maybe someone else got a clue on how to solve it…let me know in the comments!? It’s not that of a big issue as we can retrieve these settings also from a properties file, but it would be great to made it work eventually. For now, I turn back to the default config.

    What I also did was a restart of TomEE for these kinds of changes…So, don’t forget it as we change stuff in the TomEE classpath which requires a service restart on the VM!

Download the final source code ZIP package. This is source with that ‘nl.bos.OtherDocumentStoreConfig’ classcast error, but it’s not called in the code at the moment. You can check the code to see how it should work!


Wow…Wasn’t that a great journey to see how a custom document store connector can be crafted?…I give it a big “DONE” …YEAH…Nailed the bastard!…Will we have a great week-end…or what? And we did not only learn about the creation of the connector; We also learned about deployment of the JAR, restarting services with cache cleaning, monitoring logging on debug level…We even checked that indispensable CMC tool and the database…What a post…I think we can be proud on ourselves again with also a great thank you for the OpenText support people (You know who you are when you read this post)!

I see you in the next post when another great topic on a new AppWorks Tips installment…CU!

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”?