Para poder arrancar el demo lo primero es conseguir o implementar un servicio REST de tal forma de poder consumirlo. En mi caso voy a aprovechar que tengo instalado Sonatype Nexus el cual se encarga de exponer algunos servicios. La documentación de los servicios de nexus es casi inexistente al momento pero buscando por internet encontre algunos uri. Para las pruebas voy a usar el que retorna el estado de la instancia.
http://your.company.com/nexus/service/local/status
El cual devuelve un xml con la siguiente estructura (el xml es mas extenso, solo deje la parte que me interesa)
<status> <data> <appName>Sonatype Nexus Maven Repository Manager</appName> <formattedAppName>Sonatype Nexus™ Open Source Edition, Version: 1.8.0</formattedAppName> <version>1.8.0</version> <apiVersion>1.8.0</apiVersion> <editionLong>Open Source</editionLong> <editionShort>OSS</editionShort> <state>STARTED</state> <operationMode>STANDALONE</operationMode> <initializedAt>2010-10-26 12:28:06.732 EDT</initializedAt> <startedAt>2010-10-26 12:28:17.334 EDT</startedAt> <lastConfigChange>2010-10-26 12:28:17.334 EDT</lastConfigChange> <firstStart>false</firstStart> <instanceUpgraded>false</instanceUpgraded> <configurationUpgraded>false</configurationUpgraded> </data> </status>
Antes de escribir cualquier clase creo el pom.xml de maven con las dependencias respectivas
<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>neptuno.demo.spring</groupId> <artifactId>demo-spring-rest</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-spring-rest</name> <url>http://maven.apache.org</url> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <org.springframework.version>3.0.5.RELEASE</org.springframework.version> <org.logback.version>0.9.26</org.logback.version> </properties> <dependencies> <!-- Dependencias para Rest Template --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-xml</artifactId> <version>1.5.9</version> </dependency> <!-- Dependencias para pruebas unitarias --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <!-- Dependencias para logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>${org.logback.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${org.logback.version}</version> </dependency> </dependencies> </project>
El siguiente paso es crear un bean con la representación del xml
public class NexusStatus { private String appName; private String formattedAppName; private String version; private String apiVersion; private String editionLong; private String editionShort; private String state; private String operationMode; private String initializedAt; private String startedAt; private String lastConfigChange; // Generar todos los getters y setter @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("NexusStatus [appName="); builder.append(appName); builder.append(", formattedAppName="); builder.append(formattedAppName); builder.append(", version="); builder.append(version); builder.append(", apiVersion="); builder.append(apiVersion); builder.append(", editionLong="); builder.append(editionLong); builder.append(", editionShort="); builder.append(editionShort); builder.append(", state="); builder.append(state); builder.append(", operationMode="); builder.append(operationMode); builder.append(", initializedAt="); builder.append(initializedAt); builder.append(", startedAt="); builder.append(startedAt); builder.append(", lastConfigChange="); builder.append(lastConfigChange); builder.append("]"); return builder.toString(); } }Como en todas las aplicaciones de Spring, el archivo de configuración es el que se encarga de hacer la mayoría de la magia. Para este ejemplo el archivo de spring lo denomine nexus-spring.xml y esta ubicado en la carpeta resources
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <context:component-scan base-package="neptuno.demo.spring.rest" /> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter" /> </list> </property> </bean> <bean id="xpathTemplate" class="org.springframework.xml.xpath.Jaxp13XPathTemplate" /> <bean id="nexusStatusNodeMapper" class="neptuno.demo.spring.rest.NexusStatusNodeMapper" /> </beans>
En este archivo se definen 3 bean y se agrega una instruccion especial para Spring
- La entrada component-scan apunta al paquete que debe ser evaluado por Spring para buscar clases con anotaciones.
- restTemplate es una instancia de Spring RestTemplate indicando que el convertidor del cuerpo del mensaje es la clase SourceHttpMessageConverter. Esta clase convierte entre request/response http y javax.xml.transform.Source, de esta forma se puede recibir la respuesta de Nexus y procesarla como xml.
- xpathTemplate es usado para convertir entre Source y el bean NexusStatus, en este caso usando JAXP 1.3
- nexusStatusNodeMapper implementa org.springframework.xml.xpath.NodeMapper. Es similar al row mapper de JDBCTemplate y se usa para convertir un nodo del xml al objeto NexusStatus
/** * */ package neptuno.demo.spring.rest; import javax.xml.transform.Source; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.xml.xpath.Jaxp13XPathTemplate; import org.springframework.xml.xpath.NodeMapper; @Component("nexusRestAccess") public class NexusRestAccess { private static Logger logger = LoggerFactory .getLogger(NexusRestAccess.class); @Autowired private RestTemplate restTemplate; @Autowired private Jaxp13XPathTemplate xpathTemplate; @Autowired private NodeMapper nexusStatusNodeMapper; /** * Obtiene el estado de la instancia de Nexus * * @return */ public NexusStatus getNexusStatus() { Source source = restTemplate.getForObject( "http://my.company.com/nexus/service/local/status", Source.class); return (NexusStatus) xpathTemplate.evaluateAsObject("//data", source, nexusStatusNodeMapper); } public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "/nexus-spring.xml"); NexusRestAccess demo = applicationContext.getBean("nexusRestAccess", NexusRestAccess.class); NexusStatus status = demo.getNexusStatus(); logger.info(status.toString()); } }
Como ven la clase usa anotaciones para indicarle a Spring que debe inyectar. La linea
Source source = restTemplate.getForObject( "http://my.company.com/nexus/service/local/status", Source.class);
invoca el servicio remoto y devuelve el resultado en un objeto tipo Source. Después se utiliza xpathTemplate para convertirlo a NexusSource. Uno de los parametros de xpathTemplate es el mapper definido en el archivo de Spring. El código de la clase es el siguiente
/** * */ package neptuno.demo.spring.rest; import org.springframework.xml.xpath.NodeMapper; import org.w3c.dom.DOMException; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class NexusStatusNodeMapper implements NodeMapper { /** * (non-JSDoc) * * @see org.springframework.xml.xpath.NodeMapper#mapNode(org.w3c.dom.Node, * int) */ @Override public Object mapNode(Node node, int i) throws DOMException { NexusStatus newStatus = new NexusStatus(); NodeList children = node.getChildNodes(); for (int j = 0; j < children.getLength(); j++) { Node n = children.item(j); if ("appName".equals(n.getNodeName())) { newStatus.setAppName(n.getTextContent()); } else if ("version".equals(n.getNodeName())) { newStatus.setVersion(n.getTextContent()); } else if ("formattedAppName".equals(n.getNodeName())) { newStatus.setFormattedAppName(n.getTextContent()); } else if ("apiVersion".equals(n.getNodeName())) { newStatus.setApiVersion(n.getTextContent()); } else if ("state".equals(n.getNodeName())) { newStatus.setState(n.getTextContent()); } else if ("editionLong".equals(n.getNodeName())) { newStatus.setEditionLong(n.getTextContent()); } else if ("editionShort".equals(n.getNodeName())) { newStatus.setEditionShort(n.getTextContent()); } else if ("operationMode".equals(n.getNodeName())) { newStatus.setOperationMode(n.getTextContent()); } else if ("initializedAt".equals(n.getNodeName())) { newStatus.setInitializedAt(n.getTextContent()); } else if ("startedAt".equals(n.getNodeName())) { newStatus.setStartedAt(n.getTextContent()); } else if ("lastConfigChange".equals(n.getNodeName())) { newStatus.setLastConfigChange(n.getTextContent()); } } return newStatus; } }
Por ultimo ejecutamos el metodo main() de la clase NexusRestAccess la cual invoca el servicio y muestra por consola el objeto NexusStatus
Nov 10, 2010 3:56:13 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1add2dd: startup date [Wed Nov 10 15:56:13 EST 2010]; root of context hierarchy Nov 10, 2010 3:56:13 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from class path resource [nexus-spring.xml] Nov 10, 2010 3:56:16 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1bc82e7: defining beans [nexusRestAccess,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,restTemplate,xpathTemplate,nexusStatusNodeMapper]; root of factory hierarchy 15:56:19.798 [main] INFO n.demo.spring.rest.NexusRestAccess - NexusStatus [appName=Sonatype Nexus Maven Repository Manager, formattedAppName=Sonatype Nexus™ Open Source Edition, Version: 1.8.0, version=1.8.0, apiVersion=1.8.0, editionLong=Open Source, editionShort=OSS, state=STARTED, operationMode=STANDALONE, initializedAt=2010-10-26 12:28:06.732 EDT, startedAt=2010-10-26 12:28:17.334 EDT, lastConfigChange=2010-10-26 12:28:17.334 EDT]
También podemos agregar una pequeña prueba unitaria para probar el código
package neptuno.demo.spring.rest; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; @ContextConfiguration(locations = { "/nexus-spring.xml" }) public class NexusRestAccessTest extends AbstractJUnit4SpringContextTests { private NexusRestAccess demo; @Before public void setup() { demo = applicationContext.getBean("nexusRestAccess", NexusRestAccess.class); } @Test public void testNexusStatus() { NexusStatus status = demo.getNexusStatus(); Assert.assertNotNull(status); Assert.assertTrue(status.getAppName().indexOf("Sonatype") >= 0); } }
Como ven es bien sencillo consumir servicios REST usando Spring RestTemplate. Esta clase no solo soporta consultas (GET) sino también los otros métodos (PUT, DELETE, POST, etc). Para mas informaición revisen la documentación.
No hay comentarios.:
Publicar un comentario