Webservice-Schnittstellen
Webservice-Schnittstellen
Um einen Großteil der Funktionalität, die jadice server bietet, Clients unabhängig von ihrer Implementierungssprache anbieten zu können, bietet dieser zwei Webservice-Schnittstellen.
Die Kommunikation mit jadice server -- beziehungsweise dem Webservice-Frontend -- findet dabei über das HTTP-Protokoll mittels SOAP-Nachrichten bzw. REST-Aufrufen statt.
SOAP-Schnittstelle
Bei SOAP findet die Kommunikation mit Nachrichten im XML-Format statt. Die Übertragung von Dateien, die konvertiert werden sollen oder als Ergebnis an den Client zurückgesendet werden, ist als MTOM-Attachment realisiert.
Abbildung 7.1. Schematischer Aufbau der SOAP-Schnittstelle
Tipp
Zur Entwicklung und zum Debugging von SOAP-Anfragen empfehlen wir soapUI.
Aufbau einer SOAP-Nachricht
Nachdem wie in Abschnitt Konfiguration SOAP-Schnittstelle beschrieben
die Webservice-Schnittelle aktiviert wurde, kann unter
http://<url>?wsdl
(z. B. bei Konfiguration wie im
o. g. Kapitel: http://localhost:9000/jadiceServer?wsdl
) die formale
Beschreibung der Schnittstelle im
WSDL-Format heruntergeladen
werden. Damit kann in vielen Webservice-Frameworks Code generiert
werden, um den Webservice des jadice servers anzusprechen. Bei einer
SOAP-Anfrage kann jadice server auf zwei unterschiedliche Arten
angesprochen werden:
Der Workflow wird anhand eines Templates, das zuvor serverseitig abgelegt wurde, vorkonfiguriert
Der Workflow wird zur Laufzeit innerhalb der SOAP-Anfrage definiert.
Die beiden Möglichkeiten werden in den beiden folgenden Kapiteln dargestellt.
Anfrage anhand eines Templates
Es ist möglich, serverseitig eine in XML kodierte Jobbeschreibung abzulegen, sodass ein Client bei einer SOAP-Anfrage auf diese verweisen kann und der Job nicht zur Laufzeit konfiguriert werden muss.
Der Aufbau dieser Nachricht soll anhand des folgenden Beispiels erläutert werden:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.server.jadice.levigo.com/">
<soapenv:Header/>
<soapenv:Body>
<ws:run>
<ws:job templateLocation="resource:/jobtemplates/x2pdf.xml">
<ws:property name="dp.rulesetName">default</ws:property>
<ws:property name="dp.targetMimeType">application/pdf</ws:property>
<ws:property name="dp.timeout">8000</ws:property>
<ws:stream mimeType="unknown/*" uuid="123456789" nodeId="node0">
<ws:documentData>BASE64_encoded_data</ws:documentData>
</ws:stream>
</ws:job>
</ws:run>
</soapenv:Body>
</soapenv:Envelope>
Beispiel 7.1. Beispiel einer SOAP-Anfrage mit Job-Template
Neben den vom SOAP-Standard vorgegebenen Elementen für Header und Body gibt es das spezifische Element run, das die vom Webservice angebotene Methode run adressiert.
Darin wird ein Job (siehe Systemarchitektur) definiert, der über ein Template vordefiniert ist (siehe Abschnitt Definition von Job-Templates). Im Attribut templateLocation steht dabei der Ort, an dem das jeweilige Template serverseitig zu finden ist. Sind im Template Variablen definiert, so können diese mittels property-Elementen konfiguriert bzw. deren Standardwert überschrieben werden. Optional ist das Attribut messageID. Dies kann der Client frei vergeben und wird ggf. in einer Antwort des Servers übernommen.
Zu verarbeitende Datenströme werden über stream-Elemente in der
SOAP-Nachricht referenziert. Die Angabe einer eindeutigen ID (uuid) und
des MIME-Typen sind optional. Sollte der MIME-Type nicht bekannt sein,
aber dennoch angegeben werden, so ist dort unknown/*
anzugeben.
Sind in der Templatedatei mehrere StreamInputNode
s definiert, so muss
eine eindeutige Zuordnung getroffen werden, welcher Datenstrom an
welchen StreamInputNode
gesendet wird. Dies erfolgt durch das Attribut
nodeId. Dies verweist auf die ID, die dem StreamInputNode
innerhalb
des Templates (Attribut id) gegeben wurde.
Die eigentlichen Daten können entweder direkt im Tag documentData als Base64-codierter Datenstrom angegeben werden oder in einem multipart/related-Container referenziert werden, der die hier angegebene CID (contentID) besitzt, wenn das MTOM-Feature aktiviert ist.
Jobdefinition innerhalb der SOAP-Nachrichtconfig
Soll der Client keine serverseitig vordefinierte Jobkonfiguration verwenden, ist es möglich, diese in der SOAP-Anfrage einzubetten. Das Format ist hierbei das selbe, wie innerhalb eines separaten Job-Templates (vgl. Abschnitt Definition von Job-Templates). Anstelle des Root-Elements job wird hierbei die Definition als configuration-Element in die SOAP-Anfrage eingebettet.
Das folgende Beispiel soll dies veranschaulichen:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.server.jadice.levigo.com/">
<soapenv:Header/>
<soapenv:Body>
<ws:run>
<ws:job messageID="4711">
<ws:configuration type="demultipexer example">
<ws:nodes>
<ws:node class="com.levigo.jadice.server.nodes.StreamInputNode" id="input1" />
<ws:node class="com.levigo.jadice.server.nodes.StreamInputNode" id="input2" />
<ws:node class="com.levigo.jadice.server.nodes.DemultiplexerNode" id="demux" />
<ws:node class="com.levigo.jadice.server.nodes.StreamOutputNode" id="out" />
</ws:nodes>
<ws:connections>
<ws:connect from="input1" to="demux" />
<ws:connect from="input2" to="demux" />
<ws:connect from="demux" to="out" />
</ws:connections>
</ws:configuration>
<ws:stream nodeId="input1">
<ws:documentData>BASE64_encoded_data</ws:documentData>
</ws:stream>
<ws:stream nodeId="input2">
<ws:documentData>BASE64_encoded_data</ws:documentData>
</ws:stream>
</ws:job>
</ws:run>
</soapenv:Body>
</soapenv:Envelope>
Beispiel 7.2. Beispiel einer SOAP-Anfrage mit eingebetteter Job-Definition
In diesem Beispiel werden zwei StreamInputNode
s miteinander über einen
DemultiplexerNode
gekoppelt und die Eingabedaten unverändert an den
Client zurückgesendet.
Die Definition der Node
s und welchen Workflow-Graphen sie bilden, ist
innerhalb des Blocks configuration beschrieben.
Hier ist außerdem zu sehen, wie es möglich ist, bestimmte Eingabeströme
an einen StreamInputNode
zu binden: Das erste Dokument wird an den
ersten (nodeId input1), das zweite Dokument an den zweiten
StreamInputNode
(nodeId input2) gebunden.
Aufbau einer SOAP-Antwort
Der Aufbau einer Antwort, die dem Client als Antwort auf eine Anfrage geschickt wird, ist auch in der oben genannten WSDL spezifiziert.
Eine mögliche Antwort könnte so aussehen:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<js:runResponse xmlns:js="http://ws.server.jadice.levigo.com/">
<js:return>
<js:stream>
<js:documentData>BASE64_encoded_data</js:documentData>
</js:stream>
<js:status>COMPLETED</js:status>
</js:return>
</js:runResponse>
</soap:Body>
</soap:Envelope>
Beispiel 7.3. Beispiel einer SOAP-Antwort
Neben einer (eventuell leeren) Menge von Ergebnisströmen, die jeweils über eine eindeutige ID in einem multipart/related-Container referenziert werden, ist dort eine Status-Meldung vorhanden. Folgende Werte sind möglich:
Wert | Bedeutung |
---|---|
|
Job wurde durchgeführt. |
|
Job konnte nicht durchgeführt werden. |
In beiden Fällen kann das return-Element eine Menge an log-entry-Elementen enthalten, die Hinweise über den Fehlschlag der Verarbeitung oder Meldungen, die während der Verarbeitung aufgetreten sind, enthalten (vgl. Abschnitt Erstellung eines JobListeners). Die Fehlermeldung, die gezeigt wird, wenn eine nicht vorhandene Jobtemplate-Datei referenziert wird, zeigt das folgende Beispiel:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<js:runResponse xmlns:js="http://ws.server.jadice.levigo.com/">
<js:return messageID="12345">
<js:log-entry timeStamp="31.12.2009 22:33:44">
<js:level>ERROR</js:level>
<js:id>JS.WEBSERVICE-EXCEPTION</js:id>
<js:message>java.io.FileNotFoundException: Could not locate resource: does_not_exist.xml</js:message>
</js:log-entry>
<js:status>FAILED</js:status>
</js:return>
</js:runResponse>
</soap:Body>
</soap:Envelope>
Beispiel 7.4. Beispiel einer SOAP-Antwort mit Fehlermeldung
Definition von Job-Templates
Durch die Definition von Job-Templates wurde die Möglichkeit geschaffen,
dass Clients nun nicht mehr die internen Schritte des jadice server, die
für eine Konvertierung notwendig sind, kennen müssen. Diese werden für
die Webservice-Schnittstelle an zentraler Stelle vorgehalten werden.
Dadurch muss der Client nunmehr nur noch die Webservice-Methode run
sowie den Ort des auszuführenden Templates kennen (diese liegen i.d.R.
im Unterordner /server-config/
).
Im Ordner /server-config/jobtemplates
liegt die XSD-Definition für
diese Templates.
Ein Beispiel, wie solch ein Template aussehen kann, ist das im
Lieferumfang enthaltene Template x2pdf.xml
, das ähnlich dem Beispiel
aus Abschnitt Konvertierung unbekannter Eingabedaten in ein einheitliches Format (PDF) unbekannte
Eingabedaten identifiziert und in das PDF-Format umwandelt:
<job xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="jobtemplate.xsd" type="x2pdf">
<ws:properties>
<ws:property name="PROPERTY_KEY_COMMUNICATION_TIMEOUT">${communication_timeout:22000}</ws:property>
</ws:properties>
<ws:nodes>
<ws:node class="com.levigo.jadice.server.nodes.StreamInputNode" id="node0">
<ws:property name="timeout">${timeout:6000}</ws:property>
</ws:node>
<ws:node class="com.levigo.jadice.server.nodes.DynamicPipelineNode" id="node1">
<ws:property name="ruleset">${ruleset:resource:/dynamic-pipeline-rules/default.xml}</ws:property>
<ws:property name="targetMimeType">${targetMimeType:application/pdf}</ws:property>
<ws:property name="timeout">${timeout:6000}</ws:property>
</ws:node>
<ws:node class="com.levigo.jadice.server.nodes.StreamOutputNode" id="node2">
<ws:property name="timeout">${timeout:6000}</ws:property>
</ws:node>
</ws:nodes>
<ws:connections>
<ws:connect from="node0" to="node1" />
<ws:connect from="node1" to="node2" />
</ws:connections>
</ws:job>
Beispiel 7.5. Beispiel eines Job-Templates
Wie man erkennen kann, ist ein Template in drei Blöcke gegliedert:
Eigenschaften, die den kompletten Job betreffen (Timeouts etc.)
Definition von einzelnen
Node
s (Element node) und deren EigenschaftenVerkettung der
Node
s zu einem Workflow. Dieser muss einen azyklischen Graph bilden.
Sofern die einzelnen Node
s und deren Eigenschaften den Konventionen
für java beans entsprechen, können die jeweiligen Eigenschaften hier
einfach über deren Namen gesetzt werden. Außerdem ist es möglich, diese
variabel zu machen, wie im obigen Beispiel zu sehen ist. Dies geschieht
nach folgendem Muster: ${<Bezeichner>}
oder
${<Bezeichner>:<Standardwert>}
, wobei der Bezeichner zwingend mit
einem Buchstaben beginnen muss und als weitere Zeichen Buchstaben,
Ziffern, "_" (Unterstrich), "-" (Bindestrich) und "." (Punkt) haben
darf.
Durch diesen Bezeichner können diese Werte beim SOAP-Aufruf als
Eigenschaft (Element
<property name="bezeichner">wert</property>
{.xml}) gesetzt bzw.
überschrieben werden. Werden Variablen, die keinen Standardwert haben,
nicht gesetzt, so führt dies beim Aufruf zu folgendem Fehler:
com.thoughtworks.xstream.converters.ConversionException: Pattern refers to undefined variable <Bezeichner> for which there is no default
Die einzelnen Node
-Elemente müssen eine für das jeweilige Template
eindeutige ID einhalten. Durch diese werden sie im connections-Block zu
einem Workflow verkettet.
Sollen über den SOAP-Aufruf, der zu diesem Template gehört, Daten vom
Client zum Server übertragen werden, ist es notwendig, mindestens einen
StreamInputNode
zu definieren. Werden mehrere StreamInputNode
s
definiert, so müssen die einzelnen stream-Elemente in der SOAP-Anfrage
den jeweils zutreffenden Node
über das Attribut nodeId referenzieren.
Dies entfällt, wenn es genau einen StreamInputNode
gibt.
Die Datenströme, die aus StreamOutputNode
s resultieren, werden als
MTOM-Attachments in der SOAP-Antwort an den Client zurückgesendet. Dabei
ist es auch möglich, mehrere StreamOutputNode
s zu definieren. Dabei
ist die Reihenfolge, in der die StreamOutputNode
s abgefragt werden, um
ihre Datenströme an die SOAP-Antwort anzuhängen, zufällig.
Um Job-Templates in eine SOAP-Anfrage einzubetten (siehe Abschnitt Jobdefinition innerhalb der -Nachricht), muss das Root-Element job entfernt werden; der Inhalt wird stattdessen an das Element configuration in der SOAP-Anfrage eingehängt. Das XML-Attribut type wird verwendet, um den Job-Typen so im Java-Code in Beispiel 6.2. Job konfigurieren und ausführen zu deklarieren.
Generierung von Webservice-Clients
Da die Webservice-Schnittstelle durch die WSDL klar definiert ist, können frei verfügbare Webservice-Bibliotheken diese Definition verarbeiten und daraus Proxy-Klassen generieren, die die notwendigen SOAP-Anfragen kapseln und dadurch eine effiziente Entwicklung von Clientanwendungen ermöglichen. In diesem Kapitel wird dies anhand Suns Referenzimplemenentierung von JAX-WS, der Bibliothek Apache Axis2 und der Bibliothek Apache CXFgezeigt.
JAX-WS Referenzimplemenentierung
Im Lieferumfang des Java Development Kit (JDK) Version 1.7 befindet sich
das Kommandozeilentool wsimport
, mit dem Proxy-Klassen generiert
werden können. Ist der Webservice von jadice server wie in Abschnitt
??? beschrieben aktiviert worden, werden
mit folgendem Aufruf die notwenden Clientklassen erzeugt:
<jdk1.7>\bin\wsimport http://localhost:9000/jadiceServer?wsdl -keep -extension
Der Parameter -keep
bewirkt, dass nicht nur die Klassen, sondern auch
deren Quelltexte gespeichert werden. Zur weiteren Entwicklung empfehlt
es sich, mit diesen weiter zu arbeiten.
Abbildung 7.2, "Von JAX-WS generierte Klassen" zeigt
die generierten Klassen, die in die Entwicklungsumgebung eingebunden
werden können.
Abbildung 7.2, "Von JAX-WS generierte Klassen"
Einstiegspunkte für eine Clientanwendung sind die Klassen
JadiceServerJobInvoker
und JobInvocationService
, über die ein
Zugriff auf die SOAP-Schnittstelle erfolgt, und die Klasse
JobConfiguration
über die der Aufruf konfiguriert wird. Eine minimale
Implementierung kann so aussehen:
// Abstraction of the SOAP interface:
JadiceServerJobInvoker invoker = new JadiceServerJobInvoker();
JobInvocationService service = invoker.getJobInvocationServiceImplPort();
// Configuration of the job
JobConfiguration job = new JobConfiguration();
job.setTemplateLocation("resource:/jobtemplates/x2pdf.xml");
// Optional: Apply a property (e.g. a timeout)
Property timeout = new Property();
timeout.setName("timeout");
timeout.setValue("20000");
job.getProperty().add(timeout);
// Attach input data (if the template defines a StreamInputNode)
Stream inputStream = new Stream();
inputStream.setDocumentData(…);
job.getStream().add(inputStream);
// Submit the SOAP request (method is blocking!)
JobResult result = service.run(job);
// Retrieve result status
ResultStatus status = result.getStatus();
for (Log log : result.getLogEntry()) {
// Evaluate log entries...
}
// Retrieve and handle result data
for (Stream stream : result.getStream()) {
InputStream is = stream.getDocumentData().getInputStream();
…
}
Beispiel 7.6. Implementierung eines SOAP-Clients mit der JAX-WS Referenzimplemenentierung
Wenn die Security-Schnittstelle von jadice server wie in Abschnitt
Konfiguration Security-Schnittstelle beschrieben aktiviert ist, muss
der clientseitige SOAP-Request um einen Security-Header erweitert
werden. Folgendes Beispiel zeigt dies anhand der Java-WS
Referenzimplementierung. Ein SOAPHandler
erzeugt den benötigeten
Security-Header:
public class SecurityHeaderSoapHandler implements SOAPHandler<SOAPMessageContext> {
private static final String SOAP_HEADER_SECURITY_NAMESPACE //
= "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
private static final String SOAP_HEADER_USER_TOKEN_NAMESPACE //
= "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
private static final String SOAP_HEADER_PASSWORD_TYPE_NAMESPACE //
= "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
@Override
public boolean handleMessage(SOAPMessageContext context) {
final Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
final SOAPMessage message = context.getMessage();
try {
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
SOAPElement security = header.addChildElement("Security", "wsse", SOAP_HEADER_SECURITY_NAMESPACE);
SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse");
usernameToken.addAttribute(new QName("xmlns:wsu"), SOAP_HEADER_USER_TOKEN_NAMESPACE);
SOAPElement username = usernameToken.addChildElement("Username", "wsse");
username.addTextNode("myUsername");
SOAPElement password = usernameToken.addChildElement("Password", "wsse");
password.setAttribute("Type", SOAP_HEADER_PASSWORD_TYPE_NAMESPACE);
password.addTextNode("myPassword");
} catch (Exception e) {
// Causes the JAX-WS runtime to cease handler processing and generate a fault
throw new RuntimeException("Cannot apply username token", e);
}
}
return outboundProperty;
}
}
Beispiel 7.7. Implementierung eines SecuritySoapHeaderHandler
Dieser Handler wird mittels eines HandlerResolver
s dem Invoker
gesetzt. Folgender Abschnitt des Client-Codes muss hierzu angepasst
werden:
// Abstraction of the SOAP interface:
JadiceServerJobInvoker invoker = new JadiceServerJobInvoker();
invoker.setHandlerResolver(new HandlerResolver() {
@Override
public List<Handler> getHandlerChain(PortInfo portInfo) {
List<Handler> handlerChain = new ArrayList<Handler>();
SecurityHeaderSoapHandler securtiyHandler = new SecurityHeaderSoapHandler();
handlerChain.add(securtiyHandler);
return handlerChain;
}
});
JobInvocationService service = invoker.getJobInvocationServiceImplPort();
Beispiel 7.8. Implementierung eines Security Headers mit JAX-WS Referenzimplementierung
Apache Axis2
Apache Axis2 ist unter http://axis.apache.org/axis2/java/core/ verfügbar und steht unter der Apache License. Ist der Webservice von jadice server wie in Abschnitt ??? beschrieben aktiviert worden, werden mit folgendem Aufruf die notwendigen Clientklassen erzeugt:
<AXIS2_HOME>\bin\wsdl2java
-o generatedCode
-p com.levigo.jadice.server.ws.client.axis2.stub
-d jaxbri
-uri http://localhost:9000/jadiceServer?wsdl
Die Verwendung der Parameter -o
für das Ausgabeverzeichnis und -p
für den zu verwendenden Paketnamen sind optional. Der Parameter -d
bestimmt, welches Databinding für die Wandlung nach / von XML verwendet
werden soll. Standard ist das Apache Axis Data Binding (ADB). Dieses hat
in der aktuellen Fassung jedoch Probleme mit der Deserialisierung von
SOAP/MTOM-Attachments, sodass stattdessen auf die JAX-B
Referenzimplemenentierung (jaxbri
) zurückgegriffen werden sollte.
Einstiegspunkte für eine Clientanwendung sind die Klassen
JadiceServerJobInvokerStub
und Run
, über die ein Zugriff auf die
SOAP-Schnittstelle erfolgt, und die Klasse JobConfiguration
, über die
der Aufruf konfiguriert wird. Eine minimale Implementierung kann so
aussehen:
// Abstraction of the SOAP interface:
JadiceServerJobInvokerStub invoker = new JadiceServerJobInvokerStub();
// Configuration of the job
JobConfiguration job = new JobConfiguration();
job.setTemplateLocation("resource:/jobtemplates/x2pdf.xml");
// Optional: Apply a property (e.g. a timeout)
Property timeout = new Property();
timeout.setName("timeout");
timeout.setValue("20000");
job.getProperty().add(timeout);
// Attach input data (if the template defines a StreamInputNode)
Stream inputStream = new Stream();
inputStream.setDocumentData(…);
job.getStream().add(inputStream);
// Configure the SOAP request
Run run = new Run();
run.setJob(job);
// Submit the SOAP request (method is blocking!)
RunResponse response = invoker.run(run);
// Retrieve the result object
JobResult result = response.getReturn();
// Retrieve result status
ResultStatus status = result.getStatus();
for (Log log : result.getLogEntry()) {
// Evaluate log entries...
}
// Retrieve and handle result data
for (Stream stream : result.getStream()) {
InputStream is = stream.getDocumentData().getInputStream();
…
}
Beispiel 7.9. Implementierung eines SOAP-Clients mit Apache Axis2
Wenn die Security-Schnittstelle von jadice server wie in Abschnitt Konfiguration Security-Schnittstelle beschrieben aktiviert ist, muss der clientseitige SOAP-Request um einen Security-Header erweitert werden. Folgendes Beispiel zeigt dies für Apache Axis2.
private static final String SOAP_HEADER_SECURITY_NAMESPACE = //
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
private static final String SOAP_HEADER_USER_TOKEN_NAMESPACE = //
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
private static final String SOAP_HEADER_PASSWORD_TYPE_NAMESPACE = //
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
private static OMElement createSecurityHeader() {
OMFactory omFactory = OMAbstractFactory.getOMFactory();
OMElement omSecurityElement = omFactory.createOMElement(new QName(SOAP_HEADER_SECURITY_NAMESPACE, "Security", "wsse"));
omSecurityElement.addAttribute("mustUnderstand", "1", null);
OMElement omUsertoken = omFactory.createOMElement(new QName(SOAP_HEADER_SECURITY_NAMESPACE, "UsernameToken", "wsse"));
omUsertoken.declareNamespace(SOAP_HEADER_USER_TOKEN_NAMESPACE, "wsu");
OMElement omUserName = omFactory.createOMElement(new QName(SOAP_HEADER_SECURITY_NAMESPACE, "Username", "wsse"));
omUserName.setText("myUsername");
OMElement omPassword = omFactory.createOMElement(new QName(SOAP_HEADER_SECURITY_NAMESPACE, "Password", "wsse"));
omPassword.addAttribute("Type", SOAP_HEADER_PASSWORD_TYPE_NAMESPACE, null);
omPassword.setText("myPassword");
omUsertoken.addChild(omUserName);
omUsertoken.addChild(omPassword);
omSecurityElement.addChild(omUsertoken);
return omSecurityElement;
}
Beispiel 7.10. Erzeugung eines Security Headers
Der erzeugte Header wird mit den folgenden Anpassungen des Webservice Client-Codes gesetzt und für jeden Webservice-Request verwendet:
Apache CXF
Apache CXF ist unter http://cxf.apache.org/ verfügbar und steht unter der Apache License. Ist der Webservice von jadice server wie in Abschnitt Konfiguration SOAP-Schnittstelle beschrieben aktiviert worden, werden mit folgendem Aufruf die notwendigen Clientklassen erzeugt:
<CXF_HOME>\bin\wsdl2java
-d generatedCode
-p com.levigo.jadice.server.ws.client.cxf
http://localhost:9000/jadiceServer?wsdl
Die Verwendung der Parameter -d
für das Ausgabeverzeichnis und -p
für den zu verwendenden Paketnamen sind optional.
Einstiegspunkte für eine Clientanwendung sind die Klassen
JadiceServerJobInvoker
und Run
, über die ein Zugriff auf die
SOAP-Schnittstelle erfolgt, und die Klasse JobConfiguration
, über die
der Aufruf konfiguriert wird. Eine minimale Implementierung kann so
aussehen:
// Abstraction of the SOAP interface:
JadiceServerJobInvoker invoker = new JadiceServerJobInvoker();
JobInvocationService service = invoker.getJobInvocationServiceImplPort();
// Configuration of the job
JobConfiguration job = new JobConfiguration();
job.setTemplateLocation("resource:/jobtemplates/x2pdf.xml");
// Optional: Apply a property (e.g. a timeout)
Property timeout = new Property();
timeout.setName("timeout");
timeout.setValue("20000");
job.getProperty().add(timeout);
// Attach input data (if the template defines a StreamInputNode)
Stream inputStream = new Stream();
inputStream.setDocumentData(…);
job.getStream().add(inputStream);
Run run = new Run();
run.setJob(job);
// Submit the SOAP request (method is blocking!)
RunResponse response = service.run(run);
// Retrieve the result object
JobResult result = response.getReturn();
// Retrieve result status
ResultStatus status = result.getStatus();
for (Log log : result.getLogEntry()) {
// Evaluate log entries...
}
// Retrieve and handle result data
for (Stream stream : result.getStream()) {
InputStream is = stream.getDocumentData().getInputStream();
…
Beispiel 7.12. Implementierung eines SOAP-Clients mit Apache CXF
Wenn die Security-Schnittstelle von jadice server wie in Abschnitt Konfiguration Security-Schnittstelle beschrieben aktiviert ist, muss der clientseitige SOAP-Request um einen Security-Header erweitert werden. Hierbei kann die gleiche Syntax wie bei der JAX-WS Referenzimplementierung verwendet werden, siehe JAX-WS Referenzimplemenentierung
REST-Schnittstelle
Bei der REST-Schnittstelle findet die Kommunikation zwischen der Client-Anwendung und jadice server über normale HTTP-Aufrufe und die Methoden GET, POST und DELETE statt. Die Nachrichten werden dabei im XML- oder JSON-Format ausgetauscht.
Tipp
Zur Entwicklung und zum Debugging von SOAP-Anfragen empfehlen wir soapUI und Postman.
Aufbau der REST-Schnittstelle
Um die REST-Schnittstelle nutzen zu können, muss sie zunächst wie im
Abschnitt Konfiguration REST-Schnittstelle beschrieben aktiviert werden
und ist unter http://<hostname>:9001/jadiceServer/
erreichbar. Zur besseren
Lesbarkeit wird der Hostname der URL (http://<hostname>:9001
) in den
nachfolgenden Abschnitten weggelassen.
Die folgenden Abschnitte geben eine kurze Übersicht über die zur Verfügung stehenden Methoden. Im Abschnitt Ablauf der Verarbeitung über REST wird gezeigt, wie daraus der Ablauf einer Konvertierung zusammengesetzt wird.
Integrierte Dokumentation
Die REST-Schnittstelle verfügt über eine integrierte Dokumentation im
Open API-Format (auch bekannt als
"swagger". Unter der URL
/jadiceServer/swagger.json
kann sie im
JSON-Format bzw. unter
/jadiceServer/swagger.yaml
im YAML-Format
abgerufen werden.
Unter der URL
/jadiceServer/api-docs?url=/jadiceServer/swagger.json
finden Sie eine interaktive Dokumentation der REST-Schnittstelle. Dort
können Sie direkt aus dem Browser heraus die verfügbaren Methoden
aufrufen:
Abbildung 7.4. Darstellung von Swagger UI
File-Service
Um Dateien hochzuladen, damit jadice server sie anschließend verarbeiten kann, oder um Ergebnisdateien von jadice server abzurufen, ist der File-Service zuständig. Dieser ist unter der URL /jadiceServer/files erreichbar.
Folgende Methoden sind damit möglich:
POST auf /jadiceServer/files
Hochladen einer neuen Datei. Der Rückgabewert ist die ID, unter der
diese Datei in den nachfolgenden Schritten abrufbar ist (im
Folgenden {fileId}
).
GET auf /jadiceServer/files/{fileId}
Lädt eine Datei herunter. Ihr Dateiname und MIME-Type werden in die üblichen Header-Felder `Content-Disposition` und `Content-Type` übertragen.
DELETE auf /jadiceServer/files/{fileId}
Löscht eine Datei vom Server.
Template-Service
Über den Template-Service kann erfragt werden, welche Job-Templates zur
Verfügung stehen. Sie folgen der im Abschnitt Definition von
Job-Templates
dargestellten Syntax und liegen i. d. R. im Unterordner
/server-config/jobtemplates
.
Folgende Methoden stellt der Template-Service zur Verfügung:
GET auf /templates
Lädt die Liste aller verfügbaren Job-Templates herunter. Die Liste
stellt die IDs dar, unter denen die einzelnen Job-Templates
referenziert werden können (im Folgenden {templateId}
).
GET auf /templates/{templateId}
Lädt ein bestimmtes Job-Template herunter. Da diese im XML-Format definiert werden, ist hier keine Rückgabe im JSON-Format möglich.
Job-Service
Der Job-Service wird dafür genutzt, um Konvertierungsanfragen an jadice server abzusenden und um sich über deren Fortschritt zu informieren. Ferner bündelt er die Informationen über die Ergebnis-Dokumente und Log-Meldungen, die während der Verarbeitung erzeugt werden.
Der Jobs-Service stellt folgende Methoden zur Verfügung:
POST auf /jadiceServer/jobs
Erstellen eines neuen Jobs, dessen Verarbeitung sofort angestoßen
wird. Der Rückgabewert ist die ID, unter der dieser Job nachverfolgt
werden kann (im Folgenden {jobID}
).
GET auf /jadiceServer/jobs/{jobId}
Ruft den aktuellen Job-Zustand ab.
DELETE auf /jadiceServer/jobs/{jobId}
Entfernt die Informationen zu diesem Job vom Server. Falls dieser sich noch in der Verarbeitung befindet, wird er automatisch abgebrochen.
Ablauf der Verarbeitung über REST
Nachdem im vorherigen Abschnitt die einzelnen REST-Services kurz vorgestellt wurden, soll hier nun examplarisch der Ablauf einer Konvertierung dargestellt werden. Abbildung 7.5. Sequenzdiagramm der Verarbeitung über REST zeigt diesen Ablauf in der Übersicht als UML-Sequenzdiagramm.
Abbildung 7.5. Sequenzdiagramm der Verarbeitung über REST
In den folgenden Schritten folgt eine textuelle Beschreibung, sowie je ein exemplarischer Request der Clientanwendung und die zugehörige Antwort von jadice server. In den Beispielen wird die Kommunikation im JSON-Format bevorzugt.
- In den Beispielen werden die HTTP-Header-Felder, die nicht zum Verständnis beitragen, weggelassen.
- Der HTTP-Header und der Body werden durch eine Leerzeile getrennt dargestellt.
- Zur besseren Lesbarkeit werden Umbrüche und Einrückungen in die JSON-Objekte eingefügt.
- Bevor ein Job an jadice server abgeschickt werden kann, muss
zunächst ausgewählt werden, welches Job-Template verwendet werden
soll. Um in Erfahrung zu bringen, welche Job-Templates zur Verfügung
stehen, wird der Template-Service mit dem GET-Request auf
/jadiceServer/templates/
ausgerufen. Das Ergebnis ist die Liste aller zur Verfügung stehenden Templates (bzw. deren IDs) im JSON- oder XML-Format. Wenn Sie auch den Inhalt des Templates kennen möchten, so kann das über den GET-Request auf/jadiceServer/templates/{templateId}
heruntergeladen werden.
Request | Response |
---|---|
GET /jadiceServer/templates HTTP/1.1
Host: <hostname>:9001
Accept: application/json
|
HTTP/1.1 200 OK
Content-Type: application/json
|
Beispiel 7.13. Abfrage der verfügbaren Job-Templates
Request | Response |
---|---|
GET /jadiceServer/templates/x2pdf.xml HTTP/1.1
Host: <hostname>:9001
Accept: application/xml
|
HTTP/1.1 200 OK
Content-Type: application/xml
|
Beispiel 7.14. Abfrage eines einzelnen Job-Templates
- Die zu verarbeitenden Dateien werden auf den Server hochgeladen: Für
jede Datei, die konvertiert werden soll, erfolgt ein Upload in Form
eines POST-Request mit dem Content-Type
multipart/form-data
auf/jadiceServer/files
. Als Ergebnis gibt der Server die File-Id zurück, unter der diese Datei anschließend referenziert werden kann.
Request | Response |
---|---|
POST /jadiceServer/files HTTP/1.1
Host: <hostname>:9001
Accept: text/plain
Content-Type: multipart/form-data; boundary=----Boundary-12345
|
HTTP/1.1 201 Created
Content-Type: text/plain
|
Beispiel 7.15. Hochladen einer Datei
- Anschließend kann mit diesen Angaben ein Job formuliert werden, der
an den JobService in Form eines POST-Requests auf
/jadiceServer/jobs
geschickt wird. Der Service gibt die Job-Id zurück, unter der dieser Job nachverfolgt werden kann.
Request | Response |
---|---|
POST /jadiceServer/jobs HTTP/1.1
Host: <hostname>:9001
Accept: text/plain
Content-Type: application/json
|
HTTP/1.1 201 Created
Content-Type: text/plain
|
Beispiel 7.16. Starten eines Jobs
- Der aktuelle Zustand des Jobs wird über den GET-Request auf
/jadiceServer/jobs/{jobId}
ermittelt. Es muss gewartet werden, bis dieser im ZustandFINISHED
ist. Falls der Job nicht erfolgreich verarbeitet werden kann, endet er im ZustandFAILED
, sieheJob.State
.
Request | Response |
---|---|
GET /jadiceServer/jobs/job-uuid-0001 HTTP/1.1
Host: <hostname>:9001
Accept: application/json
|
HTTP/1.1 200 OK
Content-Type: application/json
|
Beispiel 7.17. Aktuellen Status des Jobs abfragen
- Die erzeugten Dateien, deren File-Ids in der Antwort des Job-Service
genannt werden, können einzeln über je einen GET-Request auf
/jadiceServer/files/{fileId}
heruntergeladen werden.
Request | Response |
---|---|
GET /jadiceServer/files/stream-uuid-0002 HTTP/1.1
Host: <hostname>:9001
Accept: application/octet-stream
|
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename=my-file.pdf
Content-Description: my-file.pdf
|
Beispiel 7.18. Herunterladen einer Datei
Abschließend müssen alle Resourcen, die nicht mehr für eine weitere Konvertierung benötigt werden, gelöscht werden:
- Die hochgeladenen und die erzeugten Dateien werden jeweils per
DELETE-Request auf
/jadiceServer/files/{fileId}
gelöscht.
- Die hochgeladenen und die erzeugten Dateien werden jeweils per
DELETE-Request auf
Request | Response |
---|---|
DELETE /jadiceServer/files/stream-uuid-0001 HTTP/1.1
Host: <hostname>:9001
Accept: application/json
|
HTTP/1.1 204 No Content
|
Beispiel 7.19. Löschen einer Datei
- Der Job wird per DELETE-Request auf
/jadiceServer/jobs/{jobId}
gelöscht.
Request | Response |
---|---|
DELETE /jadiceServer/jobs/job-uuid-0001 HTTP/1.1
Host: <hostname>:9001
Accept: application/json
|
HTTP/1.1 204 No Content
|
Beispiel 7.20. Löschen des Jobs
Das Löschen von nicht mehr benötigten Dateien und Jobs ist essenziell. Diese Ressourcen werden nicht automatisch von jadice server gelöscht und belegen u. U. unnötigen Platz auf der Festplatte und im Arbeitsspeicher.
Generierung von REST-Clients
Mit Hilfe der Open API-Spezifikation der REST-Schnittstelle und dem
Kommandozeilenwerkzeug swagger-codegen ist es möglich, eine
Client-Bibliothek zu generieren, die in der gewünschten
Implementierungssprache genutzt werden kann. Laden Sie dazu zunächst die
Bibliothek swagger-codegen-cli-<version>.jar
von
http://swagger.io/swagger-codegen/ herunter.
Für welche Sprachen swagger-codegen Client-Bibliotheken erzeugen kann,
verrät der Aufruf von java -jar swagger-codegen-cli-<version>.jar
:
Available languages: [android, aspnet5, async-scala, cwiki, csharp, cpprest, dart, flash, python-flask,
go, groovy, java, jaxrs, jaxrs-cxf, jaxrs-resteasy, jaxrs-spec, inflector, javascript, javascript-closure-angular,
jmeter, nancyfx, nodejs-server, objc, perl, php, python, qt5cpp, ruby, scala, scalatra, silex-PHP, sinatra,
rails5, slim, spring, dynamic-html, html, html2, swagger, swagger-yaml, swift, tizen, typescript-angular2,
typescript-angular, typescript-node, typescript-fetch, akka-scala, CsharpDotNet2, clojure, haskell, lumen, go-server]
Ein einfacher Java-Client wird mit diesem Kommandozeilenaufruf erzeugt:
java -jar swagger-codegen-cli-<version>.jar generate
--lang java
--input-spec http://<hostname>:9001/jadiceServer/swagger.json
swagger-codegen bietet diverse Optionen, um die generierte Client-Bibliothek anzupassen. Im folgenden Beispiel werden der Ausgabeordner und die Namespaces angepasst:
java -jar swagger-codegen-cli-2.2.1.jar generate
--lang java
--input-spec http://<hostname>:9001/jadiceServer/swagger.json
--output generated-client
--invoker-package com.acme.invoker --model-package com.acme.model --api-package com.acme.api
Die folgende Abbildung zeigt die Klassen, die als Ergebnis generiert werden.
Abbildung 7.6. Von swagger-codegen erzeugtes Java-Projekt