web-dev-qa-db-fra.com

JAX-WS Chargement de WSDL à partir de jar

J'écris un client lourd qui utilise un service SOAP pour certaines fonctionnalités (rapport de bogue, etc.)

JAX-WS fonctionne correctement, mais par défaut (dans Netbeans au moins), il récupère le WSDL du serveur distant à chaque initialisation du service. Je pense que cela aide à fournir du support de gestion de version, etc., mais ce n'est pas ce que je veux.

J'ai ajouté l'argument wsdllocation à wsimport pour pointer les classes générées vers une ressource locale. L'extrait de code suivant correspond au chargement de l'URL de la ressource WSDL à partir de ApplicationService.Java.

baseUrl = net.example.ApplicationService.class.getResource(".");
url = new URL(baseUrl, "service.wsdl");

Je suis presque sûr que cela ne devrait pas poser de problème de pointer sur une ressource stockée dans un fichier jar du package net/example/resources, et que le fichier jar est construit comme prévu. Cependant, le service ne se chargera pas ... spécifiquement, je reçois une exception NullPointerException lorsque j'appelle ApplicationService.getPort ();

Est-ce possible? ou juste une chasse à l'oie sauvage?

41
Cogsy

Oui, c’est tout à fait possible, comme je l’ai fait lors de la création de clients via javax.xml.ws.EndpointReference, une classe liée à WS-A. J'ai ajouté une référence de chemin de classe au WSDL dans WS-A EndPointReference et l'implémentation Metro de JAX-WS l'a tout simplement bien chargé. Que vous chargiez le WSDL à partir de WS-A EndPointReference, d’un fichier ou d’une URL http, votre implémentation JAX-WS doit utiliser le même code d’analyse WSDL que vous ne résolvez que par la résolution des URL.

La meilleure approche pour vous est probablement de faire quelque chose comme ceci:

URL wsdlUrl = MyClass.class.getResource(
            "/class/path/to/wsdl/yourWSDL.wsdl");

Service yourService= Service.create(
            wsdlUrl,
            ...);

Où ... représente le nom QName d'un service WSDL à l'intérieur de votre WSDL. Maintenant, la chose importante à retenir est que votre WSDL doit être complet et valide. Cela signifie que si votre WSDL importe des fichiers XSD ou d'autres WSDL, les URL doivent être correctes. Si vous avez inclus vos fichiers WSDL et XSD importés dans le même fichier JAR que le fichier WSDL, vous devez utiliser des URL relatives pour les importations et conserver toutes vos importations dans le même fichier JAR. Le gestionnaire d'URL JAR ne considère pas les URL relatives comme relatives par rapport au chemin de classe, mais plutôt comme relatives dans le fichier JAR. Vous ne pouvez donc pas importer dans votre WSDL des importations s'exécutant sur des JAR, à moins d'implémenter un gestionnaire d'URL personnalisé et votre propre préfixe résolution basée sur les chemins de classe des importations. Si votre WSDL importe des ressources externes, vous pouvez vous connecter à des problèmes de maintenance si ces ressources sont déplacées. L'utilisation même d'une copie statique du WSDL à partir de votre chemin d'accès aux classes est contraire à l'esprit du WSDL, des services Web et de JAX-WS, mais cela est parfois nécessaire.

Enfin, si vous intégrez un WSDL statique, je vous suggère au moins de rendre le point de terminaison de service configurable à des fins de test et de déploiement. Le code permettant de reconfigurer le noeud final de votre client de service Web est le suivant:

  YourClientInterface client = yourService.getPort(
            new QName("...", "..."),
            YourClientInterface.class);
  BindingProvider bp = (BindingProvider) client;
  bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                "http://localhost:8080/yourServiceEndpoint");
47
DavidValeri

Au moins pour la dernière version de JAX-WS, vous n'avez pas besoin de catalogues de schéma ni de paramétrage d'emplacement wsdl par programmeSIvous placez le fichier WSDL dans le fichier JAR, puis définissez wsimport wsdlLocation sur le chemin de ressource relatif du fichier WSDL dans le pot. C'est-à-dire que JAX-WS utilise le Class.getResource intégré à Java pour charger le WSDL.

Si vous utilisez Maven, c'est quelque chose comme:

  <plugin>
    <groupId>org.jvnet.jax-ws-commons</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>2.3</version>
    <executions>
      <execution>
        <goals>
          <goal>wsimport</goal>
        </goals>
        <!-- Following configuration will invoke wsimport once for each wsdl. -->
        <configuration>
            <!--- VERY IMPORTANT THAT THE PATH START WITH '/' -->
    <wsdlLocation>/com/adamgent/ws/blah.wsdl</wsdlLocation>
    <wsdlDirectory>${basedir}/src/main/resources/com/adamgent/ws</wsdlDirectory>
    <wsdlFiles><wsdlFile>blah.wsdl</wsdlFile></wsdlFiles>
       </configuration>
      </execution>
    </executions>
  </plugin>

Pour l'exemple ci-dessus, vous placeriez donc le WSDL à l'aide de la disposition du projet Maven ici src/main/resources/com/adamgent/ws.

Assurez-vous que le WSDL entre dans le fichier JAR de Maven, comme suit:

<build>
      <resources>
        <resource>
          <directory>src/main/resources</directory>
        </resource>
      </resources> ....

Désormais, votre code généré par wsimport et votre WSDL se trouvent dans un fichier JAR autonome. Pour utiliser le service, vous n'avez pas besoin de définir l'emplacement du WSDL. C'est aussi simple que:

BlahService myService = new BlayService_Service().getBlahServicePort();

Il devrait être trivial de le mapper sur wsimport d'ANT.

14
Adam Gent

Peut-être un peu tard, mais j'ai trouvé une solution assez simple pour résoudre ce problème, mais cela impliquait une modification du code généré de la classe Service:

Si la ligne suivante dans la classe Service

baseUrl = net.example.ApplicationService.class.getResource(".");

est changé en

baseUrl = net.example.ApplicationService.class.getResource("");

cela fonctionne bien même avec un WSDL qui est emballé dans un fichier JAR. Pas sûr du comportement supposé exact de getResource () dans l'un ou l'autre cas, mais je n'ai rencontré aucun problème avec cette approche jusqu'à présent, sur plusieurs versions de système d'exploitation et Java.

6
weiresr

Ce que vous décrivez est un bogue dans JAX-WS: JAX_WS-888 - Code incorrect pour la résolution de l’URL d’une adresse wsdlLocation personnalisée .

Elle a été corrigée pour la V2.2, il vous suffit donc de définir maintenant wsdlLocation, comme vous écrivez, pour que cela fonctionne.

5
sleske

Une autre réponse consiste à utiliser le 

new Service(wsdllocation, servicename );

pour obtenir l'objet de service. 

Voici comment j'ai résolu le problème.

3
thorty

Si votre chemin de classe a "." Class.getResource (".") renverra l’URL du répertoire à partir duquel vous avez exécuté la commande Java. Sinon, il retournera un null. Ajustez le wsdllocation en conséquence.

3
nagarjun

J'ai remplacé l'emplacement du WSDL avant de créer le fichier client ici.

  1. Copiez le WSDL dans le répertoire dir.
  2. Remplacez la classe Service, reportez-vous à WSDL à l'aide de classpath.
  3. construire les stubs du client.
  4. pot les bouts.
<copy todir="@{dest-dir}/@{dir-package}" verbose="@{verbose}">
  <fileset dir="@{dir-wsdl}" includes="*.wsdl,*.xsd" />
</copy>
<echo message="Replacing Service to point to correct WSDL path..." />
<replaceregexp match="new URL(.*)" replace='Class.class.getResource("@{name-wsdl}");' flags="gm">
  <fileset dir="@{source-dest-dir}">
    <include name="@{dir-package}/*Service.Java" />
  </fileset>
</replaceregexp>
<replaceregexp match="catch (.*)" replace='catch (Exception ex) {' flags="gm">
  <fileset dir="@{source-dest-dir}">
    <include name="@{dir-package}/*Service.Java" />
  </fileset>
</replaceregexp>
2
kiri

Je suis tombé sur le même problème. Le code client généré par JAXWS utilise l'astuce MyService.class.getResource(".") pour charger le fichier wsdl ... mais après les tests, cela ne semble fonctionner que si le fichier de classe est dans un répertoire du système de fichiers. Si le fichier de classe se trouve dans un fichier JAR, cet appel renvoie null pour l'URL. 

Cela ressemble à un bogue dans le JDK puisque si vous construisez votre URL de la manière suivante:

final URL url = new URL( MyService.class.getResource( MyService.class.getSimpleName() + ".class"), "myservice.wsdl");

alors cela fonctionne aussi si la classe et wsdl sont regroupés dans un bocal.

J'imagine que la plupart des gens vont se regrouper dans un pot!

2
David Nouls

Voici ma solution de contournement.

Je décompresse le WSDL du fichier jar et l’écris dans un fichier situé à proximité du fichier jar:

File wsdl = new File("../lib/service.wsdl");
InputStream source = getClass().getResource("resources/service.wsdl").openStream();
FileOutputStream out = new FileOutputStream(wsdl);

byte[] buffer = new byte[512];
int read;
while((read = source.read(buffer)) >= 0) {
    out.write(buffer, 0, read);
}

Puis pointez les classes de service sur file:../lib/service.wsdl.

Cela fonctionne, mais j'apprécierais que quiconque puisse me montrer une solution plus élégante. 

1
Cogsy

En voici un qui fonctionne pour moi (en particulier via http et https). Cas de JAX-WS du JDK Oracle 1.8.0_51 utilisant des classes créées par Apache CXF 3.1.1.

Notez que le WSDL distant est obtenu uniquement lors du premier appel. Selon le modèle d'utilisation (programme de longue durée), cela peut être tout à fait acceptable.

Les bases:

  • Téléchargez le fichier WSDL à partir de l'hôte distant et stockez-le en tant que fichier: wget --output-document=wsdl_raw.xml $WSDL_URL
  • Vous voudrez peut-être xmllint --format wsdl_raw.xml > wsdl.xml pour le formatage Nice
  • Générez des classes client à l'aide de l'outil de ligne de commande : ./cxf/bin/wsdl2Java -d output/ -client -validate wsdl.xml et importez-les dans votre projet.

Vérifiez que les définitions de service pour http et https existent dans le fichier WSDL. Dans mon cas, le fournisseur n'en avait pas pour https (mais avait accepté le trafic https) et je devais l'ajouter manuellement. Quelque chose dans ce sens devrait être dans le WSDL:

  <wsdl:service name="fooservice">
    <wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort">
      <soap:address location="http://ws.example.com/a/b/FooBarWebService"/>
    </wsdl:port>
  </wsdl:service>
  <wsdl:service name="fooservice-secured">
    <wsdl:port binding="tns:fooserviceSoapBinding" name="FooBarWebServicePort">
      <soap:address location="https://ws.example.com/a/b/FooBarWebService"/>
    </wsdl:port>
  </wsdl:service>

CXF aurait dû générer une classe qui implémente javax.xml.ws.Service appelée par exemple Fooservice, avec les constructeurs appropriés:

public class Fooservice extends Service {

  public Fooservice(URL wsdlLocation) {
      super(wsdlLocation, SERVICE);
  }

  public Fooservice(URL wsdlLocation, QName serviceName) {
      super(wsdlLocation, serviceName);
  }

  public Fooservice() {
      super(WSDL_LOCATION, SERVICE);
  }

  ...etc...

Quelque part dans votre code (ici, certains Groovy pour une lecture plus facile), vous initialisez l’instance Service ci-dessus, puis appelez un port. Ici, en fonction du drapeau appelé secure, nous utilisons https ou http:

static final String NAMESPACE = 'com.example.ws.a.b'
static final QName SERVICE_NAME_HTTP = new QName(NAMESPACE, 'fooservice')
static final QName SERVICE_NAME_HTTPS = new QName(NAMESPACE, 'fooservice-secured')

Fooservice wsService
File wsdlfile = new File('/somewhere/on/disk/wsdl.xml')

// If the file is missing there will be an exception at connect
// time from Sun.net.www.protocol.file.FileURLConnection.connect
// It should be possible to denote a resource on the classpath 
// instead of a file-on-disk. Not sure how, maybe by adding a handler
// for a 'resource:' URL scheme?

URI wsdlLocationUri = Java.nio.file.Paths(wsdlfile.getCanonicalPath()).toUri()

if (secure) {
  wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTPS)
}
else {
  wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP)
}

SomeServicePort port = wsService.getSomeServicePort()

port.doStuff()

L’alternative, qui télécharge le WSDL sur une connexion distincte de celle utilisée pour l’appel du service (utilisez tcpdump -n -nn -s0 -A -i eth0 'tcp port 80' pour observer le trafic), consiste simplement à:

URI wsdlLocationUri

if (secure) {
   wsdlLocationUri = new URI('https://ws.example.com/a/b/FooBarWebService?wsdl')
}
else {
   wsdlLocationUri = new URI('http://ws.example.com/a/b/FooBarWebService?wsdl')
}

Fooservice wsService = new Fooservice(wsdlLocationUri.toURL(), SERVICE_NAME_HTTP)

SomeServicePort port = wsService.getSomeServicePort()

port.doStuff()

Notez que cela utilise en fait correctement https si wsdlLocationUri spécifie https, malgré le fait que wsService a été initialisé avec SERVICE_NAME_HTTP. (Vous ne savez pas pourquoi - le service utilise-t-il le schéma utilisé pour extraire la ressource WSDL?)

Et c'est à peu près tout.

Pour déboguer la connexion, passez:

-Dcom.Sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true
-Dcom.Sun.xml.internal.ws.transport.http.HttpAdapter.dump=true

à la machine virtuelle Java sur la ligne de commande. Cela écrira les informations du code de connexion http sur stdout (malheureusement, PAS à Java.util.logging. Oracle, s'il vous plait!).

1
David Tonhofer

Ma solution était de modifier le service généré. Vous devez changer wsdlLocation dans l'annotation d'en-tête et le bloc d'instantion ressemble à ceci:

    static {
    URL url = null;
    url = com.ups.wsdl.xoltws.ship.v1.ShipService.class.getResource("Ship.wsdl");
    SHIPSERVICE_WSDL_LOCATION = url;
    }

Je place le fichier WSDL dans le répertoire bin à côté de la classe ShipService

0
IcedDante

Pas besoin de compliquer quoi que ce soit, Utilisez simplement jar classloader

ClassLoader cl = SomeServiceImplService.class.getClassLoader();
SERVICE_WSDL_LOCATION = cl.getResource("META-INF/wsdls/service.wsdl");

Essayez le!

0
lunicon

Bien que vous puissiez le faire fonctionner avec quelques manipulations, je recommanderais pas de le faire et de le garder tel que vous le faites actuellement.

Les fournisseurs de points de terminaison de service Web doivent fournir un WSDL dans le cadre de leur contrat. Le code que vous générez doit être extrait du WSDL du serveur lui-même.

Lors du déploiement sur WebSphere, vous pouvez remplacer les noeuds finaux par d'autres à partir de l'interface utilisateur de déploiement. Pour les autres serveurs d’applications, vous devrez peut-être connaître le code de liaison XML spécifique au fournisseur.

Cela se produit uniquement lors de l'initialisation, l'impact sur votre application globale doit donc être négligeable.

0
Archimedes Trajano