# Apache FOP 0.20-5 mit JNI von C++ Code aufrufen



## Chris81T (4. Sep 2008)

Hallo zusammen,

ich hatte gestern schon einen Thread  hier gestartet, um zu wissen, wie man Java Methoden mit JNI im Cpp Code aufruft.

Das klappt nun wunderbar und kann nun zur nächsten Problematik gehen:

( es wurde mir auch eine Alternative hierzu schon genannt, dieses mit ner Interprocesskommunikation  zu lösen, möchte es aber mit JNI probieren )

*Mein Ziel:*
Mit C++ Code / Funktionen den JAVA Apache FOP 0.20-5 (die neuere Version kann leider nicht verwendet werden!) mit Hilfe von JNI anzusprechen, damit dieser aus einer vorhandenen XML und XSL-FO Datei daraus ein PDF File erzeugt.


Einfache Methoden aus einer Java Class aufzurufen, hat bisher geklappt, aber bei _imports_ hört's mit meinen JAVA Kenntnissen auf

*Mein(e) Problem(e) / Fragen:*

Wie gehe ich das nun am Besten an? Ich habe mir gedacht, ich schreibe eine kleine JAVA Klasse mit einer Methode, die dazu dient, den JAVA FOP anzusprechen und die Arbeit XML2PDF zu erfüllen. Diese Methode würde ich dann via JNI in meinem C++/Qt Hauptprogramm nutzen. 

Um dieses Vorhaben zu Testen, will ich mir das nun erstmal unabhängig in ner JAVA Klasse testen


```
import java.util.*;
import java.util.Date;
import java.io.*;
import java.net.*;

import org.apache.fop.apps.Driver;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.Options;

class InvokingFop
{

	public static void main(String[] args)
	{
		System.out.println("JAVA InvokingFop: void main(String[] args) {..}");

                // Hier soll dann der passende Code zum Test reinkommen, der den FOP zum Transformieren anspricht
	}	
}
```

Wenn ich das "Kompiliere" mit javac, meckert dieser, dass er die 3 imports von apache.fop nicht kennt. Das macht ja auch ganz klar Sinn, aber habe keine Ahnung, wie man das JAVA bekannt gibt.

Ich habe da nun eine fop.jar. Ich denke mal, dass diese halt irgendwie bekannt gegeben werden muss. Aber wie?


Falls jemand noch Anregungen hat, wie man mein Ziel auch noch lösen kann, bitte melden



Vielen Dank für eure Hilfe


----------



## Murray (4. Sep 2008)

Sowohl zum Übersetzen als auch später zur Laufzeit muss die VM alle benötigten Klassen finden können. Dazu gibt es den sog. Classpath. Den kann man als Umgebungsvariable definieren, oder aber beim Aufruf des Compilers bzw. der VM als Option mitgeben.
Also z.B.:
javac -cp .;fop.jar MeineKlasse.java
java -cp .;fop.jar MeineKlasse


----------



## Chris81T (5. Sep 2008)

So, es gibt von Apache FOP ein Java Beispiel/Code, der genau mein Vorhaben realisiert. Und zwar aus XML + XSL-FO = PDF zu machen.

Wenn ich das JAVA Prog. (das Beispiel verwendet und mit javac compiliert.) mit java -cp passenden classpaths Invokefop aufrufe, tut der Apache Fop auch seine Arbeit. Also das funzt schon ma.

Wenn ich mein Cpp Prog. starte, wird die Invokefop JAVA Klasse nicht gefunden (C++ Zeile 75) 

:arrow: habe noch ne Art HelloWorld.class mit am Start und wenn ich die "Invokefop" mit der HelloW Klasse in Zeile 75 austausche, wird die passende Klasse des Hello Worlds gefunden und korrekt die JAVA main Methode aufgerufen. Das sollte ja dann schon zeigen, dass auch der Cpp Code soweit okay ist.

:arrow: Ich habe durch passendens Auskommentieren in der Invokefop.java herausgefunden, dass erst dann die Invokefop Klasse nicht mehr gefunden wird, wenn die JAVA Zeile 36 nicht auskommentiert ist.

@Murray + all:
Könntest du mir bitte noch helfen, wie man der VM die Classpath's bekannt macht?

Bisher habe ich das so in meiner C++ stehen:

```
options[0].optionString = "-Djava.class.path=C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs";
```

In fopLibs liegen alle jar Files, die benötigt werden. Ist dass dann so okay, oder muss es irgendwie anders gemacht werden?

Wer weis Rat?


Vielen Dank


( Auch beim nachfolgendem Code gilt, ist nur en dirty Prototype  )
C++ Code: 

```
// Qt requiered Includes
#include <QApplication>
#include <QtDebug>
#include <QPushButton>

// JNI requiered Includes
#include <jni.h>

JNIEnv * createJavaVM(JavaVM * jvm, JNIEnv * env)
{
	qDebug() << "bool createJavaVM(JavaVM * jvm, JNIEnv * env)";
	
	// benötigte Variablen:
	JavaVMOption options[1];
	JavaVMInitArgs vm_args;	
	
	// Version festlegen:
	vm_args.version = JNI_VERSION_1_6;
	
	// default Argumente holen:
	JNI_GetDefaultJavaVMInitArgs(&vm_args);
	
	// Pfad zur Java- Klasse festlegen:
	// --> ist der Pfad zu der *.class JAVA Datei/en
	options[0].optionString = "-Djava.class.path=C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs";
	vm_args.nOptions = 1;
	vm_args.options = options;
	
	// zur Fehlererkennung:
	jint value;
	
	// JavaVM erzeugen:
	value = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
	if(value < 0)
	{
		qDebug() << value << " : ";
		switch(value)
		{
			case JNI_ERR:
				qDebug() << "JNI_ERR" << endl;
			break;
			case JNI_EDETACHED:
				qDebug() << "JNI_EDETACHED" << endl;
			break;
			case JNI_EVERSION:
				qDebug() << "JNI_EVERSION" << endl;
			break;
			case JNI_ENOMEM:
				qDebug() << "JNI_ENOMEM" << endl;
			break;
			case JNI_EEXIST:
				qDebug() << "JNI_EEXIST" << endl;
			break;
			case JNI_EINVAL:
				qDebug() << "JNI_EINVAL" << endl;
			break;
		}
		return 0;
	}
	qDebug() << "CreateJavaVM success!!" << endl;
	qDebug() << env; // TODO löschen
	// env wurde von JNI verändert und muss zurückgegeben werden
	return env;
}

bool callApacheFop( JNIEnv * env)
{
	qDebug() << "bool callApacheFop( JNIEnv * env)";
	
	// jclass zeigt auf die gewünschte JAVA Klasse:
	qDebug() << env; // TODO löschen
	jclass XML2PDF;

	if (!(XML2PDF = env->FindClass("Invokefop")))
	{
		qDebug() << "FindClass failed :(" << endl;
		return false;
	}
	qDebug() << "FindClass success!!" << endl;
	
	// jmethodID zeigt auf die gewünschte JAVA Methode:
	jmethodID convertXML2PDF;
	
	if(!(convertXML2PDF = env->GetStaticMethodID(XML2PDF, "main", "([Ljava/lang/String;)V"))) 
		{	
			qDebug() << "GetStaticMethodID failed :(" << endl;
			return false;
		}
	qDebug() << "GetStaticMethodID success!!" << endl;

	// mit jclass und passender jmethodID wird hier die Funktion ausgeführt:
	env->CallStaticVoidMethod(XML2PDF, convertXML2PDF, "LSPRT03.xml", "LSPRT03.xsl", "LSPRT03.pdf");
	return true;
}

// How many times the fop will be called: ( up to now unused )
#define MAX 30

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

	qDebug() << "Using Apache FOP with Java Native Interface...";	

//	Testing Area: call Apache FOP methods with JNI:
//	*******************************************************************************
	// benötigte Variablenzeiger:
    JavaVM * jvm;
	JNIEnv * env;

	if((env = createJavaVM(jvm, env)))
	{ 	// creating success
		qDebug() << "Now try to call the Apache FOP...";
		if(callApacheFop(env))
			qDebug() << "calling FOP success!!";
		else
			qDebug() << "calling FOP failed!!";
		
		// zum Schluss wird die VM zerstört:
//		jvm->DestroyJavaVM();		
	}
			
//	*******************************************************************************
	
	QPushButton closeButton("Fop Qt Jni :: Testprojekt &beenden!");
    closeButton.resize(200, 200);
	
	QObject::connect(&closeButton, SIGNAL(clicked()),
					 &app, SLOT(quit()));

    closeButton.show();
    int result = app.exec();
	qDebug() << "Finished Executing. Bye bye";
	return result;
}
```

JAVA Code Beispiel:

```
//Java
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

//JAXP
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.Source;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.sax.SAXResult;

//Avalon
import org.apache.avalon.framework.ExceptionUtil;
import org.apache.avalon.framework.logger.ConsoleLogger;
import org.apache.avalon.framework.logger.Logger;

//FOP
import org.apache.fop.apps.Driver;
import org.apache.fop.apps.FOPException;
import org.apache.fop.messaging.MessageHandler;

public class Invokefop 
{

    public void convertXML2PDF(File xml, File xslt, File pdf) 
                throws IOException, FOPException, TransformerException {
        //Construct driver
        Driver driver = new Driver();
        
        //Setup logger
        Logger logger = new ConsoleLogger(ConsoleLogger.LEVEL_INFO);
        driver.setLogger(logger);
        MessageHandler.setScreenLogger(logger);

        //Setup Renderer (output format)        
        driver.setRenderer(Driver.RENDER_PDF);
        
        //Setup output
        OutputStream out = new java.io.FileOutputStream(pdf);
        try {
            driver.setOutputStream(out);

            //Setup XSLT
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer(new StreamSource(xslt));
        
            //Setup input for XSLT transformation
            Source src = new StreamSource(xml);
        
            //Resulting SAX events (the generated FO) must be piped through to FOP
            Result res = new SAXResult(driver.getContentHandler());

            //Start XSLT transformation and FOP processing
            transformer.transform(src, res);
        } finally {
            out.close();
        }
    }


    public static void main(String[] args) {
        try {
            System.out.println("JAVA XML2PDF:: FOP ExampleXML2PDF\n");
            System.out.println("JAVA XML2PDF:: Preparing...");

            //Setup directories
			File base = new File("../");
            File baseDir = new File(base, "in");
            File outDir = new File(base, "out");
            outDir.mkdirs();

            //Setup input and output files            
            File xmlfile = new File(baseDir, args[0]);
            File xsltfile = new File(baseDir, args[1]);
            File pdffile = new File(outDir, args[2]);

            System.out.println("JAVA XML2PDF:: Input: XML (" + xmlfile + ")");
            System.out.println("JAVA XML2PDF:: Stylesheet: " + xsltfile);
            System.out.println("JAVA XML2PDF:: Output: PDF (" + pdffile + ")");
            System.out.println();
            System.out.println("JAVA XML2PDF:: Transforming...");
            
            Invokefop app = new Invokefop();
            app.convertXML2PDF(xmlfile, xsltfile, pdffile);
            
            System.out.println("JAVA XML2PDF:: Success!");
        } catch (Exception e) {
            System.err.println(ExceptionUtil.printStackTrace(e));
            System.exit(-1);
        }
    }
}
```


----------



## Murray (5. Sep 2008)

Beim Classpath reicht es nicht, ein Verzeichnis anzugeben, in dem sich Jar-Files befinden. Man muss vielmehr jedes einzelne Jar-File explizit aufführen. Seit Java 6 gibt es Classpath-Wildcards; man kann also ein "*" schreiben, um alle Jar-Files eines Verzeichnisses zu referenzieren.


----------



## Murray (5. Sep 2008)

Um dem nächsten Fehler vorzubeugen: Wilddcards matchen nur JAR-Files; einzelne Class-Files würden damit trotzdem nicht gefunden. Wenn Du auch mit einzelnen Class-Files arbeitest, dann brauchst Du in jedem Fall zwei Einträge.

Also entweder (für alle Java-Versionen)

```
options[0].optionString = "-Djava.class.path=C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs;C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\fop.jar";
```
oder (ab Java 6): 

```
options[0].optionString = "-Djava.class.path=C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs;C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\*";
```


----------



## Chris81T (5. Sep 2008)

@Murray:
Vielen Dank, da hab ich nun wieder einiges dazugelernt. Nun wird der FOP auch angestupst, aber bricht irgendwann ab.

Also konkret:
die JAVA main läuft durch, die convertXML2PDF wird aufgerufen und ab der JAVA Zeile 32 ist Schicht (weis das, weil ich da debug Kommentare eingefügt habe) und die letzten Kommentare kommen dann von der C++ Seite wieder.

Von Grund auf sollte doch die JAVA Methode durch den C++ Aufruf komplett durchlaufen (bei mir die main und allem was dahintersteckt) und dann danach weiter im C++ Code. 

>>  Schon mal so einen Fall gehabt, oder eine Idee :?:

Wenn ich die JAVA Main mit JAVA direkt aufrufe, läuft der FOP ohne Mucken durch.


----------



## Murray (5. Sep 2008)

Ersetze doch mal - testweise! - Deine Zeile 32 durch

```
Driver driver null;
      try {
        driver  = new Driver(); 
      } catch ( Exception excpt) {
          System.out.println( "Caught unexpected Exception " + excpt);
          excpt.printStackTrace( System.out);
      } catch ( Error err) {
          System.out.println( "Caught unexpected Error " + err);
          err.printStackTrace( System.out);
      }
```


----------



## Chris81T (5. Sep 2008)

So sieht's nu im JAVA Codeabschnitt aus:

```
//Construct driver
        System.out.println("JAVA XML2PDF:: Construct Driver...");
		//Driver driver = new Driver();
        
		///////////////////////////////////
		Driver driver = null;
	    try {
	      driver  = new Driver();
	    } catch ( Exception excpt) {
	        System.out.println( "Caught unexpected Exception " + excpt);
	        excpt.printStackTrace( System.out);
	    } catch ( Error err) {
	        System.out.println( "Caught unexpected Error " + err);
	        err.printStackTrace( System.out);
	    }
        ///////////////////////////////////
		
        //Setup logger
        System.out.println("JAVA XML2PDF:: Setup logger...");
        Logger logger = new ConsoleLogger(ConsoleLogger.LEVEL_INFO);
        driver.setLogger(logger);
        MessageHandler.setScreenLogger(logger);

        //Setup Renderer (output format)        
        System.out.println("JAVA XML2PDF:: Setup Renderer (output format)...");
        driver.setRenderer(Driver.RENDER_PDF);
```

Das kommt als Ausgabe:


> Using Apache FOP with Java Native Interface...
> bool createJavaVM(JavaVM * jvm, JNIEnv * env)
> CreateJavaVM success!!
> 
> ...



Hm, _NoClassDefFoundError_. Die Klasse Driver sollte aber wohl in der jop.jar drin sein?! Oder? 

Also wegen den "includes". So sieht nun meine options aus:


```
options[0].optionString = "-Djava.class.path=
C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\;
C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\jop.jar;
C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\batik.jar;
C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\avalon-framework-cvs-20020806.jar;
C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\xalan-2.4.1.jar;
C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\xercesImpl-2.2.1.jar;
C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\xml-apis.jar;
C:\\CHT\\Test- Projects\\ApacheFopJni\\fopLibs\\XML2PDF.class";
```

Das sind laut der FOP Doku alle, die benötigt werden. Und hab noch die eigentliche Class einfach noch mit angegeben.

Ich seh das Problem nocht nicht so ganz, bzw. was man noch angeben muss :?:

PS: XML2PDF ist gleich dem oben genannten "Invokefop" (nicht beirren lassen)


----------



## Chris81T (5. Sep 2008)

@all:

Immer wieder diese Tippfehler, die man auf Anhieb nicht sieht  :shock: 

Kann ja auch schlecht gehen, wenn man anstatt fop -> jop im Classpath angibt. Jetzt läuft's   


Nochmal großen Dank an Murray!


----------

