View Javadoc

1   /*
2    * Copyright (C) 2007 ETH Zurich
3    *
4    * This file is part of Fosstrak (www.fosstrak.org).
5    *
6    * Fosstrak is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License version 2.1, as published by the Free Software Foundation.
9    *
10   * Fosstrak is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with Fosstrak; if not, write to the Free
17   * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18   * Boston, MA  02110-1301  USA
19   */
20  
21  package org.fosstrak.epcis.queryclient;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.net.MalformedURLException;
29  import java.net.URL;
30  import java.rmi.RemoteException;
31  import java.security.KeyStore;
32  import java.security.SecureRandom;
33  import java.security.cert.CertificateException;
34  import java.security.cert.X509Certificate;
35  import java.util.Arrays;
36  import java.util.List;
37  import java.util.Properties;
38  
39  import javax.net.ssl.KeyManagerFactory;
40  import javax.net.ssl.TrustManager;
41  import javax.net.ssl.X509TrustManager;
42  import javax.xml.bind.JAXBContext;
43  import javax.xml.bind.JAXBElement;
44  import javax.xml.bind.JAXBException;
45  import javax.xml.bind.Unmarshaller;
46  import javax.xml.namespace.QName;
47  import javax.xml.ws.Service;
48  import javax.xml.ws.soap.SOAPBinding;
49  
50  import org.apache.cxf.Bus;
51  import org.apache.cxf.bus.CXFBusFactory;
52  import org.apache.cxf.configuration.jsse.TLSClientParameters;
53  import org.apache.cxf.configuration.security.AuthorizationPolicy;
54  import org.apache.cxf.endpoint.Client;
55  import org.apache.cxf.frontend.ClientProxy;
56  import org.apache.cxf.transport.http.ClientOnlyHTTPTransportFactory;
57  import org.apache.cxf.transport.http.HTTPConduit;
58  import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
59  import org.fosstrak.epcis.model.EmptyParms;
60  import org.fosstrak.epcis.model.GetSubscriptionIDs;
61  import org.fosstrak.epcis.model.Poll;
62  import org.fosstrak.epcis.model.QueryResults;
63  import org.fosstrak.epcis.model.Subscribe;
64  import org.fosstrak.epcis.model.Unsubscribe;
65  import org.fosstrak.epcis.soap.DuplicateSubscriptionExceptionResponse;
66  import org.fosstrak.epcis.soap.EPCISServicePortType;
67  import org.fosstrak.epcis.soap.ImplementationExceptionResponse;
68  import org.fosstrak.epcis.soap.InvalidURIExceptionResponse;
69  import org.fosstrak.epcis.soap.NoSuchNameExceptionResponse;
70  import org.fosstrak.epcis.soap.NoSuchSubscriptionExceptionResponse;
71  import org.fosstrak.epcis.soap.QueryParameterExceptionResponse;
72  import org.fosstrak.epcis.soap.QueryTooComplexExceptionResponse;
73  import org.fosstrak.epcis.soap.QueryTooLargeExceptionResponse;
74  import org.fosstrak.epcis.soap.SecurityExceptionResponse;
75  import org.fosstrak.epcis.soap.SubscribeNotPermittedExceptionResponse;
76  import org.fosstrak.epcis.soap.SubscriptionControlsExceptionResponse;
77  import org.fosstrak.epcis.soap.ValidationExceptionResponse;
78  import org.fosstrak.epcis.utils.AuthenticationType;
79  
80  /**
81   * This query client makes calls against the EPCIS query control interface and
82   * also provides some convenience methods for polling and subscribing queries
83   * given in XML form.
84   * 
85   * @author Marco Steybe
86   */
87  public class QueryControlClient implements QueryControlInterface, X509TrustManager {
88  
89      private static final String PROPERTY_FILE = "/queryclient.properties";
90      private static final String PROP_QUERY_URL = "default.url";
91      private static final String DEFAULT_QUERY_URL = "http://demo.fosstrak.org/epcis/query";
92  
93      private static final QName SERVICE = new QName("urn:epcglobal:epcis:wsdl:1", "EPCglobalEPCISService");
94      private static final QName PORT = new QName("urn:epcglobal:epcis:wsdl:1", "EPCglobalEPCISServicePort");
95  
96      private String queryUrl;
97  
98      /**
99       * The locator for the service.
100      */
101     private EPCISServicePortType servicePort;
102 
103     /**
104      * Whether or not this service is configured and ready to use.
105      */
106     private boolean serviceConfigured = false;
107 
108     /**
109      * Constructs a new QueryControlClient using a default URL and no
110      * authentication. You can also configure the service through
111      * {@link #configureService(URL, Object[])} prior to calling any
112      * QueryControlInterface service method.
113      */
114     public QueryControlClient() {
115         this(null, null);
116     }
117 
118     /**
119      * Constructs a new QueryControlClient using the given URL and no
120      * authentication. You can also configure the service through
121      * {@link #configureService(URL, Object[])} prior to calling any
122      * QueryControlInterface service method.
123      */
124     public QueryControlClient(String url) {
125         this(url, null);
126     }
127 
128     /**
129      * Constructs a new QueryControlClient using the given URL and
130      * authentication options. You can also configure the service through
131      * {@link #configureService(URL, Object[])} prior to calling any
132      * QueryControlInterface service method.
133      * 
134      * @param url
135      *            the URL of the repository, or <code>null</code> if a default
136      *            value should be loaded.
137      * @param authenticationOptions
138      *            The authentication options:
139      *            <p>
140      *            <table border="1">
141      *            <tr>
142      *            <td><code>authenticationOptions[0]</code></td>
143      *            <td><code>[1]</code></td>
144      *            <td><code>[2]</code></td>
145      *            </tr>
146      *            <tr>
147      *            <td><code>AuthenticationType.BASIC</code></td>
148      *            <td>username</td>
149      *            <td>password</td>
150      *            </tr>
151      *            <tr>
152      *            <td><code>AuthenticationType.HTTPS_WITH_CLIENT_CERT</code></td>
153      *            <td>keystore file</td>
154      *            <td>password</td>
155      *            </tr>
156      *            </table>
157      */
158     public QueryControlClient(String url, Object[] authenticationOptions) {
159         if (url != null) {
160             this.queryUrl = url;
161         } else {
162             Properties props = loadProperties();
163             this.queryUrl = props.getProperty(PROP_QUERY_URL, DEFAULT_QUERY_URL);
164             try {
165                 new URL(queryUrl);
166             } catch (MalformedURLException e) {
167                 queryUrl = DEFAULT_QUERY_URL;
168             }
169         }
170         try {
171             configureService(new URL(queryUrl), authenticationOptions);
172         } catch (Exception e) {
173             throw new RuntimeException("unable to configure QueryControlClient: " + e.getMessage(), e);
174         }
175     }
176 
177     /**
178      * @return The query client properties.
179      */
180     private Properties loadProperties() {
181         Properties props = new Properties();
182         InputStream is = getClass().getResourceAsStream(PROPERTY_FILE);
183         if (is != null) {
184             try {
185                 props.load(is);
186                 is.close();
187             } catch (IOException e) {
188                 System.out.println("Unable to load queryclient properties from "
189                         + QueryControlClient.class.getResource(PROPERTY_FILE).toString() + ". Using defaults.");
190             }
191         } else {
192             System.out.println("Unable to load queryclient properties from " + PROPERTY_FILE + ". Using defaults.");
193         }
194         return props;
195     }
196 
197     /**
198      * @return whether or not this service is configured and ready to use.
199      */
200     public boolean isServiceConfigured() {
201         return serviceConfigured;
202     }
203 
204     public String getQueryUrl() {
205         return queryUrl;
206     }
207 
208     private boolean isEmpty(String s) {
209         return s == null || "".equals(s);
210     }
211 
212     /**
213      * Configures the service to communicate with the given endpoint address
214      * using the desired authentication method.
215      * 
216      * @param endpointAddress
217      *            The endpoint address this client will communicate to.
218      * @param authenticationOptions
219      *            The authentication options:
220      *            <p>
221      *            <table border="1">
222      *            <tr>
223      *            <td><code>authenticationOptions[0]</code></td>
224      *            <td><code>[1]</code></td>
225      *            <td><code>[2]</code></td>
226      *            </tr>
227      *            <tr>
228      *            <td><code>AuthenticationType.BASIC</code></td>
229      *            <td>username</td>
230      *            <td>password</td>
231      *            </tr>
232      *            <tr>
233      *            <td><code>AuthenticationType.HTTPS_WITH_CLIENT_CERT</code></td>
234      *            <td>keystore file</td>
235      *            <td>password</td>
236      *            </tr>
237      *            </table>
238      * @throws Exception
239      */
240     public void configureService(URL endpointAddress, Object[] authenticationOptions) throws Exception {
241         // logger.debug("Configuring service to communicate with endpoint: " +
242         // endpointAddress);
243         serviceConfigured = false;
244 
245         // setup the CXF bus
246         setUpBus();
247 
248         // instantiates a client proxy object from the EPCISServicePortType
249         // interface using the JAX-WS API
250         Service service = Service.create(SERVICE);
251         service.addPort(PORT, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress.toString());
252         servicePort = service.getPort(PORT, EPCISServicePortType.class);
253 
254         // turn off chunked transfer encoding
255         Client client = ClientProxy.getClient(servicePort);
256         HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
257         HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
258         httpClientPolicy.setAllowChunking(false);
259         httpConduit.setClient(httpClientPolicy);
260 
261         // set up any authentication
262         if (authenticationOptions != null) {
263             if (AuthenticationType.BASIC.equals(authenticationOptions[0])) {
264                 // logger.debug("Authenticating via Basic as: " +
265                 // authenticationOptions[1]);
266 
267                 String username = (String) authenticationOptions[1];
268                 String password = (String) authenticationOptions[2];
269 
270                 if (isEmpty(username) || isEmpty(password)) {
271                     throw new Exception("Authentication method " + authenticationOptions[0]
272                             + " requires a valid user name and password");
273                 }
274 
275                 AuthorizationPolicy ap = httpConduit.getAuthorization();
276                 ap.setUserName(username);
277                 ap.setPassword(password);
278             } else if (AuthenticationType.HTTPS_WITH_CLIENT_CERT.equals(authenticationOptions[0])) {
279                 // logger.debug("Authenticating with certificate in file: " +
280                 // authenticationOptions[1]);
281 
282                 if (!"HTTPS".equalsIgnoreCase(endpointAddress.getProtocol())) {
283                     throw new Exception("Authentication method " + authenticationOptions[0]
284                             + " requires the use of HTTPS");
285                 }
286 
287                 String keyStoreFile = (String) authenticationOptions[1];
288                 String password = (String) authenticationOptions[2];
289 
290                 if (isEmpty(keyStoreFile) || isEmpty(password)) {
291                     throw new Exception("Authentication method " + authenticationOptions[0]
292                             + " requires a valid keystore (PKCS12 or JKS) and password");
293                 }
294 
295                 KeyStore keyStore = KeyStore.getInstance(keyStoreFile.endsWith(".p12") ? "PKCS12" : "JKS");
296                 keyStore.load(new FileInputStream(new File(keyStoreFile)), password.toCharArray());
297                 KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
298                 keyManagerFactory.init(keyStore, password.toCharArray());
299 
300                 TLSClientParameters tlscp = new TLSClientParameters();
301                 tlscp.setKeyManagers(keyManagerFactory.getKeyManagers());
302                 tlscp.setSecureRandom(new SecureRandom());
303                 tlscp.setDisableCNCheck(true);
304                 tlscp.setTrustManagers(new TrustManager[] { this });
305 
306                 httpConduit.setTlsClientParameters(tlscp);
307             }
308         }
309 
310         /*
311          * For instantiating a client proxy object using reflection (CXF simple
312          * frontend), use the ClientProxyFactoryBean and provide it with the
313          * service interface. The setter methods on the factory object can be
314          * used to configure the client proxy.
315          */
316         // ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
317         // factory.setServiceClass(EPCISServicePortType.class);
318         // factory.setAddress(endpointAddress);
319         // factory.setBindingId(SOAPBinding.SOAP11HTTP_BINDING);
320         // servicePort = (EPCISServicePortType) factory.create();
321         /*
322          * For instantiating a client proxy object using the CXF-generated
323          * service implementation, uncomment the following lines. This will
324          * create the proxy from the WSDL file, using the endpointAddress from
325          * the <wsdlsoap:address> element inside the WSDL document.
326          */
327         // EPCglobalEPCISService s = new EPCglobalEPCISService(wsdlLocation,
328         // SERVICE);
329         // servicePort = s.getEPCglobalEPCISServicePort();
330         serviceConfigured = true;
331     }
332 
333     private void setUpBus() {
334         Bus bus = CXFBusFactory.getDefaultBus();
335         ClientOnlyHTTPTransportFactory httpTransport = new ClientOnlyHTTPTransportFactory();
336         // httpTransport = new ServletTransportFactory();
337         httpTransport.setBus(bus);
338         List<String> transportIds = Arrays.asList(new String[] {
339                 "http://schemas.xmlsoap.org/wsdl/soap/http", "http://schemas.xmlsoap.org/soap/http",
340                 "http://www.w3.org/2003/05/soap/bindings/HTTP/", "http://schemas.xmlsoap.org/wsdl/http/",
341                 "http://cxf.apache.org/transports/http/configuration", "http://cxf.apache.org/bindings/xformat", });
342         httpTransport.setTransportIds(transportIds);
343         httpTransport.registerWithBindingManager();
344         // httpTransport.register();
345     }
346 
347     // X509TrustManager methods: Note that this client will trust any server
348     // you point it at. This is probably OK for the usage for which this program
349     // is intended, but is hardly a robust implementation.
350 
351     public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
352     }
353 
354     public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
355     }
356 
357     public X509Certificate[] getAcceptedIssuers() {
358         return null;
359     }
360 
361     /**
362      * {@inheritDoc}
363      * 
364      * @see org.fosstrak.epcis.queryclient.QueryControlInterface#getQueryNames()
365      */
366     public List<String> getQueryNames() throws ImplementationExceptionResponse, SecurityExceptionResponse,
367             ValidationExceptionResponse {
368         if (!serviceConfigured) {
369             throw new QueryClientNotConfiguredException(
370                     "Please configure service by calling configureService(URL, String[]).");
371         }
372         return servicePort.getQueryNames(new EmptyParms()).getString();
373     }
374 
375     /**
376      * {@inheritDoc}
377      * 
378      * @see org.fosstrak.epcis.queryclient.QueryControlInterface#getStandardVersion()
379      */
380     public String getStandardVersion() throws ImplementationExceptionResponse, SecurityExceptionResponse,
381             ValidationExceptionResponse {
382         if (!serviceConfigured) {
383             throw new QueryClientNotConfiguredException(
384                     "Please configure service by calling configureService(URL, String[]).");
385         }
386         return servicePort.getStandardVersion(new EmptyParms());
387     }
388 
389     /**
390      * {@inheritDoc}
391      * 
392      * @see org.fosstrak.epcis.queryclient.QueryControlInterface#getSubscriptionIds(java.lang.String)
393      */
394     public List<String> getSubscriptionIds(final String queryName) throws ImplementationExceptionResponse,
395             SecurityExceptionResponse, ValidationExceptionResponse, NoSuchNameExceptionResponse {
396         if (!serviceConfigured) {
397             throw new QueryClientNotConfiguredException(
398                     "Please configure service by calling configureService(URL, String[]).");
399         }
400         GetSubscriptionIDs parms = new GetSubscriptionIDs();
401         parms.setQueryName(queryName);
402         return servicePort.getSubscriptionIDs(parms).getString();
403     }
404 
405     /**
406      * {@inheritDoc}
407      * 
408      * @see org.fosstrak.epcis.queryclient.QueryControlInterface#getVendorVersion()
409      */
410     public String getVendorVersion() throws ImplementationExceptionResponse, SecurityExceptionResponse,
411             ValidationExceptionResponse {
412         if (!serviceConfigured) {
413             throw new QueryClientNotConfiguredException(
414                     "Please configure service by calling configureService(URL, String[]).");
415         }
416         return servicePort.getVendorVersion(new EmptyParms());
417     }
418 
419     /**
420      * {@inheritDoc}
421      * 
422      * @see org.fosstrak.epcis.queryclient.QueryControlInterface#poll(org.fosstrak.epcis.model.Poll)
423      */
424     public QueryResults poll(final Poll poll) throws ImplementationExceptionResponse, QueryTooComplexExceptionResponse,
425             QueryTooLargeExceptionResponse, SecurityExceptionResponse, ValidationExceptionResponse,
426             NoSuchNameExceptionResponse, QueryParameterExceptionResponse {
427         if (!serviceConfigured) {
428             throw new QueryClientNotConfiguredException(
429                     "Please configure service by calling configureService(URL, String[]).");
430         }
431         return servicePort.poll(poll);
432     }
433 
434     /**
435      * Parses the query given in its XML representation and sends it to the
436      * Query Operations Module. Same operation as the method with the
437      * InputStream argument.
438      * 
439      * @param query
440      *            The query in its XML form.
441      * @return The QueryResults as it is returned from the repository's Query
442      *         Operations Module.
443      * @throws ServiceException
444      *             If an error within the Query Operations Module occurred.
445      * @throws IOException
446      * @throws QueryParameterExceptionResponse
447      * @throws NoSuchNameExceptionResponse
448      * @throws ValidationExceptionResponse
449      * @throws SecurityExceptionResponse
450      * @throws QueryTooLargeExceptionResponse
451      * @throws QueryTooComplexExceptionResponse
452      * @throws ImplementationExceptionResponse
453      */
454     public QueryResults poll(final String query) throws QueryTooComplexExceptionResponse,
455             QueryTooLargeExceptionResponse, SecurityExceptionResponse, ValidationExceptionResponse,
456             NoSuchNameExceptionResponse, QueryParameterExceptionResponse, IOException, ImplementationExceptionResponse {
457         InputStream is = new ByteArrayInputStream(query.getBytes());
458         return poll(is);
459     }
460 
461     /**
462      * Parses the query given in its XML representation and sends it to the
463      * Query Operations Module.
464      * 
465      * @param queryStream
466      *            The query in its XML form.
467      * @return The QueryResults as it is returned from the repository's Query
468      *         Operations Module.
469      * @throws RemoteException
470      *             If an error communicating with the Query Operations Module
471      *             occurred.
472      * @throws ServiceException
473      *             If an error within the Query Operations Module occurred.
474      * @throws QueryParameterExceptionResponse
475      * @throws NoSuchNameExceptionResponse
476      * @throws ValidationExceptionResponse
477      * @throws SecurityExceptionResponse
478      * @throws QueryTooLargeExceptionResponse
479      * @throws QueryTooComplexExceptionResponse
480      * @throws ImplementationExceptionResponse
481      * @throws IOException
482      */
483     public QueryResults poll(final InputStream queryStream) throws ImplementationExceptionResponse,
484             QueryTooComplexExceptionResponse, QueryTooLargeExceptionResponse, SecurityExceptionResponse,
485             ValidationExceptionResponse, NoSuchNameExceptionResponse, QueryParameterExceptionResponse, IOException {
486         try {
487             JAXBContext context = JAXBContext.newInstance("org.fosstrak.epcis.model");
488             Unmarshaller unmarshaller = context.createUnmarshaller();
489             // setting schema to null will turn XML validation off
490             // unmarshaller.setSchema(null);
491             JAXBElement<?> elem = (JAXBElement<?>) unmarshaller.unmarshal(queryStream);
492             Poll poll = (Poll) elem.getValue();
493             return poll(poll);
494         } catch (JAXBException e) {
495             // wrap JAXBException into IOException to keep the interface
496             // JAXB-free
497             IOException ioe = new IOException(e.getMessage());
498             ioe.setStackTrace(e.getStackTrace());
499             throw ioe;
500         }
501     }
502 
503     /**
504      * {@inheritDoc}
505      * 
506      * @see org.fosstrak.epcis.queryclient.QueryControlInterface#subscribe(org.fosstrak.epcis.model.Subscribe)
507      */
508     public void subscribe(final Subscribe subscribe) throws DuplicateSubscriptionExceptionResponse,
509             ImplementationExceptionResponse, QueryTooComplexExceptionResponse, SecurityExceptionResponse,
510             InvalidURIExceptionResponse, ValidationExceptionResponse, SubscribeNotPermittedExceptionResponse,
511             NoSuchNameExceptionResponse, SubscriptionControlsExceptionResponse, QueryParameterExceptionResponse {
512         if (!serviceConfigured) {
513             throw new QueryClientNotConfiguredException(
514                     "Please configure service by calling configureService(URL, String[]).");
515         }
516         servicePort.subscribe(subscribe);
517     }
518 
519     /**
520      * Parses the query given in its XML representation and sends it to the
521      * Query Operations Module. Same operation as the method with the
522      * InputStream argument.
523      * 
524      * @param query
525      *            The query in its XML form.
526      * @throws ServiceException
527      *             If an error within the Query Operations Module occurred.
528      * @throws QueryParameterExceptionResponse
529      * @throws SubscriptionControlsExceptionResponse
530      * @throws NoSuchNameExceptionResponse
531      * @throws SubscribeNotPermittedExceptionResponse
532      * @throws ValidationExceptionResponse
533      * @throws InvalidURIExceptionResponse
534      * @throws SecurityExceptionResponse
535      * @throws QueryTooComplexExceptionResponse
536      * @throws ImplementationExceptionResponse
537      * @throws DuplicateSubscriptionExceptionResponse
538      * @throws IOException
539      */
540     public void subscribe(final String query) throws DuplicateSubscriptionExceptionResponse,
541             ImplementationExceptionResponse, QueryTooComplexExceptionResponse, SecurityExceptionResponse,
542             InvalidURIExceptionResponse, ValidationExceptionResponse, SubscribeNotPermittedExceptionResponse,
543             NoSuchNameExceptionResponse, SubscriptionControlsExceptionResponse, QueryParameterExceptionResponse,
544             IOException {
545         InputStream is = new ByteArrayInputStream(query.getBytes());
546         subscribe(is);
547     }
548 
549     /**
550      * Parses the query given in its XML representation and sends it to the
551      * Query Operations Module.
552      * 
553      * @param query
554      *            The query in its XML form.
555      * @throws RemoteException
556      *             If an error communicating with the Query Operations Module
557      *             occurred.
558      * @throws ServiceException
559      *             If an error within the Query Operations Module occurred.
560      * @throws QueryParameterExceptionResponse
561      * @throws SubscriptionControlsExceptionResponse
562      * @throws NoSuchNameExceptionResponse
563      * @throws SubscribeNotPermittedExceptionResponse
564      * @throws ValidationExceptionResponse
565      * @throws InvalidURIExceptionResponse
566      * @throws SecurityExceptionResponse
567      * @throws QueryTooComplexExceptionResponse
568      * @throws ImplementationExceptionResponse
569      * @throws DuplicateSubscriptionExceptionResponse
570      * @throws IOException
571      */
572     public void subscribe(final InputStream query) throws DuplicateSubscriptionExceptionResponse,
573             ImplementationExceptionResponse, QueryTooComplexExceptionResponse, SecurityExceptionResponse,
574             InvalidURIExceptionResponse, ValidationExceptionResponse, SubscribeNotPermittedExceptionResponse,
575             NoSuchNameExceptionResponse, SubscriptionControlsExceptionResponse, QueryParameterExceptionResponse,
576             IOException {
577         try {
578             JAXBContext context = JAXBContext.newInstance("org.fosstrak.epcis.model");
579             Unmarshaller unmarshaller = context.createUnmarshaller();
580             JAXBElement<?> elem = (JAXBElement<?>) unmarshaller.unmarshal(query);
581             Subscribe subscribe = (Subscribe) elem.getValue();
582             subscribe(subscribe);
583         } catch (JAXBException e) {
584             // wrap JAXBException into IOException to keep the interface
585             // JAXB-free
586             IOException ioe = new IOException(e.getMessage());
587             ioe.setStackTrace(e.getStackTrace());
588             throw ioe;
589         }
590     }
591 
592     /**
593      * {@inheritDoc}
594      * 
595      * @see org.fosstrak.epcis.queryclient.QueryControlInterface#unsubscribe(java.lang.String)
596      */
597     public void unsubscribe(final String subscriptionId) throws ImplementationExceptionResponse,
598             SecurityExceptionResponse, ValidationExceptionResponse, NoSuchSubscriptionExceptionResponse {
599         if (!serviceConfigured) {
600             throw new QueryClientNotConfiguredException(
601                     "Please configure service by calling configureService(URL, String[]).");
602         }
603         Unsubscribe parms = new Unsubscribe();
604         parms.setSubscriptionID(subscriptionId);
605         servicePort.unsubscribe(parms);
606     }
607 
608     /**
609      * Parses the XML from the given input stream as an Unsubscribe object and
610      * unsubscribes the specified subscription ID from the repository.
611      * 
612      * @param unsubscribeIs
613      * @throws ImplementationExceptionResponse
614      * @throws SecurityExceptionResponse
615      * @throws ValidationExceptionResponse
616      * @throws NoSuchSubscriptionExceptionResponse
617      * @throws IOException
618      */
619     public void unsubscribe(final InputStream unsubscribeIs) throws ImplementationExceptionResponse,
620             SecurityExceptionResponse, ValidationExceptionResponse, NoSuchSubscriptionExceptionResponse, IOException {
621         try {
622             JAXBContext context = JAXBContext.newInstance("org.fosstrak.epcis.model");
623             Unmarshaller unmarshaller = context.createUnmarshaller();
624             JAXBElement<?> elem = (JAXBElement<?>) unmarshaller.unmarshal(unsubscribeIs);
625             Unsubscribe unsubscribe = (Unsubscribe) elem.getValue();
626             unsubscribe(unsubscribe.getSubscriptionID());
627         } catch (JAXBException e) {
628             // wrap JAXBException into IOException to keep the interface
629             // JAXB-free
630             IOException ioe = new IOException(e.getMessage());
631             ioe.setStackTrace(e.getStackTrace());
632             throw ioe;
633         }
634     }
635 }