Eigenen Java-Code bei HCL Connections Ereignissen ausführen mit SPI-Events

Eigenen Java-Code bei HCL Connections Ereignissen ausführen mit SPI-Events

HCL Connections lässt sich bereits umfangreich konfigurieren und anpassen, etwa über die XML-Dateien. Noch weiter geht die SPI-Schnittstelle: Sie ermöglicht es, Java-Code bei bestimmten Ereignissen auszuführen – beispielsweise dem Hochladen einer Datei, wenn neue Mitglieder zu einer Community hinzugefügt werden, usw. Damit hat man nahezu grenzenlose Freiheiten, um CNX in andere Systeme zu integrieren.

So funktionierten SPI-Ereignisse

Am Anfang steht eine eigene Java-Klasse, welche die Schnittstelle com.ibm.connections.spi.events.EventHandler implementiert. Sie stellt eine Methode handleEvent zur Verfügung – diese wird beim Eintreten eines Ereignisses zusammen mit einem Event Objekt aufgerufen. Für den Verweis auf die EventHandler-Schnittstelle muss lc.events.spi.jar im Java ClassPath liegen.

In der events-config.xml definiert ein Event-Handler, welche Ereignisse eure Anwendung empfangen soll. Außerdem die Art der Ausführung: Pre-Events können Daten verändern, Post-Events durch ihre asynchrone Ausführung nach dem Ereignis nicht. Aus eurem Code und dem Interface muss eine Jar erzeugt und als gemeinsame Bibliothek in WebSphere hinterlegt werden. Schlussendlich ist es erforderlich, diese in der News-Anwendung zu aktivieren. Nach dessen Neustart läuft euer Code.

Die Ereignisse werden von der News-Anwendung abgearbeitet. Sämtliche Log-Einträge aus dem eigenen Code landen daher im InfraCluster (SystemOut.log).

Erzeugen eines Jar-Archives mit eigenem Java-Code

Zur Demonstration habe ich eine simple Klasse AuditHandler (Paket classes) als Post-EventHandler entwickelt. Sie reagiert auf neu hochgeladene Dateien (Ereignis files.file.created) und schreibt uns dazu Informationen (Nutzername, ID von Datei/Bibliothek, Dateiname, Pfad im Dateisystem, Mime-Type) in das Log des InfraCluster. Wenn man nur auf bestimmte Ereignisse reagieren möchte, können diese in der events-config.xml angegeben werden. Die Filterung hier dient als Beispiel zur Demonstration, da man ggf. mehrere abonniert und im Code differenzieren muss.

package classes;

import com.ibm.connections.spi.events.EventHandler;
import com.ibm.connections.spi.events.EventHandlerException;
import com.ibm.connections.spi.events.EventHandlerInitException;
import com.ibm.connections.spi.events.object.Attachment;
import com.ibm.connections.spi.events.object.Event;
import java.io.IOException;
import java.io.Serializable;

import java.util.logging.Level;
import java.util.logging.Logger;

public class AuditHandler implements EventHandler, Serializable {
  private static Logger log = Logger.getLogger("AuditHandler");

  public void destroy() {
    log.info("Destroy MailFileHandler");
  }
  
  public void init() throws EventHandlerInitException {
    log.info("START init with java " + System.getProperty("java.version"));
  }
  
  public void handleEvent(Event event) throws EventHandlerException {
    String fileId = event.getItem().getID();
    String libraryId = event.getContainer().getID();
    String appName = event.getSource().getName();
    Object eventType = event.getType(); // CREATE
    String actorUserId = event.getActor().getExtID();
    String actorDisplayName = event.getActor().getDisplayName();

    if(event.getName().equals("files.file.created")) {
        log.info("Handling SPI event: " + event.getName() + " from app " + appName);
        System.out.println("  Actor Id: " + actorUserId);
        System.out.println("  DisplayName: " + actorDisplayName);
        System.out.println("  FileId: " + fileId);
        System.out.println("  FileLibId: " + libraryId);

        if (event.getAttachmentData() == null || event.getAttachmentData().getAdded().isEmpty()) {
          log.warning("No attachment data available for the event. Cannot retrieve old filename or file system location.");
          return;
        }

        Attachment attachment = event.getAttachmentData().getAdded().iterator().next();
        System.out.println("Attachment object");
        System.out.println(attachment.getFileName());
        System.out.println(attachment.getFileSystemLocation());
        System.out.println(attachment.getContentType());
      }else {
        System.out.println("Skipping SPI-Event: " + event.getName());
      }
  }
}

Damit aus diesem Code eine Jar-Datei gebaut werden kann, ist die anfangs erwähnte lc.events.spi.jar Bibliothek notwendig. Ihr findet sie im entpackten Ear-Ordner jeder CNX-Anwendung: Blogs, Wikis, Dateien, usw.1 Es spielt keine Rolle, aus welcher die Jar kopiert wird.

find /opt -name lc.events.spi.jar
/opt/IBM/WebSphere/AppServer/profiles/CnxNode01/installedApps/CnxCell/Blogs.ear/lc.events.spi.jar
/opt/IBM/WebSphere/AppServer/profiles/CnxNode01/installedApps/CnxCell/Activities.ear/lc.events.spi.jar
/opt/IBM/WebSphere/AppServer/profiles/CnxNode01/installedApps/CnxCell/Files.ear/lc.events.spi.jar
...

Java benötigt eine MANIFEST.MF Datei für Jars mit Angabe des Einstiegspunktes sowie der externen Bibliothek, damit diese im ClassPath verfügbar ist:

Manifest-Version: 1.0
Class-Path: lc.events.spi.jar
Main-Class: classes.AuditHandler

Zum bauen wird ein JDK benötigt, und zwar ein altes JDK 8. Das verwenden CNX/WAS – diese können euren Code nicht laden, wenn dessen Jar mit einer neueren Version gebaut wurde. OpenJDK jdk8u362-b09 hat in meinem Test funktioniert. Folgender Aufruf geht davon aus, dass AuditHandler.java im Ordner classes liegt, lc.events.spi.jar auf der aktuellen Ebene.

jar cfm AuditHandler.jar MANIFEST.MF "classes/*.class"
jar uf AuditHandler.jar lc.events.spi.jar

Es macht Sinn, dies für die Entwicklung in einem Skript durchzuführen. Insbesondere, wenn das verwendete JDK nicht global in $PATH liegt. Dort können auch die erzeugten Class-Dateien bereinigt werden – so liegt bei jeder Ausführung ein sauberer Build vor.

#!/bin/bash
rm "classes/*.class"

set -e 
set -o pipefail
PATH=$PATH:~/jdk8u362-b09/bin

javac -cp ./FILES_lc.events.spi.jar "classes/*.java"
rm AuditHandler.jar
jar cfm AuditHandler.jar MANIFEST.MF "classes/*.class"
jar uf AuditHandler.jar lc.events.spi.jar

Anlegen des Event-Handlers in CNX

In events-config.xml im Knoten <postHandlers> eure Klasse registrieren:

<postHandler class="classes.AuditHandler" enabled="true" invoke="ASYNC" name="AuditHandler">
    <subscriptions>
       <subscription eventName="*" source="FILES" type="*"/>
    </subscriptions>
</postHandler>

Dieses Beispiel registriert sich für alle Ereignisse der Files-Anwendung. Beides lässt sich filtern bzw. erweitern, eine Liste aller unterstützten Anwendungen & Ereignisse ist in der API Dokumentation zu finden.2 Nach dem Ändern der Datei auf dem Deployment-Manager muss in der ISC ein sync durchgeführt und CNX neu gestartet werden.

Registrieren der Jar in WebSphere

Legt einen neuen Ordner an, der für alle WAS-Nodes verfügbar ist – z.B. im shared directory. Der Pfad ist frei wählbar, so lange der WAS-Node darauf zugreifen kann (oder eben die Nodes). In diesen Ordner wird die erzeugte AuditHandler.jar kopiert.

In der ISC unter Environment > Shared libraries einen neuen Eintrag mit dem Scope cells an, bei dem dieser Pfad zur Jar-Datei eingefügt wird. Im Falle von Abhängigkeiten muss deren Pfad in eine neue Zeile. Hierfür kann eine WAS-Variable (Environment > WebSphere variables) angelegt und im Classpath-Feld im Format ${Varname} verwendet werden.

EventHandler in der News-Anwendung aktivieren

Damit die Jar aufgerufen wird, muss sie in der ISC unter Applications > Application Types > WebSphere enterprise applications in der News Anwendung bei Shared library references aktiviert werden.

Hat die vorherige Registrierung funktioniert (Neustart), taucht in der Liste Available nun der AuditHandler auf. Durch anklicken und Pfeil nach Rechts wird er aktiviert.

Nach dem Neustart der News Anwendung sieht man in SystemOut.log die Informationen, welche über das Ereigniss übermittelt werden:

Es stehen weitere zur Verfügung. Die Events Reference Dokumentation listet diese ausführlich auf.3

Weitere Informationen

Über Properties ist es anscheinend möglich, dem Event Parameter mitzugeben. Ich habe das als Codeschnipsel wo kopiert, eine Möglichkeit zum Auslesen im Java-Code fehlt. Ist eventuell ein nützlicher Ansatz, falls man das braucht.

<postHandler class="classes.AuditHandler" enabled="true" invoke="ASYNC" name="AuditHandler">
    <subscriptions>
       <subscription eventName="*" source="FILES" type="*"/>
    </subscriptions>
    <properties>
       <property name="foo">bar</property>
    </properties>
</postHandler>

Quellen/Dokumentation

  1. https://ds_infolib.hcltechsw.com/ldd/lcwiki.nsf/xpAPIViewer.xsp?lookupName=HCL+Connections+7.0+API+Documentation#action=openDocument&res_title=Programming_an_event_handler_70&content=apicontent ↩︎
  2. https://ds_infolib.hcltechsw.com/ldd/lcwiki.nsf/xpAPIViewer.xsp?lookupName=HCL+Connections+7.0+API+Documentation#action=openDocument&res_title=Deploying_an_event_handler_70&content=apicontent ↩︎
  3. https://ds_infolib.hcltechsw.com/ldd/lcwiki.nsf/xpAPIViewer.xsp?lookupName=HCL+Connections+7.0+API+Documentation#action=openDocument&res_title=HCL_Connections_Event_Reference_-_Files_70&content=apicontent ↩︎

Leave a Reply