Integrazione tra Java e Jython
Note: Return to tutorial view.
Introduzione
Jython è l'implementazione dell'interprete Python sulla virtual machine Java. Per evitare confusione d'ora in avanti chiamerò Python il liguaggio e Jython l'interprete.
Per un'introduzione su Jython vi invio a consultare questa guida.
Qui invece approfondiremo l'argomento dell'integrazione di Jython e Java.
Perchè ?
E' innegabile che uno dei vantaggi che ci dà Python rispetto a Java è una estrema velocità di sviluppo.
Questa velocità può essere sfruttata ad esempio per fare prototipi (da sostituire in seguito in Java) oppure per sviluppare componenti con un alto tasso di manutenzione.
Come? (un primo metodo)
Ci sono fondamentalmente due modi per utilizzare del codice Python in Java:
Il primo modo è quello di utilizzare nel nostro programma Java la classe org.python.util.PythonInterpreter che contiene l'interprete Jython. Questa classe si trova nel file jython.jar e quindi bisogna avere l'accortezza di aggiugerlo alla classpath java.
E' necessario importare le classi necessarie con:
import org.python.util.*; import org.python.core.*;
Nel codice si può quindi creare l'interprete:
PySystemState.initialize(); PythonInterpreter interp = new PythonInterpreter;
Questo ci permette di richiamare del codice Python contenuto in una stringa
interp.exec("print 'hello world'");o in un file:
interp.execfile("helloworld.py");Con questo sistema il codice gira in modo isolato da quello Java e non permette di utilizzare gli oggetti definiti localmente a meno di non "iniettarli" nell'interprete con l'istruzione:
String nomeattributo = "pippo";
interp.set("nomeattributo",nomeattributo);I dati elaborati si possono invece ottenere con:
String s = (String)interp.get("nomeattributo", String.class);Il cast in questo caso è necessario in quanto il metodo restituisce un oggetto di tipo PyObject.
Come integrare
Come ? (il secondo metodo)
L'altro sistema (quello che verrà approfondito) invece si integra perfettamente con Java ma richiede la compilazione del codice.
Fortunatamente Jython ci permette di costruire delle classi compatibili (ed integrabili) con Java!! Bisogna però fare attenzione ad alcune limitazioni ed impostare la nostra classe secondo alcune regole.
In primo luogo faccio notare che, sebbene da Jython non ci sia alcun problema ad utilizzare classi Java, da Java non è possibile, senza questi accorgimenti, utilizzare classi Jython.
Java ha una politica di incapsulamento molto più rigida di Python: i metodi e gli attributi sono esposti all'esterno solo se dichiarati "Public". Inoltre riveste molta importanza la "signature" del metodo: l'insieme formato dal nome del metodo e di tutti gli argomenti passati (compresi i loro tipi).
Python, al contrario, non ha particolari vincoli sulla visibilità dei metodi e degli attributi (a parte l'utilizzo del doppio underscore). Tutti i metodi e gli attributi sono esposti. Inoltre la signature non tiene conto degli argomenti che, in Python, sono variabili e tipizzati dinamicamente.
Per far dialogare questi due ambienti è quindi necessario:
- definire la nostra classe python in un file (analogamente a quanto si fa per Java). E' possibile avere più classi per file ma sarà possibile esporne una sola;
- la classe deve essere imparentata con una classe Java. E' possibile anche che sia figlia di java.lang.Object;
- a questo punto la classe esporrà i metodi che sono pubblici nelle classi genitrici. Per rendere pubblici ulteriori metodi deve essere messa la signature java nella docstring del metodo (preceduta da @sig);
- non è possibile rendere pubblici gli attributi di una classe ma è possibile rendere pubblici i metodi getter e setter di ogni attributo;
- una volta pronta la nostra classe si può compilare con jythonc. Questo comando è incluso nell'installazione di Jython e si occupa di tradurre il nostro codice Python in Java, di compilarlo e di impacchettarlo in un jar. Il comando è anche dotato di alcune utili opzioni per includere nel jar l'interprete ottenendo quindi un jar autonomo.
Attenzione:
Per utilizzare Jythonc è ncessario utilizzare jdk (java developer kit). Le jre (java runtime environment) non sono sufficienti per eseguire la compilazione. Le jdk si trovano sul sito della Sun. Bisogna accertarsi che la versione di Java utilizzata da Jython sia quella delle jdk. Per fare questo è necessario controllare lo script che lancia Jython e modificarlo. Ad esempio su Windows ho dovuto modificare
"C:\Programmi\Java\jre6\bin\java.exe" -Dpython.home="C:\jython2.2.1" -classpath "C:\jython2.2.1\jython.jar;%CLASSPATH%" org.python.util.jython %ARGS%
in
"C:\Programmi\java\jdk1.5.0_17\bin\java.exe" -Dpython.home="C:\jython2.2.1" -classpath "C:\jython2.2.1\jython.jar;%CLASSPATH%" org.python.util.jython %ARGS%
Un esempio
Cominciamo con il più classico esempio: "Hello world".
Come prima cosa creo il mio file Hello.py con la mia classe:
class Hello(java.lang.Object):
def __init__(self):
self.name=""
def say(self):
"""@sig public java.lang.String say()"""
return "hello %s" % self.name
def getName(self):
"""@sig public java.lang.String getName()"""
return self.name
def setName(self,txt):
"""@sig public void setName(java.lang.String txt)"""
self.name=txt
E' importante notare tre cose:
- la classe è figlia di una classe Java
- la doc string di alcuni metodi Python contengono la signature Java preceduta da "@sig"
- il tipo java String deve essere dichiarato per esteso (in Python non è importato implicitamente)
Una volta completato il mio file posso compilarlo con:
jythonc -p lupo --all -j Hello.jar Hello.py
Con l'opzione p indico il nome del package, l'opzione all include l'interprete nel jar, l'opzione j serve a dare il nome del jar.
Se la compilazione va a buon fine è possibile utilizzare questo jar da Java aggiungendolo alla classpath e importandolo in questo modo:
import lupo.Hello;
in seguito possiamo usarlo con:
saluto = new Hello();
saluto.setName("Maurizio");
System.out.println(saluto.say);
Un esempio di integrazione
Analizzeremo ora un caso particolare di integrazione di codice Python in un'applicazione Java: la creazione di componenti custom in Adobe Livecycle.
LiveCycle è un motore di workflow che permette di creare processi. Questi processi avranno delle variabili di input e di output e saranno formati da componenti legati l'uno all'altro.
LiveCycle esporrà in modo automatico questi processi come EJB, web services (SOAP), watched mail e watched folder.
Insieme al prodotto vengono forniti alcuni componenti (sono venduti in pacchetti).
I componenti che fornisce Adobe gestiscono il rendering di pdf a partire da template, trasformazione e parsing di file xml, spedizione email, interfacciamento con db (via jdbc), interfacciamento con webservices ecc.
I processi sono disegnati con un interfaccia grafica che si chiama WorkBench. In questo programma i componenti sono visualizzati come dei "blocchetti". Per disegnare un processo si collegano i componenti tra di loro determinando così l'ordine di esecuzione, costrutti di selezione, ciclo e programmazione concorrente.
Non mi dilungo di più su questo prodotto la documentazione di LiveCycle si trova sul sito di Adobe.
Per quello che ci interessa è prevista nell'architettura del sistema la creazione di componenti custom da inserire all'interno dei nostri processi. Questi componenti non sono altro che dei semplici jar con dentro una classe java, un file di configurazione xml, e un paio di immagini che saranno utilizzate per visualizzare il componente.
La documentazione completa si trova di nuovo sul sito di Adobe.
Un componente Python di esempio
Per realizzare il nostro componente Python bisogna creare la nostra classe python come abbiamo fatto precedentemente.
Creo quindi un file Convert.py con la mia classe.
import java
from trasformations import make_transform
import com.adobe.idp.Document as doc
import StringIO
class Convert(java.lang.Object):
def convert(self,txt):
"""@sig public java.lang.String convert(com.adobe.idp.Document txt)"""
f_in=open(str(txt.getFile())) # get java file
f_out=StringIO.StringIO() # get fake file
make_transform(f_in,f_out) #convert
f_in.close()
f_out.seek(0)
content=f_out.read() # write content in string
return content
La classe nell'esempio ha un solo metodo pubblico di nome convert che prende in input un file di tipo com.adobe.idp.Document, lo legge, e lo converte con la mia funzione python make_transform (non ci interessa il suo contenuto ai fini dell'esempio). Infine viene restituito in output una stringa (le stringhe jython vengono convertite automaticamente in java.lang.String).
Nella creazione del jar bisogna avere l'accortezza di utilizzare la stessa versione di Java con la quale è stato compilato LiveCycle (nel nostro caso LC 8.2 usa java 1.5). Per ottenere questo si deve modificare lo script che lancia Jython come da esempio sulla pagina precedente della guida.
Una volta ottenuto un jar dobbiamo scompattarlo e aggiungere il file xml e le icone.
Il file xml sarà fatto così:
<component xmlns="http://adobe.com/idp/dsc/component/document"> <!-- Unique id identifying this component --> <component-id>lupo.Form2xml</component-id> <!-- Version --> <version>1.0</version> <class-path></class-path> <!-- Start of the Service definition --> <services> <service name="Convert"> <implementation-class>lupo.Convert</implementation-class> <description>Convert a form file into flat xml</description> <small-icon>icons/jython16.gif</small-icon> <large-icon>icons/jython32.gif</large-icon> <auto-deploy service-id="Convert" /> <operations> <operation name="convert"> <input-parameter name="txt" title="form file" type="com.adobe.idp.Document"> <hint>form file</hint> </input-parameter> <output-parameter name="xml" title="xml file" type="java.lang.String"> <hint>xml file</hint> </output-parameter> <faults> <fault name="Exception" type="java.lang.Exception"/> </faults> <hint>Convert a form file into flat xml</hint> </operation> </operations> </service> </services> </component>
In questo file ho dichiarato il nome della mia classe e del mio componente.
Nella sezione class-path avrei potuto nominare alcuni jar da includere nella class path ma ho preferito aggiungere le classi direttamente nel mio jar. Sotto services vengono elencate le classi utilizzabili dal mio componente e sotto operations vengono elencati i metodi richiamabili per ogni classe. Nel mio caso ho solo una classe Convert che espone un solo metodo convert. Per ogni service è possibile specificare dove trovare le icone per raffigurare il componente su WorkBench (nel mio caso jython16.gif e jython32.gif).
Per ogni metodo esposto vengono elencati gli argomenti in input e quelli in output. Grazie alla descrizione fornita nell'xml LiveCycle esporrà una comoda interfaccia grafica per la configurazione del componente.
La documentazione completa su cosa riportare sull'xml si trova sul sito ufficiale.
Ora dobbiamo ricompattare il jar. Basta usare lo zip senza compressione (LiveCycle non funziona correttamente con i Jar compressi ! ).
Per comodità ho aggiunto anche la classe dove viene definito il tipo com.adobe.idp.Document (l'ho trovata nel developer kit di Livecycle).
Ora abbiamo il nostro componente!
Apriamo WorkBench, importiamo il nostro componente e lo attiviamo (il mio componente l'ho chiamato Form2xml):
Et voilà. Possiamo utilizzarlo all'interno dei nostri processi !
