oboeck
I'm new here

Wie baue ich eine Library (Bibliothek) Komponente

Hallo zusammen,

ich habe in der Vergangenheit schon das ein oder andere kleine Modul gebaut welche aber immer nur aus Executables, Plugins oder Providern bestand.

Hierfür habe ich auch immer nur die PUBLIC Komponente für den ModulDeskriptor genutzt.

Jetzt habe ich den Anwendungsfall das ich eine selbst geschriebene Klasse die eigentlich keine Verbindung zur FS API benötigt innerhalb von Templates oder Skripten verfügbar machen möchte.

Die Funktion der Klasse ist es PDFs und Office Dokumente über ein per Dependency eingebebundenen Apache-Tika-Parser zu parsen und den Text daraus zurück zu geben.

Ich will in den FS Templates die Klasse anhand dieser Anleitung nutzen könne: Re: Methode aus eigenem Modul in einer Vorlage aufrufen

Hallo,

du kannst die Klasse/Methode über

     $CMS_VALUE(class("mein.package.Klasse").methode())$

bzw eine Instanz/Konstruktor über

     $CMS_SET(class, class("mein.package.Klasse").new(...))$ 

     $CMS_SET(class, class("mein.package.Klasse").instance(...))$ 

holen.

Laut Doku könnte man ja hierfür die LIBRARY Komponente verwenden, denn ich will nicht unbedingt aus meiner Klasse eine Executable machen.

Nun sieht meine module.xml so aus:

<?xml version="1.0" encoding="UTF-8"?>

<module>

    <name>${project.name}</name>

    <version>${project.version}_${module.build.timestamp}​</version>

    <description>​${project.description}</description>

    <vendor>${project.organization.name}​</vendor>

    <components>

        <library>

            <name>DocumentParser</name>

            <resources>

                <resource>lib/${project.artifactId}-${project.version}.jar​</resource>

            </resources>

        </library>

    </components>

    <resources>

        ${module.resources.global.legacy}

    </resources>

</module>

Und die POM.xml sieht so aus (ich nutzte die firstspirit-module-parent-pom für das bauen der .fsm (ich kenne zwar noch den Weg über ein selbst geschriebens Plugin, aber ich wollte an dieser Stelle mal den von e-Spirit vorgeschlagenen Weg austesten)

<?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>de.xxx.firstspirit.module</groupId>

    <artifactId>tika-document-parser</artifactId>

    <version>1.0.0-SNAPSHOT</version>

    <name>Document Parser</name>

    <description>Module to parse documents like pdf, docx, e.g.</description>

    <parent>

        <groupId>com.espirit.ps.maven</groupId>

        <artifactId>firstspirit-module-parent-pom</artifactId>

        <version>3.0.1</version>

    </parent>

    <properties>

        <firstspirit.version>​5.2.180909​</firstspirit.version>

        <tika.version>1.20</tika.version>

        <module.build.timestamp>${maven.build.timestamp}</module.build.timestamp>

        <maven.build.timestamp.format>yyyyMMdd-HHmmss</maven.build.timestamp.format>

    </properties>

    <dependencies>

        <dependency>

            <groupId>org.apache.tika</groupId>

            <artifactId>tika-core</artifactId>

            <version>${tika.version}</version>

        </dependency>

        <dependency>

            <groupId>org.apache.tika</groupId>

            <artifactId>tika-parsers</artifactId>

            <version>${tika.version}</version>

        </dependency>

        ...

    </dependencies>

</project>

Das Modul erzeugt ja beim maven build die .jar Datei für das Projekt mit folgenden Klassen und legt diese in den lib Ordner. Und genau diese erzeugte .jar verlinke ich dann in der LIBRARY Komponenten (Bitte Bescheid geben wenn ich hier einen Denkfehler habe).

intellij_folder.jpg

Und die Impl Klasse sieht so aus, diese will ich dann auch in den Templates aufrufen können:

public class DocumentParserImpl {

     private static final String ENCODING_UTF8 = "UTF-8";

     public DocumentParserImpl() {}

     public String testString() {

          return "TEST from Module";

     }

}

Folgener Aufruf ist z.b. in einem Skript oder RenderTemplate gewünscht (siehe oben).

$CMS_SET(set_return, class("de.package.DocumentParserImpl").testString())$ 

Nichts desto trotz erhalte ich folgende Fehlermeldung wenn ich das Modul dann installieren will.

FSVersion=5.2.180909.77696#5536;JDK=1.8.0_191 64bit Oracle Corporation;OS=Linux 3.10.0-957.1.3.el7.x86_64 amd64;Date=25.01.2019 11:58:44

java.io.FileNotFoundException: /home/oboeck/Dokumente/tika-document-parser-1.0.0-SNAPSHOT.fsm: resource 'lib/tika-document-parser-1.0.0-SNAPSHOT.jar​' not found!

at de.espirit.firstspirit.module.descriptor.AbstractDescriptor$ResourceDescriptor.validate(AbstractDescriptor.java:670)

at de.espirit.firstspirit.module.descriptor.AbstractDescriptor.validate(AbstractDescriptor.java:184)

at de.espirit.firstspirit.module.descriptor.ModuleDescriptor.validate(ModuleDescriptor.java:220)

at de.espirit.firstspirit.server.module.ModuleManagerProxy$ClientModuleManager.checkModuleVersion(ModuleManagerProxy.java:322)

at de.espirit.firstspirit.admin.gui.ModuleConfigurationPanel$2.run(ModuleConfigurationPanel.java:345)

at java.lang.Thread.run(Thread.java:748)

Zusatz:

Wenn ich die module.xml so konfiguriere erhalte ich in FS immer ClassNotFoundExceptions wenn ich den DocumentParserImpl aufrufen möchte, also denke ich das man Public nur nehmen kann wenn man auch ein FS Interface o.ä. einbindet?

<?xml version="1.0" encoding="UTF-8"?>

<module>

    <name>${project.name}</name>

    <version>${project.version}_${module.build.timestamp}​</version>

    <description>​${project.description}</description>

    <vendor>${project.organization.name}​</vendor>

    <components>

      <public>

          <name>DocumentParser</name>

          <class>de.xxx.firstspirit.module.documentparser.DocumentParserImpl</class>

      </public>

    </components>

    <resources>

        ${module.resources.global.legacy}

    </resources>

</module>

Vielen Dank für eure Hilfe, ich komm hier sonst irgendwie nicht weiter.

Vorschläge wie man es besser macht sind herzlich willkommen, das wissen habe ich mir aus einer Mischung aus Community Einträgen und Entwickler Doku zusammen gebastelt.

Olli

0 Kudos
7 Replies
NMc
Crownpeak employee
Crownpeak employee

Moin Olli,

Ich bin wirklich kein Profi was die module.xmls angeht, aber die Doku sagt mir, dass du prinzipiell den richtigen Weg gehst.

Siehe hier https://docs.e-spirit.com/odfs/vorlagenentwick/vorlagensyntax/funktionen/anweisungen/class/index.htm...

<library>

<name>MeinModul-library</name>

<resources>

<resource>lib/mymodule-1.0.0-SNAPSHOT.jar</resource>

<resource name="com.example.package" scope="server" version="1.0.0-SNAPSHOT">lib/myclasscontainer-1.0.0-SNAPSHOT.jar</resource>

</resources>

</library>

Wenn du das in deiner module.xml stehen hast, sollte es eigentlich möglich sein mit folgendem Befehl statische Funktionalitäten deiner Klasse MyClass aufzurufen:

$CMS_VALUE(class("com.example.package.MyClass").methode())$

(Willst du auf nicht statische Inhalte zugreifen, musst du die Klasse natürlich zuvor instanziieren.)

Dabei sollte sich in deinem Lib-Ordner natürlich eine

mymodule-1.0.0-SNAPSHOT.jar (= Das eigentliche Modul) und eine

myclasscontainer-1.0.0-SNAPSHOT.jar (= Dort liegt die Logik, die du aus dem Template aufrufen möchtest)

befinden.

Ich hoffe ich konnte dir weiterhelfen,

Gruß

Nico

0 Kudos

Hmm... für mich ist die Unterscheidung zwischen:

  • <resource>lib/mymodule-1.0.0-SNAPSHOT.jar</resource> 

und

  • <resource name="com.example.package" scope="server" version="1.0.0-SNAPSHOT">lib/myclasscontainer-1.0.0-SNAPSHOT.jar</resource>

irgendwie nicht verständlich... wenn ich das fsm baue landet einfach nur eine einzige tika-document-parser-1.0.0-SNAPSHOT.jar innerhalb der tika-document-parser-1.0.0-SNAPSHOT.fsm unterhalb des LIB Folders (und natürlich alle möglichen dependencies die da so über die pom.xml rumliegen)... was aber meines wissens glaube ich durch folgende Zeite passiert

  1. <module>  
  2.     <resources> 
  3.         ${module.resources.global.legacy} 
  4.     </resources>
  5. <module>  

200833_pastedImage_4.png

Inhalt der tika-document-parser-1.0.0-SNAPSHOT.fsm im lib-Folder:

200835_pastedImage_7.png

Und innerhalb der tika-document-parser-1.0.0-SNAPSHOT.jar dann halt meine eigentlichen Java Klassen.

Was ist der unterschied zwischen mymodule-1.0.0-SNAPSHOT.jar und myclasscontainer-1.0.0-SNAPSHOT.jar

0 Kudos
mbergmann
Crownpeak employee

Hallo Olli,

die FileNotFoundException sieht erstmal stark danach aus, als ob die jar mit einem anderen Namen/Pfad im FSM liegt als in der erzeugten module.xml angegeben. Schau bitte mal in das erzeugte FSM rein, ob der Pfad aus in der darin enthaltenen module.xml mit dem tatsächlichen Ablageort im FSM übereinstimmt. Das heißt, da stimmt schonmal was nicht. Bevor diese Fehlermeldung nicht weg ist, brauchst du andere Dinge gar nicht erst versuchen 😉

Richtig, <public> bringt nur etwas wenn es sich um die Implementierung eines dafür vorgesehenen Interfaces aus der FS-API handelt, z.B. ein Plugin oder Executable.

Eine Library-Komponente funktioniert da grundsätzlich schon - zumindest wenn man diesen Weg gehen will (s.u. für eine vielleicht bessere Alternative(n)). Ein jar reicht hier - Nicos Beispiel passt eher zu einem anderen Fall bzw. es wird glaube ich nicht ganz klar dass mit der "anderen" jar eine Fremd-Lib gemeint war. Die brauchst du natürlich.

Außerdem nutzt Du aktuell beim Aufruf die Syntax zum Aufruf einer ​statischen ​Methode. Willst Du ein Objekt, brauchst Du tatsächlich ein class(...).new(), siehe hier (aber hattes Du ja auch schon probiert - was aber eben wegen dem Problem bei der Installation nichts gebracht hat).

Letztlich kommen hier mehrere Dinge zusammen. Erster Schritt wäre, die FileNotFoundException bei der Modulinstallation loszuwerden. Dann erstmal testen ob die Klasse wirklich da ist z.B. mit $CMS_VALUE(class("...").getName())$ im Template. Dann erst das echte Parsen.

Ein weiteres grundsätzliches Problem an der Sache ist aber, dass - wenn Du die Klasse "direkt" nutzen willst, die entsprechende JAR im "server scope" zur Verfügung gestellt werden muss. Solange das nur eine eigene Klasse ist, ist das nicht so problematisch - das eigentliche Problem sind dann eher die Abhängigkeiten (=Fremd-Libs) die Du mitbringst. Damit die in FS dann aus Deiner Klasse heraus nutzbar sind, müssen deren jars dann auch in den Server scope. Und das kann irgendwann zu Konflikten führen, wenn die (später vielleicht...) auch von anderen Modulen genutzt / mitgebracht werden - aber in einer anderen Version. Da bringt die Nutzung von Executables den Vorteil, dass man die (und damit auch deren Abhängigkeiten) in den module scope legen kann und dann die Modul-Ressourcen voneinander abgeschottet sind.

Wenn die Executable dann als <public> Komponente implementiert wird, ist sie über ihren Namen zugreifbar (über den Umweg über ein kleines Skript). Wichtig: <public> ist was anderes als scope="public", bei Executables greift da ein "Spezialmechanismus". Wie man sowas grundsätzlich bauen kann, habe ich hier mal beschrieben. Damit passiert die Ausgabe nicht "direkt" über das Executable (ginge zwar auch, ist für meinen Geschmack aber zuviel "Magie" und unflexibel), sondern es wird eher als eine Art "Factory" genutzt um sich Instanzen eigener Klassen geben zu lassen. Auf denen kann man dann Methoden aufrufen.

Letztlich hat die direkte Nutzung des Klassennamens bzw. des FQCN auch noch den Nachteil, dass es nicht refactoring-sicher ist, Executables führen über ihren <name> quasi eine zusätzliche „Abstraktionsschicht“ ein.

Alternative

Ich könnte mir vorstellen, dass das Parsen des PDF - vor allem bei jedem Aufruf / jeder Generierung - nicht ganz effizient ist. Vielleicht wäre es hier besser, einen UploadHook zu implementieren, der beim Hochladen bzw. Aktualisieren des PDF dieses EINMAL parst und den Text dann in den Metadaten des Medienobjektes ablegt. Oder ggf. auch in einer detaillierteren Struktur (=mehrere Eingabekomponenten) dort.

Viele Grüße

Michael

0 Kudos

Hallo Olli,

ich hatte bei meiner ursprünglichen Antwort zwei Dinge durcheinander geworfen - habe sie mal entsprechend geändert.

Viele Grüße

Michael

0 Kudos

Hallo Michael,

erst einmal vielen lieben Dank für die ausführliche Beschreibung, das hat schon einiges geholfen!

Den Grund warum die FileNotFoundException geworfen wurde habe ich finden können. Ich hatte einen Fehler in der pom.xml dort hatte ich als artifactID = tika-document-parser eingetragen.

Die von e-Spirit bereitgestellte fsm-parent-pom + module-resource-plugin haben dann eine .jar Datei erzeugt die tika-document-parser-1.0.0.jar hieß.

Aber im Modul Deskriptor für die Ressource stand als Name hinter dem Doppelpunkt der Package Name aus dem eigentlichen Projekt. Deswegen konnte er die jar nicht finden, weil er eigentlich eine  documentparser-1.0.0-SNAPSHOT.jar erwartet hat.

<resource name="de.xxx.firstspirit.module:documentparser">lib/tika-document-parser-1.0.0-SNAPSHOT.jar</resource>

Nichts desto trotz habe ich mich auf Grund deines Hinweises bzgl. der module und server Scope Thematik gegen eine LIBRARY Komponente entschieden und stattdessen eine PUBLIC Komponente mit einem Executable verwendet.

Erstes Hindernis war allerdings das die Executable im "server"-Scope vorhanden sein muss, sonst konnte ich Sie im Script-Template nicht aufrufen. Dort kam dann auch immer ClassNotFound so lange sich die documentparser-1.0.0-SNAPSHOT.jar nicht im scope="server" befand.

Danach funktioniert das auch soweit die Klasse per Skript aufzurufen. Allerdings haben alle anderen Module die ich bisher gebaut habe nie so viele Dependencies benötigt.

Ich habe jetzt das Problem, das ich zwar die Executable aufrufen kann, aber diese nutzt ja unter der Haube diese ganzen anderen libs und jetzt sag er mir das er zu diesen die Klassen nicht finden kann. Ich vermute weil diese im scope="module" sitzen?

Ich habe schon angefangen diese nach und nach in der plugin configuration des module-resource-plugins innerhalb der pom.xml auf scope="server" zu setzten, aber ich denke nicht dass das der richtige Weg ist, oder?

<build>

    <pluginManagement>

        <plugins>

            <plugin>

                <groupId>com.espirit.ps.psci</groupId>

                <artifactId>module-resource-plugin</artifactId>

                <version>0.1</version>

                <executions>

                    <execution>

                        <id>generate-module-resources</id>

                        <phase>generate-resources</phase>

                        <goals>

                            <goal>generate</goal>

                        </goals>

                        <configuration>

                            <resources>

                                <resource>

                                    <scope>server</scope>

                                    <identifier>${project.groupId}:${project.artifactId}</identifier>

                                    <path>lib/${project.artifactId}-${project.version}.jar</path>

                                </resource>

<!--

                                <resource>

                                    <scope>server</scope>

                                    <identifier>org.apache.tika:tika-core</identifier>

                                    <path>lib/</path>

                                </resource>

-->

                            </resources>

                        </configuration>

                    </execution>

                </executions>

            </plugin>

        </plugins>

    </pluginManagement>

</build>

Von der Logik her funktioniert die Klasse so:

DocumentParserExecutable -> DocumentParserService -> Tika-core.jar -> Tika-Parser.jar -> diverse Parser Libraries je nach Documenten Typ.

Ich habe mal die aktuellste module.xml mit drangehängt.

Und hier ist noch der Aufruf in dem Script (ist noch nicht die finale Version):

//!Beanshell

import de.kernpunkt.firstspirit.module.documentparser.DocumentParserExecutable;

Map map = new HashMap();

map.put("documentInputStream", sc_stream);

#! executable-class

executable = new DocumentParserExecutable();

executable.execute(map);

Hast du da noch eine Idee?

Danke und Grüße

Olli

0 Kudos
mbergmann
Crownpeak employee

Hallo Olli,

hast Du dir dieses Posting mal angesehen? Damit reicht der module scope. Der Trick ist, dass im entsprechenden "Script" die Notation

#executable-class

NAME_DER_PUBLIC_EXECUTABLE_LAUT_MODULE_XML

benutzt wird. Es ist halt ein kleiner Umweg: Das (bzw. ein weiteres) Executable (z.B. ein "ParserProviderExecutable") macht nicht "selbst" die Arbeit, sondern ist erstmal nur dazu da, ein entsprechendes Objekt "herauszureichen" - in Deinem Fall eben den Parser - der muss dann selber kein Executable sein, sondern eine "normale" Klasse.

Oder habe ich etwas übersehen warum das in Deinem Fall vielleicht nicht "passt"?

Viele Grüße

Michael

0 Kudos

Hey Michael,

leider bleibt es dabei, auch mit der Umsetzung anhand deines Postings aus dem anderen Thread.

Der sagt mir ständig, das er Klassen aus LIBs die aktuell auf module=scope stehen nicht finden kann.

java.lang.NoClassDefFoundError: org/apache/tika/exception/TikaException

In dem Objekt, also dem Parser, den ich aus der Executable rausgebe (wie in deinem Posting), wird halt dann die externe Lib die als module=scope eingebunden ist verwendet und dazu kann er dann die Klassen nicht finden.

VG, Olli

0 Kudos