WebserviceClient mit InvocationProxy

Generierung eines Standard Webserviceclients mit JAXWS (WS-Stub Generierung):

"%java_home%\bin\wsimport" -keep http://<Pfad zur WSDL-Beschreibung des XYZ Service>

In den folgenden Beispielklassen müssen die generierten Klassen dann noch statt der XYZ Klassen eingesetzt werden.

Die eigentliche Klasse des "WebserviceClientWithInvocationProxy":

Die Idee hinter dieser Klasse ist es einen XYZService zu instantiieren, der aber dann nicht direkt verwendet werden soll, sondern in eine Instanz von "java.lang.reflect.Proxy" eingepackt wird. Dieser Proxy erhält zusätzlich einen "SoapHeaderInvocationHandler" (siehe unten) welcher vor und nach jedem Aufruf der Methoden des eigentlichen Service aufgerufen wird und das Handling von SOAP-Headern oder andere Dinge übernimmt. Von diesem indirekten Aufruf bekommt der Nutzer des Proxy nichts mit, dies ist für ihn somit transparent. Zusätzlich wird die erzeugte Service-Instanz hier noch für weitere Verwendung gepuffert, da das Erzeugen dieser manchmal viel Zeit in Anspruch nehmen kann.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package de.soderer;

import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;

import de.soderer.XYZ;
import de.soderer.XYZService;
import com.sun.xml.ws.developer.WSBindingProvider;

public class WebserviceClientWithInvocationProxy {
  private static final String SERVICE_NAMESPACE = "urn:xyzservice.soderer.de";
  private static String SERVICE_ENDPOINT_URL_CONTEXT = "services/XYZService";
  
  private String serviceServerMachine_Hostname = null;
  private int serviceServerMachine_Port = 0;
  private XYZ service = null;
  private SoapHeaderInvocationHandler invocationHandler;
  
  public WebserviceClientWithInvocationProxy(String serverMachine_Hostname, int serverMachine_Port) {
    this.serviceServerMachine_Hostname = serverMachine_Hostname;
    this.serviceServerMachine_Port = serverMachine_Port;
  }

  public XYZ getService() throws MalformedURLException {
    if (service == null) {
      XYZServiceservice = new XYZService(
        new URL("http", serviceServerMachine_Hostname, serviceServerMachine_Port, SERVICE_ENDPOINT_URL_CONTEXT),
        new QName(SERVICE_NAMESPACE, "XYZService"));
      XYZ maskedService = service.getXYZPort();

      if (maskedService == null)
        throw new RuntimeException("Init of Service FAILED");
      
      invocationHandler = new SoapHeaderInvocationHandler((WSBindingProvider)maskedService);
      
      service = (XYZ)Proxy.newProxyInstance(
        XYZ.class.getClassLoader(),
        new Class[] { XYZ.class },
        invocationHandler);
    }

    if (service == null)
      throw new RuntimeException("Init of Service FAILED");

    return service;
  }
  
  public int getLastExecutionDurationInMillis() {
    return invocationHandler.getLastExecutionDurationInMillis();
  }
}

Die Hilfs-Klasse "SoapHeaderInvocationHandler":

Hier passiert die eigentliche Magie. Denn jeder Aufruf einer Methode des eingepackten Service-Objects wird durch die Methode "invoke" durchgeschleust. Vor und nach dem Aufruf der Service-Methoden kann also beliebiger Code ausgeführt werden. In diesem Beispiel wird ein AuthenticationToken als SOAP-Header übertragen und ein zurückgegebenes Token dann für den nächsten Aufruf zwischengespeichert. Ebenso kann zu Performance-Messzwecken die Zeitdauer der Methodenaufrufe vom Client aus gemessen werden, wie dies hier in "lastServerDurationInMillis" geschieht.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package de.soderer;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import com.sun.xml.bind.api.JAXBRIContext;
import com.sun.xml.ws.api.message.Header;
import com.sun.xml.ws.api.message.Headers;
import com.sun.xml.ws.developer.WSBindingProvider;

public class SoapHeaderInvocationHandler implements InvocationHandler {
  private WSBindingProvider service;
  private String authentificationToken = null;
  private int lastServerDurationInMillis = -1;
  
  public SoapHeaderInvocationHandler(WSBindingProvider service) {
    this.service = service;
  }
  
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    boolean errorOccurred = false;
    try {
      lastServerDurationInMillis = -1;
      
      // Add the current AuthentificationToken or create a new one and add supplemental SOAP-Headers
      ((WSBindingProvider)service).setOutboundHeaders(createOutboundHeaders(authentificationToken));

      Date executionStart = new Date();
      Object result = method.invoke(service, args);
      Date executionEnd = new Date();

      lastServerDurationInMillis = (int) (executionEnd.getTime() - executionStart.getTime());

      return result;
    }
    catch(Exception ex) {
      errorOccurred = true;
      throw ex;
    }
    finally {
      // Retrieve the new AuthenticationToken and store it in the client
      readInboundAuthenticationToken((WSBindingProvider)service, !errorOccurred);
    }
  }
  
  /**
   * Add the current AuthentificationToken as SOAP-Header
   * @param authToken
   * @return
   */

  private static List<Header> createOutboundHeaders(String authToken) {
    try {
      List<Header> headerList = new ArrayList<Header>(3);
      
      de.soderer.ObjectFactory objFactory = new de.soderer.ObjectFactory();
      de.soderer.Authentication auth = objFactory.createAuthentication();
      auth.setAuthenticationToken(authToken);
      headerList.add(
        Headers.create(
          (JAXBRIContext)JAXBContext.newInstance(de.soderer.Authentication.class),
          objFactory.createOTAuthentication(auth)));

      return headerList;
    }
    catch (JAXBException e) {
      throw new RuntimeException("Creation of SOAP-Headers FAILED: " + e.getMessage());
    }
  }
  
  /**
   * Retrieve the new AuthenticationToken and store it in the client
   * @param service
   */

  private void readInboundAuthenticationToken(WSBindingProvider service, boolean throwOnMissingToken) {
    String newAuthToken;
    try {
      newAuthToken = null;

      List<Header> inboundHeaders = service.getInboundHeaders();
      for (Header header : inboundHeaders) {
        if (header.getLocalPart().equals("Authentication")) {
          XMLStreamReader streamReader = inboundHeaders.get(0).readHeader();
          try {
            StringBuffer readContent = new StringBuffer();
            while (streamReader.hasNext()) {
              streamReader.next();
              if (streamReader.getEventType() == XMLStreamReader.START_ELEMENT) {
                if (streamReader.getLocalName().equals("AuthenticationToken"))
                  readContent = new StringBuffer();
              }
              else if (streamReader.getEventType() == XMLStreamReader.END_ELEMENT) {
                if (streamReader.getLocalName().equals("AuthenticationToken")) {
                  newAuthToken = readContent.toString().trim();
                  break;
                }
              }
              else if (streamReader.getEventType() == XMLStreamReader.CHARACTERS) {
                readContent.append(streamReader.getTextCharacters());
              }
            }
          }
          finally {
            if (streamReader != null) {
              streamReader.close();
              streamReader = null;
            }
          }
        }
      }
    }
    catch (XMLStreamException e) {
      throw new RuntimeException("Readout of new Authenticationtoken in serverresponse FAILED: " + e.getMessage());
    }

    if (newAuthToken != null)
      authentificationToken = newAuthToken;
    else if (throwOnMissingToken)
      throw new RuntimeException("New Authenticationtoken in serverresponse is missing");
    else
      System.out.println("Authenticationtoken was empty, but other error is priorized");
  }

  public int getLastExecutionDurationInMillis() {
    return lastServerDurationInMillis;
  }
}

Beispiel Service Aufruf:

WebserviceClientWithSOAPHandler client = new WebserviceClientWithSOAPHandler("myhostname", 8080);
client.getService().authenticate(username, userpassword);
client.getService().xyzMethode(4711);
client.getService().xyzMethode(0815);