sábado, 23 de julio de 2016

Cómo crear un cliente SOAP en Java sin usar AXIS, CXF o Metro


Hace un rato que no echamos algo de código, así que hoy vamos a ver como construir un cliente SOAP sin usar las populares librerías externas de AXIS, CXF o Metro, entre otras que se podrían usar.

Esto no es sólo académico. En ocasiones queremos proveer comportamientos específicos, o “tunear” la conexión de modo particular, ganar en flexibilidad y control o buscar mejoras en el rendimiento, y por esta razón nos vemos en la necesidad de efectuar la conexión “a mano” de un cliente SOAP.

Asumimos en este artículo que se tienen conocimientos básicos de Java, SOAP, XML, Xpath, Maven, SoapUI y la librería Freemarker.

Pare este ejemplo vamos a usar el webservice para CloblaWeater que se ubica en la siguiente urlhttp://www.webservicex.net/globalweather.asmx?WSDL.

Así que manos a las sobras… digo, a las obras jajajaja


Lo primero que haremos es probar nuestro servicio con el SoapUI para obtener además el cuerpo del mensaje SOAP.

Abrinos el SoapUI y creamos un proyecto SOAP con la URL antes suministrada.




Marcar la casilla de “Create Requests” la cual permite obtener las llamadas de ejemplo para los servicios disponibles en el WSDL.

Al pulsar Ok, se nos crea un proyecto con una estructura como la que se muestra a continuación



Vamos a usar la versión 1.2 de Soap. Vemos que tenemos los métodos GetCitiesByCountry y, el que nos interesa, GetWeather.

Pulsamos en el Request1 de GetWeather y podemos ver el cuerpo de la llamada SOAP



Podemos notar los parámetros CityName y CountryName que se le deben pasar a la función.
Hagamos una prueba colocando los siguientes valores CityName : Santiago, CountryName : Chile.
Un ejemplo de la llamada y su resultado:


Listo, ya sabemos que funciona nuestro servicio y tenemos incluso el cuerpo de la llamada SOAP.
Vamos ahora a crear nuestro programa Java para acceder a ese servicio.
Voy a usar eclipse neon para crear un nuevo proyecto Maven y dar la configuración adecuada.
Agrego las dependencias y mi archivo POM.xml queda así:
 <project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> 
   <groupId>org.pigbar.hal9k.soap</groupId> 
   <artifactId>ClienteSoapWeather</artifactId> 
   <version>0.0.1-SNAPSHOT</version> 
   <name>ClienteSoapWeather</name> 
   <description>Cliente soap sin librerias</description> 
   <dependencies> 
       <dependency> 
       <groupId>org.apache.httpcomponents</groupId> 
       <artifactId>httpclient</artifactId> 
       <version>4.2.5</version> 
   </dependency> 
   <dependency> 
       <groupId>freemarker</groupId> 
       <artifactId>freemarker</artifactId> 
       <version>2.3.9</version> 
       </dependency> 
   </dependencies> 
</project>
Ahora ejecutamos en elcipse el comando Run as…. Maven Build… con los Goals “clean package”, para que Maven descargue las dependencias de los repositorios indicados. Debe dar una salida como:

[INFO] ------------------------------------------------------------------------ 
[INFO] BUILD SUCCESS 
[INFO] ------------------------------------------------------------------------ 
[INFO] Total time: 12.511 s 
[INFO] Finished at: 2016-07-23T17:16:39-03:00 
[INFO] Final Memory: 12M/242M 
[INFO] ------------------------------------------------------------------------ 

Procedemos a crear la plantilla o template que vamos a usar para el Freemarker.
Como es un proyecto Maven lo hacemos en la carpeta de recursos (src/main/resources). Estos archivos deben llevar por omisión la extensión .ftl. Creamos el archivo “getWeatherByCityTemplate.ftl”. En eclipse es posible que nos solicite instalar un plugin asociado a esa extensión de archivo o que la asociemos al editor por defecto. Ambas acciones son válidas y queda a preferencia del lector. En mi caso yo ya tengo instalado un editor adecuado.
Abrimos el archivo .ftl y pegamos la Request1 de la función GetWeather de la sección Soap 1.2 de nuestro proyecto en SoapUI. Nos debe quedar algo como:


 
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" 
    xmlns:web="http://www.webserviceX.NET"> 
 <soap:Header/> 
 <soap:Body> 
   <web:GetWeather> 
     <web:CityName>${CityName}</web:CityName> 
     <web:CountryName>${CountryName}</web:CountryName> 
     </web:GetWeather> 
   </soap:Body> 
</soap:Envelope>

El punto a destacar aquí son los parámetros ${CityName} y ${CountryName} que se declaran en el template.

Ahora procedemos a crear nuestra clase de prueba para acceder al template, configurar los parámetros, llamar al servicio SOAP y procesar el resultado.

En eclipse, en nuestro proyecto Maven, en la sección src/main/java creamos un paquete adecuado y la clase para procesar la llamada:

 
package org.pigbar.hal9k.soap; 
import java.io.IOException; 
import java.io.StringWriter; 
import java.util.HashMap; 
import java.util.Map; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.parsers.ParserConfigurationException; 
import javax.xml.xpath.XPath; 
import javax.xml.xpath.XPathConstants; 
import javax.xml.xpath.XPathExpression; 
import javax.xml.xpath.XPathExpressionException; 
import javax.xml.xpath.XPathFactory; 
import org.apache.http.HttpResponse; 
import org.apache.http.client.HttpClient; 
import org.apache.http.client.methods.HttpPost; 
import org.apache.http.entity.StringEntity; 
import org.apache.http.impl.client.DefaultHttpClient; 
import org.w3c.dom.Document; 
import org.xml.sax.SAXException; 
import freemarker.template.Configuration; 
import freemarker.template.Template; 
import freemarker.template.TemplateException; 

public class WeatherByCitySoapClient { 
  public static void main(String[] args) { 
  // Configuración de Freemarker 
  Configuration freeMarCfg = new Configuration(); 
  // cliente http 
  HttpClient httpClient = null; 
  try { 
    // Cargar template .ftl 
    Template weatherByCityTmplate = 
    freeMarCfg.getTemplate("src/main/resources/getWeatherByCityTemplate.ftl"); 
    // Cargamos los parámetros 
    Map params = new HashMap(); 
    params.put("CityName", "Santiago"); 
    params.put("CountryName", "Chile"); 
    // Crear mensaje y Llamar al servicio SOAP 
    // mensaje 
    StringWriter strOut = new StringWriter(); 
    weatherByCityTmplate.process(params, strOut); 
    String strEnvelope = strOut.getBuffer().toString(); 
    System.out.println("La llamada queda así: \n" + strEnvelope); 
    // llamada a servicio 
    httpClient = new DefaultHttpClient(); 
    HttpPost postRequest = new HttpPost("http://www.webservicex.net/globalweather.asmx"); 
    StringEntity input = new StringEntity(strEnvelope); 
    input.setContentType("application/soap+xml"); 
    postRequest.setEntity(input); 
    // procesar respuesta 
    HttpResponse response = httpClient.execute(postRequest); 
    if (response.getStatusLine().getStatusCode() != 200) { 
      throw new RuntimeException("Error en la llamada : " + 
      response.getStatusLine().getStatusCode()); 
    } 
    // Obtener información de la respuesta 
    DocumentBuilderFactory factoryDoc = DocumentBuilderFactory.newInstance(); 
    Document XMLDocument = factoryDoc.newDocumentBuilder().parse(response.getEntity().getContent()); 
    XPath xpath = XPathFactory.newInstance().newXPath(); 
    XPathExpression expr = xpath.compile("//GetWeatherResult"); 
    String respuesta = String.class.cast(expr.evaluate(XMLDocument, XPathConstants.STRING)); 
    System.out.println("\n\nLa Respuesta es:\n" + respuesta); 
  } catch (TemplateException e) { 
    e.printStackTrace(); 
  } catch (IOException e) { 
    e.printStackTrace(); 
  } catch (IllegalStateException e) { 
    e.printStackTrace(); 
  } catch (SAXException e) { 
    e.printStackTrace(); 
  } catch (ParserConfigurationException e) { 
    e.printStackTrace(); 
  } catch (XPathExpressionException e) { 
    e.printStackTrace(); 
  }
 } 
}

Acá los puntos mas relevantes son:
Cargar la configuración de Freemarker, cargar plantilla y pasar parámetros.

  // Configuración de Freemarker 
  Configuration freeMarCfg = new Configuration(); 
  // Cargar template .ftl 
  Template weatherByCityTmplate = 
  freeMarCfg.getTemplate("src/main/resources//getWeatherByCityTemplate.ftl"); 
  // Cargamos los parámetros 
  Map params = new HashMap(); 
  params.put("CityName", "Santiago"); 
  params.put("CountryName", "Chile");

Generar cuerpo de llamada y hacer llamada por método Post de Http.

   StringWriter strOut = new StringWriter(); 
   weatherByCityTmplate.process(params, strOut); 
   String strEnvelope = strOut.getBuffer().toString(); 
   System.out.println("La llamada queda así: \n" + strEnvelope); 
   // llamada a servicio 
   httpClient = new DefaultHttpClient(); 
   HttpPost postRequest = new HttpPost("http://www.webservicex.net/globalweather.asmx"); 
   StringEntity input = new StringEntity(strEnvelope); 
   input.setContentType("application/soap+xml"); 
   postRequest.setEntity(input);

Los datos para el nput.setContentType("application/soap+xml");
Los podemos ver en el SoapUI, en la respuesta del servicio, en la pestaña RAW, allí sale el Content-Type adecuado.

Y obtenemos la información de la respuesta usando Xpath.

   // Obtener información de la respuesta 
   DocumentBuilderFactory factoryDoc = DocumentBuilderFactory.newInstance(); 
   Document XMLDocument = factoryDoc.newDocumentBuilder().parse(response.getEntity().getContent()); 
   XPath xpath = XPathFactory.newInstance().newXPath(); 
   XPathExpression expr = xpath.compile("//GetWeatherResult"); 
   String respuesta = String.class.cast(expr.evaluate(XMLDocument, XPathConstants.STRING)); 
   System.out.println("\n\nLa Respuesta es:\n" + respuesta);

Al ejecutar este programa en eclipse obtenemos una salida similar a la siguiente:

 
  La llamada queda así: 

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:web="http://www.webserviceX.NET"> 
 <soap:Header/> 
 <soap:Body> 
   <web:GetWeather> 
   <!--Optional:--> 
   <web:CityName>Santiago</web:CityName> 
   <!--Optional:--> 
   <web:CountryName>Chile</web:CountryName> 
   </web:GetWeather> 
 </soap:Body> 
</soap:Envelope> 
 

La Respuesta es: 

<?xml version="1.0" encoding="utf-16"?> 
<CurrentWeather> 
  <Location>Quintero Santiago, Chile (SCER) 32-47S 071-31W 8M</Location> 
  <Time>Mar 30, 2011 - 11:00 AM EDT / 2011.03.30 1500 UTC</Time> 
  <Wind> from the SW (220 degrees) at 10 MPH (9 KT) (direction variable):0</Wind> 
  <Visibility> greater than 7 mile(s):0</Visibility> 
  <Temperature> 75 F (24 C)</Temperature> 
  <DewPoint> 57 F (14 C)</DewPoint> 
  <RelativeHumidity> 53%</RelativeHumidity> 
  <Pressure> 29.91 in. Hg (1013 hPa)</Pressure> 
  <Status>Success</Status> 
</CurrentWeather>

Como vemos esta forma de llamada nos brinda un gran control sobre la ejecución y los recursos asociados. Ideal para esos entornos donde no podemos usar otras librerías ya mencionadas.


No hay comentarios:

Publicar un comentario