<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet title="XSL formatting" type="text/xsl" href="http://blog.netapsys.fr/index.php/feed/rss2/xslt" ?><rss version="2.0"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <title>Netapsys Blog</title>
  <link>http://blog.netapsys.fr/index.php/</link>
  <atom:link href="http://blog.netapsys.fr/index.php/feed/author/jean-baptiste_defard/rss2" rel="self" type="application/rss+xml"/>
  <description></description>
  <language>fr</language>
  <pubDate>Wed, 08 Feb 2012 21:50:52 +0100</pubDate>
  <copyright>Netapsys 2008 - 2011</copyright>
  <docs>http://blogs.law.harvard.edu/tech/rss</docs>
  <generator>Dotclear</generator>
  
    
  <item>
    <title>Web service REST avec JAXB. Simple vraiment ?</title>
    <link>http://blog.netapsys.fr/index.php/post/2011/02/23/Web-service-REST-avec-JAXB.-Simple-vraiment</link>
    <guid isPermaLink="false">urn:md5:594ffe9952d223cd31101c2388900668</guid>
    <pubDate>Wed, 23 Feb 2011 14:48:00 +0100</pubDate>
    <dc:creator>Jean-Baptiste Defard</dc:creator>
        <category>Java J2EE</category>
            
    <description>&lt;p&gt;Ce billet discute de la réalisation d'un web service REST avec JAXB dans le contexte d'un code existant. Dans ce cadre, REST/JAXB est elle la solution la plus simple&amp;nbsp;?&lt;/p&gt;    &lt;p&gt;La mise en oeuvre de REST décrite &lt;a href=&quot;http://blog.netapsys.fr/index.php/post/2010/12/02/RESTer-simple-avec-Spring-et-Jaxb-Partie-1-%3A-Binding-avec-JAXB&quot; hreflang=&quot;fr&quot;&gt;ici&lt;/a&gt; et &lt;a href=&quot;http://blog.netapsys.fr/index.php/post/2010/12/02/RESTer-simple-avec-Spring-et-Jaxb-Partie-2-%3A-serveur-REST-avec-Spring-web&quot; hreflang=&quot;fr&quot;&gt;là&lt;/a&gt; est intéressante par sa simplicité et son économie.
Je l'ai utilisée pour mettre en oeuvre un service de supervision très simple, juste une opération echo()&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
public interface MonitorService
{
  String echo(final String valeur);
}

@Controller
@RequestMapping(value=&amp;quot;/monitoring&amp;quot;)
public class MonitorServiceImpl
implements MonitorService
{

    final private Logger log = LoggerFactory.getLogger(MonitorServiceImpl.class);

    @ResponseBody
    @RequestMapping(value = &amp;quot;/echo/{valeur}&amp;quot;, method = RequestMethod.GET)
    public String echo(@PathVariable final String valeur)
    {
    	log.info(&amp;quot;echo(&amp;quot;+valeur+&amp;quot;)&amp;quot;);
    	if (valeur == null) {
    		return &amp;quot;NULL&amp;quot;;
    	}
    	return valeur.toUpperCase();
    }
}
&lt;/pre&gt;

&lt;p&gt;Avec les déclarations Spring suivante aux &quot;bons endroits&quot;&amp;nbsp;:
Dans le web.xml&lt;/p&gt;
&lt;pre&gt;
&amp;lt;servlet&amp;gt;
  &amp;lt;servlet-name&amp;gt;monitor&amp;lt;/servlet-name&amp;gt;
  &amp;lt;servlet-class&amp;gt;org.springframework.web.servlet.DispatcherServlet&amp;lt;/servlet-class&amp;gt;
  &amp;lt;load-on-startup&amp;gt;1&amp;lt;/load-on-startup&amp;gt;
&amp;lt;/servlet&amp;gt;

&amp;lt;servlet-mapping&amp;gt;
  &amp;lt;servlet-name&amp;gt;monitor&amp;lt;/servlet-name&amp;gt;
  &amp;lt;url-pattern&amp;gt;/do/*&amp;lt;/url-pattern&amp;gt;
&amp;lt;/servlet-mapping&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Dans le fichier Spring monitor-servlet.xml de configuration de la servlet&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
&amp;lt;bean class=&amp;quot;org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter&amp;quot;&amp;gt;
  &amp;lt;property name=&amp;quot;messageConverters&amp;quot;&amp;gt;
    &amp;lt;list&amp;gt;
       &amp;lt;bean class=&amp;quot;org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter&amp;quot;/&amp;gt;
    &amp;lt;/list&amp;gt;
  &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&amp;quot;monitor.service.http&amp;quot;  class=&amp;quot;org.netapsys.monitor.impl.MonitorServiceImpl&amp;quot;/&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Le tout est assemblé dans un war, puis déployé sur un serveur d'application.&lt;/p&gt;


&lt;p&gt;Il ne reste plus qu'à écrire la partie cliente&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
public class MonitoringClientImpl 
implements MonitorService
{
    private RestTemplate restTemplate  = null;
    
    private String urlServeur = StringUtils.EMPTY;

    @Override
    public String echo(final String valeur) {
        return getRestTemplate().getForObject(
                getUrlServeur() + &amp;quot;/do/monitoring/echo/{valeur}&amp;quot;,
                String.class,
                reference);
    }
}
&lt;/pre&gt;

&lt;p&gt;Avec le fichier Spring de configuration pour le test&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
&amp;lt;bean id=&amp;quot;monitor.client&amp;quot; class=&amp;quot;org.netapsys.monitor.client.MonitoringClientImpl&amp;quot;&amp;gt;
  &amp;lt;property name=&amp;quot;restTemplate&amp;quot; ref=&amp;quot;rstemp.rest.template&amp;quot;/&amp;gt;
  &amp;lt;property name=&amp;quot;urlServeur&amp;quot; value=&amp;quot;${rstemp.url}&amp;quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&amp;lt;bean id=&amp;quot;rstemp.rest.template&amp;quot; class=&amp;quot;org.springframework.web.client.RestTemplate&amp;quot;/&amp;gt;
&amp;lt;!-- rstemp.url est une valeur substituée par un PropertyPlaceholderConfigurer --&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Et le code de test&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
public class MonitorRestClientImplTest
extends AbstractSpringContexteTestNg
{

    private final String[] fichiersContextes = {&amp;quot;test-context.xml&amp;quot;};

    @BeforeClass
    public void chargerContexte()
    {
        chargerContexte(this.fichiersContextes);
    }

    @Test
    public void testHello()
    {
        final MonitorService service = (MonitorService) getBean(&amp;quot;monitor.client&amp;quot;);
        final String result = service.echo(&amp;quot;hello&amp;quot;);
        info(result);
        Assert.assertEquals(result, &amp;quot;HELLO&amp;quot;);
    }
}
&lt;/pre&gt;

&lt;p&gt;Malheureusement, le test échoue&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
698 DEBUG RestTemplate(78): Created GET request for &amp;quot;http://localhost:8080/rstempx/do/monitoring/echo/hello&amp;quot;
700 DEBUG RestTemplate(520): Setting request Accept header to [text/plain, */*]
930  WARN RestTemplate(478): GET request for &amp;quot;http://localhost:8080/rstempx//do/monitoring/echo/hello&amp;quot; resulted in 406 (Not Acceptable); invoking error handler
FAILED: testHello
org.springframework.web.client.HttpClientErrorException: 406 Not Acceptable
&lt;/pre&gt;

&lt;p&gt;La trace côté serveur confirme que l'invocation est bien reçue mais qu'il y a un problème dans le traitement&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
904 DEBUG DispatcherServlet(693): DispatcherServlet with name 'monitor' processing GET request for [/rstempx//do/monitoring/echo/hello]
911 DEBUG DefaultAnnotationHandlerMapping(266): Matching patterns for request [/monitoring/echo/hello] are [/monitoring/echo/{valeur}]
913 DEBUG DefaultAnnotationHandlerMapping(290): URI Template variables for request [/monitoring/echo/hello] are {valeur=hello}
915 DEBUG DefaultAnnotationHandlerMapping(221): Mapping [/monitoring/echo/hello] to HandlerExecutionChain with handler [org.netapsys.monitor.impl.MonitorServiceImpl@3f3cbbbf] and 2 interceptors
919 DEBUG DispatcherServlet(769): Last-Modified value for [/rstempx//do/monitoring/echo/hello] is: -1
962 DEBUG HandlerMethodInvoker(173): Invoking request handler method: public java.lang.String org.netapsys.monitor.impl.MonitorServiceImpl.echo(java.lang.String)
962  INFO MonitorServiceImpl(51): echo(hello)
967 DEBUG AnnotationMethodHandlerExceptionResolver(132): Resolving exception from handler [org.netapsys.monitor.impl.MonitorServiceImpl@3f3cbbbf]: org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
&lt;/pre&gt;

&lt;p&gt;Le problème est que la classe &lt;strong&gt;java.util.String&lt;/strong&gt; n'est pas annotée avec JAXB, et plus précisement, qu'elle ne déclare pas l'annotation &lt;strong&gt;@XmlRootElement&lt;/strong&gt;. Or, c'est une condition nécessaire pour que le convertisseur &lt;strong&gt;Jaxb2RootElementHttpMessageConverter&lt;/strong&gt; fonctionne comme prévu. Une solution est de rajouter un convertisseur qui prend en charge les Strings. En modifiant la liste des convertisseurs côté serveur&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
&amp;lt;bean class=&amp;quot;org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter&amp;quot;&amp;gt;
  &amp;lt;property name=&amp;quot;messageConverters&amp;quot;&amp;gt;
    &amp;lt;list&amp;gt;
	&amp;lt;bean class=&amp;quot;org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter&amp;quot;/&amp;gt;
	&amp;lt;bean class=&amp;quot;org.springframework.http.converter.StringHttpMessageConverter&amp;quot;/&amp;gt;
     &amp;lt;/list&amp;gt;
  &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Le test est en succès&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
667 DEBUG RestTemplate(78): Created GET request for &amp;quot;http://localhost:8080/rstempx//do/monitoring/echo/hello&amp;quot;
668 DEBUG RestTemplate(520): Setting request Accept header to [text/plain, */*]
679 DEBUG RestTemplate(465): GET request for &amp;quot;http://localhost:8080/rstempx//do/monitoring/echo/hello&amp;quot; resulted in 200 (OK)
680 DEBUG RestTemplate(71): Reading [java.lang.String] as &amp;quot;text/plain&amp;quot; using [org.springframework.http.converter.StringHttpMessageConverter@49c4a5ec]
681  INFO SupportLoggingImpl(72): HELLO
PASSED: testHello
&lt;/pre&gt;

&lt;p&gt;Par défaut, le bean &lt;strong&gt;AnnotationMethodHandlerAdapter&lt;/strong&gt; est initialisé pour utiliser, entre autres, le convertisseur &lt;strong&gt;StringHttpMessageConverter&lt;/strong&gt;. Dans le cas où l'on souhaite mettre en oeuvre un convertisseur qui n'est pas initialisé par défaut, ce qui est le cas du convertisseur JAXB, il faut également spécifier les convertisseurs par défaut (au besoin). Il n'y a pas à ma connaissance de possibilité de rajouter un convertisseur à ceux déjà utilisés lors de la déclaration du bean, car la propriété &lt;strong&gt;messageConverters&lt;/strong&gt; est un tableau et non une collection.&lt;/p&gt;


&lt;p&gt;En fait, comme la requète est simple, on peut tester la méthode directement avec un navigateur&amp;nbsp;:
&lt;img src=&quot;http://blog.netapsys.fr/public/REST-JAXB/jaxb1.png&quot; alt=&quot;Invocation echo()&quot; style=&quot;display:block; margin:0 auto;&quot; /&gt;&lt;/p&gt;


&lt;p&gt;Au final dans mon cas, le résultat est correct. Cependant, on peut faire plusieurs remarques:&lt;/p&gt;


&lt;p&gt;L'invocation de la méthode echo() n'utilise à aucun moment JAXB. La déclaration du convertisseur JAXB est inutile. Conséquence on peut se contenter dans ce cas du bean par défaut&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
&amp;lt;bean class=&amp;quot;org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter&amp;quot;/&amp;gt;
&lt;/pre&gt;


&lt;p&gt;A contrario, si côté client, on attend une réponse sous la forme d'un fragment XML, le fait de retourner une chaine &quot;brute&quot; représente un cas particulier&amp;nbsp;: parfois je retourne du XML, parfois non... De plus, comme Spring fournit un convertisseur pour les String, il n'y a pas de problème. Mais si l'on considère des opérations comme&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
int getValue(final int v);
boolean getStatus(final boolean status);
&lt;/pre&gt;


&lt;p&gt;les invocations de ces méthodes ne fonctionnent pas et l'erreur est du même type que précédement&amp;nbsp;: 406 Not Acceptable.&lt;/p&gt;


&lt;p&gt;Il existe plusieurs solutions&amp;nbsp;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;1 Ecrire les convertisseurs pour tous les types utilisés dans les signatures des API.&lt;/li&gt;
&lt;li&gt;2 Reformuler toutes les API pour n'utiliser que des classes annotées avec JAXB.&lt;/li&gt;
&lt;li&gt;3 Utiliser un sérialiseur XML supportant les types simples.&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;La solution 1 présente l'inconvénient de ne pas répondre simplement à tous les cas, particulièrement dans le cas d'utilisation de collections dans les signatures de méthodes. J'ai choisi de ne pas l'explorer dans la suite de ce billet, car il ne me semble pas facile à généraliser.&lt;/p&gt;


&lt;h2&gt;Reformuler les API en utilisant JAXB&lt;/h2&gt;


&lt;p&gt;Ce n'est pas un problème en pratique, si l'utilisation des méthodes dans un contexte REST/JAXB est prévue lors de la conception de l'API. Au pire, il est toujours possible d'utiliser un Adaptateur au dessus de l'API existante. Par exemple l'utilisation de la classe ci-dessous permet de régler le cas des méthodes précédentes&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
@XmlRootElement(name=&amp;quot;response&amp;quot;)
@XmlAccessorType(XmlAccessType.FIELD)
public class SimpleResponse&amp;lt;T&amp;gt; 
{
  @XmlElement(name=&amp;quot;value&amp;quot;)
  private T value;

  public SimpleResponse() {
    // empty
  }

  public SimpleResponse(final T v) {
    this.value = v;
  }

  public T getValue() {
    return this.value;
  }

  public void setValue(final T value) {
    this.value = value;
  }
}
&lt;/pre&gt;


&lt;p&gt;La classe de service devient&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
@Controller
@RequestMapping(value=&amp;quot;/monitoring&amp;quot;)
public class MonitorServiceImpl
implements MonitorService
{

  final private Logger log = LoggerFactory.getLogger(MonitorServiceImpl.class);

  @ResponseBody
  @RequestMapping(value = &amp;quot;/echo/{valeur}&amp;quot;, method = RequestMethod.GET)
  public SimpleResponse&amp;lt;String&amp;gt; echo(@PathVariable final String valeur) {
    log.info(&amp;quot;echo(&amp;quot;+valeur+&amp;quot;)&amp;quot;);
    if (valeur == null) {
      return new SimpleResponse&amp;lt;String&amp;gt;(&amp;quot;NULL&amp;quot;);
    }
    return new SimpleResponse&amp;lt;String&amp;gt;(valeur.toUpperCase());
  }

  @ResponseBody
  @RequestMapping(value = &amp;quot;/value/{v}&amp;quot;, method = RequestMethod.GET)
  public SimpleResponse&amp;lt;Integer&amp;gt; getValue(@PathVariable final int v) {
    log.info(&amp;quot;value(&amp;quot;+v+&amp;quot;)&amp;quot;);
    return new SimpleResponse&amp;lt;Integer&amp;gt;(v * 2);
  }

  @ResponseBody
  @RequestMapping(value = &amp;quot;/status/{status}&amp;quot;, method = RequestMethod.GET)
  public SimpleResponse&amp;lt;Boolean&amp;gt; getStatus(@PathVariable final boolean status) {
    return new SimpleResponse&amp;lt;Boolean&amp;gt;(!status);
  }
}
&lt;/pre&gt;

&lt;p&gt;Il faut aussi modifier le client&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
public class MonitoringClientImpl 
implements MonitorService
{

  private RestTemplate restTemplate  = null;

  private String urlServeur = StringUtils.EMPTY;

  public SimpleResponse&amp;lt;Integer&amp;gt; getValue(int v) {
    return getRestTemplate().getForObject(
        getUrlServeur() + &amp;quot;/do/monitoring/value/{v}&amp;quot;,
        SimpleResponse.class,
        v);
  }

  public SimpleResponse&amp;lt;Boolean&amp;gt; getStatus(final boolean status) {
    return getRestTemplate().getForObject(
        getUrlServeur() + &amp;quot;/do/monitoring/status/{status}&amp;quot;,
        SimpleResponse.class,
        status);
  }

  public SimpleResponse&amp;lt;String&amp;gt; echo(final String reference) {
    return getRestTemplate().getForObject(
        getUrlServeur() + &amp;quot;/do/monitoring/echo/{reference}&amp;quot;,
        SimpleResponse.class,
        reference);
  }
}
&lt;/pre&gt;


&lt;p&gt;Et enfin le test&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
@Test
public void testEcho()() {
  final MonitorService service = (MonitorService) getBean(&amp;quot;monitor.client&amp;quot;);
  final String result = service.echo(&amp;quot;hello&amp;quot;).getValue();
  Assert.assertEquals(result, &amp;quot;HELLO&amp;quot;);
}

@Test
public void testValue() {
  final MonitorService service = (MonitorService) getBean(&amp;quot;monitor.client&amp;quot;);
  final int result = service.getValue(21).getValue();
  Assert.assertEquals(result, 42);
}

@Test
public void testStatus() {
  final MonitorService service = (MonitorService) getBean(&amp;quot;monitor.client&amp;quot;);
  final boolean result = service.getStatus(true).getValue();
  Assert.assertEquals(result, false);
}
&lt;/pre&gt;


&lt;p&gt;Cela devient plus compliqué lorsque les signatures des méthodes comportent des collections et que l'on ne peut pas radicalement modifier les contrats. C'est une situation fréquente dans le contexte de réalisation d'un web service au dessus d'un code existant que l'on ne souhaite pas modifier. Il faut écrire une classe dérivée d'&lt;strong&gt;XmlAdapter&lt;/strong&gt;, et &quot;saupoudrer&quot; le tout d'annotations judicieusement choisies. En gros pour une méthode retournant une collection &quot;simple&quot; style &lt;strong&gt;Vector&amp;lt;Integer&amp;gt;&lt;/strong&gt;, comme&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
@ResponseBody
@RequestMapping(value = &amp;quot;/list/{base}&amp;quot;, method = RequestMethod.GET)
public VectorIntegerResponse getList(@PathVariable int base) {
  log.info(&amp;quot;list(&amp;quot; + base + &amp;quot;)&amp;quot;);
  Vector&amp;lt;Integer&amp;gt; result = new Vector&amp;lt;Integer&amp;gt;();
  result.add(base);
  result.add(base * 2);
  result.add(base * 4);
  result.add(base * 8);
  return new VectorIntegerResponse(result);
}
&lt;/pre&gt;

&lt;p&gt;Cela revient à écrire 3 classes&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = &amp;quot;VectorSupportInteger&amp;quot;)
public class VectorSupportInteger 
{
    @XmlElement(name = &amp;quot;item&amp;quot;, required = true)
    private final List&amp;lt;Integer&amp;gt; chunck = new ArrayList&amp;lt;Integer&amp;gt;();

    public List&amp;lt;Integer&amp;gt; getChunck() {
        return this.chunck;
    }
}

public class VectorIntegerAdaptor 
extends XmlAdapter&amp;lt;VectorSupportInteger,Vector&amp;lt;Integer&amp;gt;&amp;gt;
{
	@Override
	public Vector&amp;lt;Integer&amp;gt; unmarshal(VectorSupportInteger v) throws Exception {
        final Vector&amp;lt;Integer&amp;gt; result = new Vector&amp;lt;Integer&amp;gt;();
        for ( final Integer e : v.getChunck() ) {
            result.add(e);
        }
        return result;
	}

	@Override
	public VectorSupportInteger marshal(Vector&amp;lt;Integer&amp;gt; v) throws Exception {
		final VectorSupportInteger result = new VectorSupportInteger();
        for ( final Integer e : v) {
            result.getChunck().add(e);
        }
		return result;
	}
}

@XmlRootElement(name=&amp;quot;response&amp;quot;)
@XmlAccessorType(XmlAccessType.FIELD)
public class VectorIntegerResponse 
{

	@XmlJavaTypeAdapter(VectorIntegerAdaptor.class)
	private Vector&amp;lt;Integer&amp;gt; value;

	public VectorIntegerResponse()
	{
		// vide;
	}
	
	public VectorIntegerResponse(final Vector&amp;lt;Integer&amp;gt; v)
	{
		value = v;
	}
	
	public Vector&amp;lt;Integer&amp;gt; getValue() {
		return value;
	}

	public void setValue(final Vector&amp;lt;Integer&amp;gt; value) {
		this.value = value;
	}
}
&lt;/pre&gt;


&lt;p&gt;Pour être complet voici le fragment XML résultant de l'invocation&amp;nbsp;:
&lt;img src=&quot;http://blog.netapsys.fr/public/REST-JAXB/jaxb2.png&quot; alt=&quot;invocation getListe()&quot; style=&quot;display:block; margin:0 auto;&quot; /&gt;&lt;/p&gt;


&lt;p&gt;Il est possible de limiter le nombre de classes en utilisant la généricité de façon plus poussée, mais cela ne change pas vraiment la situation&amp;nbsp;: pour un type Collection retourné par une méthode, il faut fournir 3 classes de support à la liaison avec JAXB.
Partant de ce constat, les arguments des tenants d'une approche &quot;schéma en premier&quot; plutôt que &quot;code en premier&quot; commencent à prendre du sens. Dans une approche &quot;schéma en premier&quot;, les classes d'adaptateurs sont générées à partir du schéma et par conséquent la complexité d'écriture est masquée. Dans l'approche &quot;code en premier&quot; précédente on utilise JAXB à l'envers. En pratique, dans un contexte de code existant et avec la motivation de ne pas &quot;trop&quot; faire évoluer les contrats d'interfaces, il n'est pas facile de faire autrement.&lt;/p&gt;


&lt;h2&gt;Mise en oeuvre d'un autre sérialiseur XML.&lt;/h2&gt;


&lt;p&gt;Pour diminuer la complexité induite par JAXB, le mieux est d'éviter de l'utiliser dans ce contexte. Une solution de remplacement est de mettre en oeuvre &lt;a href=&quot;http://xstream.codehaus.org/&quot; hreflang=&quot;en&quot;&gt;XStream&lt;/a&gt; pour la sérialisation/désérialisation des fragments XML. C'est assez facile à mettre en oeuvre car Spring fournit tout ce qu'il faut. On commence par rétablir les méthodes du service dans leurs versions originales&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
@Controller
@RequestMapping(value=&amp;quot;/monitoring&amp;quot;)
public class MonitorServiceImpl
implements MonitorService
{

    final private Logger log = LoggerFactory.getLogger(MonitorServiceImpl.class);

    @ResponseBody
    @RequestMapping(value = &amp;quot;/echo/{valeur}&amp;quot;, method = RequestMethod.GET)
    public String echo(@PathVariable final String valeur) {
    	log.info(&amp;quot;echo(&amp;quot;+valeur+&amp;quot;)&amp;quot;);
    	if (valeur == null) {
    		return &amp;quot;NULL&amp;quot;;
    	}
    	return valeur.toUpperCase();
    }

    @ResponseBody
    @RequestMapping(value = &amp;quot;/value/{v}&amp;quot;, method = RequestMethod.GET)
    public int getValue(@PathVariable final int v) {
    	log.info(&amp;quot;value(&amp;quot;+v+&amp;quot;)&amp;quot;);
    	return v * 2;
    }
    
    @ResponseBody
    @RequestMapping(value = &amp;quot;/status/{status}&amp;quot;, method = RequestMethod.GET)
    public boolean getStatus(@PathVariable final boolean status) {
    	return !status;
    }
    
    @ResponseBody
    @RequestMapping(value = &amp;quot;/list/{base}&amp;quot;, method = RequestMethod.GET)
    public Vector&amp;lt;Integer&amp;gt; getList(@PathVariable int base) {
    	log.info(&amp;quot;list(&amp;quot; + base + &amp;quot;)&amp;quot;);
    	Vector&amp;lt;Integer&amp;gt; result = new Vector&amp;lt;Integer&amp;gt;();
    	result.add(base);
    	result.add(base * 2);
    	result.add(base * 4);
    	result.add(base * 8);
    	return result;
    }
}
&lt;/pre&gt;


&lt;p&gt;Ensuite, on déclare le bean XStream et on modifie la configuration de la servlet monitor pour qu'elle utilise le marshaller XStream&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
&amp;lt;bean id=&amp;quot;xml.xstream.marshaller&amp;quot; class=&amp;quot;org.springframework.oxm.xstream.XStreamMarshaller&amp;quot;/&amp;gt;

&amp;lt;bean class=&amp;quot;org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter&amp;quot;&amp;gt;
  &amp;lt;property name=&amp;quot;messageConverters&amp;quot;&amp;gt;
    &amp;lt;list&amp;gt;
      &amp;lt;bean class=&amp;quot;org.springframework.http.converter.xml.MarshallingHttpMessageConverter&amp;quot;&amp;gt;
         &amp;lt;property name=&amp;quot;marshaller&amp;quot; ref=&amp;quot;xml.xstream.marshaller&amp;quot;/&amp;gt;
         &amp;lt;property name=&amp;quot;unmarshaller&amp;quot; ref=&amp;quot;xml.xstream.marshaller&amp;quot;/&amp;gt;
      &amp;lt;/bean&amp;gt;
    &amp;lt;/list&amp;gt;
  &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&amp;quot;monitor.service.http&amp;quot;  class=&amp;quot;org.netapsys.monitor.impl.MonitorServiceImpl&amp;quot;/&amp;gt;
&lt;/pre&gt;


&lt;p&gt;Côté client, on modifie le RestTemplate de manière analogue&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
&amp;lt;bean id=&amp;quot;monitor.client&amp;quot; class=&amp;quot;org.netapsys.monitor.client.MonitoringClientImpl&amp;quot;&amp;gt;
  &amp;lt;property name=&amp;quot;restTemplate&amp;quot; ref=&amp;quot;rstemp.rest.template&amp;quot;/&amp;gt;
  &amp;lt;property name=&amp;quot;urlServeur&amp;quot; value=&amp;quot;${rstemp.url}&amp;quot;/&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&amp;quot;rstemp.rest.template&amp;quot; class=&amp;quot;org.springframework.web.client.RestTemplate&amp;quot;&amp;gt;
  &amp;lt;property name=&amp;quot;messageConverters&amp;quot;&amp;gt;
    &amp;lt;list&amp;gt;
      &amp;lt;bean id=&amp;quot;messageConverter&amp;quot; class=&amp;quot;org.springframework.http.converter.xml.MarshallingHttpMessageConverter&amp;quot;&amp;gt;
         &amp;lt;property name=&amp;quot;marshaller&amp;quot; ref=&amp;quot;xml.xstream.marshaller&amp;quot; /&amp;gt;
         &amp;lt;property name=&amp;quot;unmarshaller&amp;quot; ref=&amp;quot;xml.xstream.marshaller&amp;quot; /&amp;gt;
      &amp;lt;/bean&amp;gt;
    &amp;lt;/list&amp;gt;
  &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;


&lt;p&gt;Le code du client devient&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
public class MonitoringClientImpl 
implements MonitorService
{
   private RestTemplate restTemplate  = null;
    
   private String urlServeur = StringUtils.EMPTY;

   public int getValue(int v) {
     return getRestTemplate().getForObject(
        getUrlServeur() + &amp;quot;/do/monitoring/value/{v}&amp;quot;,
        Integer.class,
        v);
   }

   public boolean getStatus(final boolean status) {
     return getRestTemplate().getForObject(
        getUrlServeur() + &amp;quot;/do/monitoring/status/{status}&amp;quot;,
        Boolean.class,
        status);
   }

   public String echo(final String reference) {
     return getRestTemplate().getForObject(
        getUrlServeur() + &amp;quot;/do/monitoring/echo/{reference}&amp;quot;,
        String.class,
        reference);
   }

   public Vector&amp;lt;Integer&amp;gt; getList(int base) {
     return getRestTemplate().getForObject(
        getUrlServeur() + &amp;quot;/do/monitoring/list/{base}&amp;quot;,
        Vector.class,
        base);
   }

}
&lt;/pre&gt;


&lt;p&gt;Enfin les résultats d'invocation&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
http://localhost:8080/rstempx/do/monitoring/echo/Hello
résultat =&amp;gt;    &amp;lt;string&amp;gt;HELLO&amp;lt;/string&amp;gt;
http://localhost:8080/rstempx/do/monitoring/value/21	  
résultat =&amp;gt;    &amp;lt;int&amp;gt;42&amp;lt;/int&amp;gt;
http://localhost:8080/rstempx/do/monitoring/status/false   
résultat =&amp;gt;     &amp;lt;boolean&amp;gt;true&amp;lt;/boolean&amp;gt;
http://localhost:8080/rstempx/do/monitoring/list/21
résultat =&amp;gt;
&amp;lt;vector&amp;gt;
  &amp;lt;int&amp;gt;21&amp;lt;/int&amp;gt;
  &amp;lt;int&amp;gt;42&amp;lt;/int&amp;gt;
  &amp;lt;int&amp;gt;84&amp;lt;/int&amp;gt;
  &amp;lt;int&amp;gt;168&amp;lt;/int&amp;gt;
&amp;lt;/vector&amp;gt;
&lt;/pre&gt;


&lt;p&gt;La solution demande moins de code et permet de respecter les contrats existants, comparativement à la solution utilisant JAXB, elle est beaucoup plus simple.&lt;/p&gt;


&lt;h2&gt;Conclusion&lt;/h2&gt;


&lt;p&gt;Si l'utilisation RestTemplate est une bonne solution pour écrire un web-service REST, JAXB ne rime pas toujours avec simplicité, surtout dans une approche &quot;code en premier&quot; avec du code hérité. Il me semble préférable dans ce cas d'utiliser un sérialiseur XStream pour le marshalling/unmarshalling des données. Il faut noter que la solution RestTemplate présentée n'a pas que des avantages par rapport à une solution JAXRS. Par exemple, elle ne permet pas de fournir un descriptif WADL des opérations utilisables.&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.netapsys.fr/index.php/post/2011/02/23/Web-service-REST-avec-JAXB.-Simple-vraiment#comment-form</comments>
      <wfw:comment>http://blog.netapsys.fr/index.php/post/2011/02/23/Web-service-REST-avec-JAXB.-Simple-vraiment#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.netapsys.fr/index.php/feed/atom/comments/226</wfw:commentRss>
      </item>
    
  <item>
    <title>Apache Camel - Transformation des données d'un fichier texte</title>
    <link>http://blog.netapsys.fr/index.php/post/2011/02/13/Apache-Camel-Transformation-des-donn%C3%A9es-d-un-fichier-texte</link>
    <guid isPermaLink="false">urn:md5:401006d1eea97520aa8f8ecd3559179c</guid>
    <pubDate>Sun, 13 Feb 2011 16:20:00 +0100</pubDate>
    <dc:creator>Jean-Baptiste Defard</dc:creator>
        <category>Java J2EE</category>
        <category>Camel</category><category>Java</category>    
    <description>&lt;p&gt;Une erreur d'appréciation fréquente concernant &lt;a href=&quot;http://camel.apache.org&quot; hreflang=&quot;en&quot;&gt;Apache Camel&lt;/a&gt; est de croire que son utilisation est réservée à des contextes client/serveur du style mise en oeuvre de Webservice, ou d'une messagerie JMS... Il est vrai que c'est pour ce genre de besoins que bien souvent on l'utilise la première fois. A l'usage, on découvre rapidement qu'il permet de faire beaucoup d'autres choses sympathiques.&lt;/p&gt;    &lt;p&gt;Combien de fois ai-je écrit un bout de code qui lit un fichier de données et transforme celles-ci pour utilisation dans une application? En fait, je ne sais pas exactement, mais trop souvent, c'est certain, je l'avoue, C'est le genre de code que j'ai en de multiples exemplaires, quelque part, &quot;juste quelques adaptations&quot; (comprendre un bon copié/collé) et hop&amp;nbsp;! Voilà, c'est fait...&lt;/p&gt;


&lt;h2&gt;Pour commencer&lt;/h2&gt;


&lt;p&gt;Grâce au composant file et aux formats de données existants, l'utilisation de Camel fournit une solution plus concise.
Par exemple, la lecture ligne par ligne d'un fichier texte et l'affichage du contenu dans les logs s'écrit avec la route:&lt;/p&gt;
&lt;pre&gt;
from(&amp;quot;file:/tmp/csv?noop=true&amp;amp;fileName=test.csv&amp;quot;)
.split(body().tokenize(&amp;quot;
&amp;quot;))
.log(&amp;quot;ligne: ${body}&amp;quot;).end();
&lt;/pre&gt;


&lt;p&gt;Autrement dit&amp;nbsp;: lire le fichier /tmp/csv/test.csv, éclater son contenu en morceaux en cherchant les fins de lignes, écrire le tout sur la sortie de log et c'est fini. Bon d'accord, il n'y a pas vraiment de traitement des données, mais cela reste une bonne base.&lt;/p&gt;


&lt;h2&gt;Utilisation du format Bindy&lt;/h2&gt;


&lt;p&gt;Apache Camel est plutôt bien outillé pour réaliser des transformations de données. Après un petit tour dans la documentation, je peux faire en sorte que Camel convertisse les lignes. Dans mon exemple, le fichier test.csv contient des valeurs séparées par des virgules&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
nexistepas,com,2020-01-03,OWN-3121-4959
nomdomaine,fr,2013-11-14,OWN-4242-4431
&lt;/pre&gt;


&lt;p&gt;Je modifie ma route pour insérer la conversion de chaque ligne en une liste de valeurs correspondant à chacune des colonnes&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
from(&amp;quot;file:/tmp/csv?noop=true&amp;amp;fileName=test.csv&amp;quot;)
.split(body().tokenize(&amp;quot;
&amp;quot;))
.log(&amp;quot;ligne: ${body}&amp;quot;)
.unmarshal().csv()
.log(&amp;quot;colonnes: ${body}&amp;quot;)
.end();
&lt;/pre&gt;


&lt;p&gt;L'exécution de cette route avec le fichier de test donne comme résultat dans les logs&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
21,737  INFO route1(212): ligne: nexistepas,com,2020-01-01,OWN-3121-4999
21,745  INFO route1(212): colonnes: [nexistepas, com, 2020-01-01, OWN-3121-4999]
21,746  INFO route1(212): ligne: nomdomaine,fr,2013-10-11,OWN-4413-4431
21,747  INFO route1(212): colonnes: [nomdomaine, fr, 2013-10-11, OWN-4413-4431]
&lt;/pre&gt;

&lt;p&gt;On obtient bien une liste de String contenant les valeurs des colonnes. Il ne reste plus qu'à ajouter un bean de traitement qui utilise les valeurs. Par exemple&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
public class InportSimple
{
    Logger log = LoggerFactory.getLogger(InportSimple.class);

    public void importer(final List&amp;lt;String&amp;gt; colonnes)
    {
        for( final String colonne: colonnes) {
            this.log.info(&amp;quot;c:&amp;quot; + colonne);
        }
    }
}
&lt;/pre&gt;


&lt;p&gt;Comme le bean ne contient qu'une seule méthode et n'a besoin de rien d'autre qu'un Logger, il est possible de le créer directement. Dans des cas plus complexes, Camel sait  injecter le bean depuis Spring par exemple. De même, il pourrait implémenter &lt;a href=&quot;http://camel.apache.org/processor.html&quot; hreflang=&quot;en&quot;&gt;Processor&lt;/a&gt;, ce qui a l'avantage de donner l'accès à l'objet &lt;a href=&quot;http://camel.apache.org/exchange.html&quot; hreflang=&quot;en&quot;&gt;Exchange&lt;/a&gt; courant. Pour la mise en oeuvre du bean, il faut modifier la route, qui devient&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
from(&amp;quot;file:/tmp/csv?noop=true&amp;amp;fileName=test.csv&amp;quot;)
.split(body().tokenize(&amp;quot;
&amp;quot;))
.log(&amp;quot;ligne: ${body}&amp;quot;)
.unmarshal().csv()
.log(&amp;quot;colonnes: ${body}&amp;quot;)
.bean(InportSimple.class)
.end();
&lt;/pre&gt;

&lt;p&gt;La trace montre que l'on obtient bien le comportement attendu&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
45,061  INFO route1(212): ligne: nexistepas,com,2020-01-01,OWN-3121-4999
45,067  INFO route1(212): colonnes: [nexistepas, com, 2020-01-01, OWN-3121-4999]
45,069  INFO InportSimple(27): c:nexistepas
45,069  INFO InportSimple(27): c:com
45,069  INFO InportSimple(27): c:2020-01-01
45,069  INFO InportSimple(27): c:OWN-3121-4999
45,071  INFO route1(212): ligne: nomdomaine,fr,2013-10-11,OWN-4413-4431
45,072  INFO route1(212): colonnes: [nomdomaine, fr, 2013-10-11, OWN-4413-4431]
45,073  INFO InportSimple(27): c:nomdomaine
45,073  INFO InportSimple(27): c:fr
45,073  INFO InportSimple(27): c:2013-10-11
45,073  INFO InportSimple(27): c:OWN-4413-4431
&lt;/pre&gt;

&lt;p&gt;Il est possible d'améliorer cette solution pour obtenir directement la conversion d'une ligne en un objet. Dans le cas d'un fichier CSV, cela passe par l'utilisation du format de données &lt;a href=&quot;http://camel.apache.org/bindy.html&quot; hreflang=&quot;en&quot;&gt;Bindy&lt;/a&gt;. Après l'inclusion de l'artefact camel-bindy dans le classpath, il faut écrire un bean POJO correspondant aux colonnes. La liaison entre les données et les objets est spécifiée avec des annotations&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
package org.netapsys.csv.descripteur;

import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
import org.apache.camel.dataformat.bindy.annotation.DataField;

@CsvRecord(separator = &amp;quot;,&amp;quot;, crlf = &amp;quot;UNIX&amp;quot;)
public class Descripteur
{
    @DataField(pos = 1)
    private String nom = &amp;quot;&amp;quot;;

    @DataField(pos = 2)
    private String zone = &amp;quot;&amp;quot;;

    @DataField(pos = 3, pattern = &amp;quot;yyyy-MM-dd&amp;quot;)
    private Date date = null;

    @DataField(pos = 4,required = false)
    private String titulaire = &amp;quot;&amp;quot;;

    // Omission des accesseurs .../...

    // Juste pour faciliter la lecture des traces.
    @Override
    public String toString()
    {
        return &amp;quot;Descripteur [nom=&amp;quot; + getNom() + &amp;quot;, zone=&amp;quot; + getZone() + &amp;quot;, date=&amp;quot; + getDate()
                + &amp;quot;, titulaire=&amp;quot; + getTitulaire() + &amp;quot;]&amp;quot;;
    }
}
&lt;/pre&gt;

&lt;p&gt;Reste ensuite à modifier la route, pour demander la conversion&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
from(&amp;quot;file:/tmp/csv?noop=true&amp;amp;fileName=test.csv&amp;quot;)
.split(body().tokenize(&amp;quot;
&amp;quot;))
.log(&amp;quot;ligne: ${body}&amp;quot;)
.unmarshal().bindy(BindyType.Csv, &amp;quot;org.netapsys.csv.descripteur&amp;quot;)
.bean(ImportBindy.class).end();
&lt;/pre&gt;


&lt;p&gt;On utilise un autre bean pour traiter chaque objet car le format Bindy retourne une liste de dictionnaires d'objets&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
public class ImportBindy
{
    Logger log = LoggerFactory.getLogger(ImportBindy.class);

    public void importer(final List&amp;lt;HashMap&amp;lt;String, Descripteur&amp;gt;&amp;gt; donnees)
    {
        for (final HashMap&amp;lt;String,Descripteur&amp;gt; dictionnaire : donnees) {
            for (final String cle : dictionnaire.keySet()) {
                    this.log.info(&amp;quot;toString: &amp;quot; + dictionnaire.get(cle));
            }
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;La trace d'exécution permet de vérifier que c'est bien la méthode toString() de la classe Descripteur qui est finalement appelée&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
55,250  INFO route1(212): ligne: nomdomaine,fr,2013-10-11,OWN-4413-4431
55,251 DEBUG BindyCsvDataFormat(178): Size of the record splitted : 4
55,251 DEBUG BindyCsvFactory(177): Pos : 1, Data : nomdomaine, Field type : class java.lang.String
55,251 DEBUG BindyCsvFactory(177): Pos : 2, Data : fr, Field type : class java.lang.String
55,252 DEBUG BindyCsvFactory(177): Pos : 3, Data : 2013-10-11, Field type : class java.util.Date
55,252 DEBUG BindyCsvFactory(177): Pos : 4, Data : OWN-4413-4431, Field type : class java.lang.String
55,252 DEBUG BindyCsvFactory(213): Counter mandatory fields : 0
55,252 DEBUG BindyCsvDataFormat(191): Graph of objects created : {org.netapsys.csv.descripteur.Descripteur=Descripteur [nom=nomdomaine, zone=fr, date=Fri Oct 11 00:00:00 CEST 2013, titulaire=OWN-4413-4431]}
55,253  INFO ImportBindy(30): toString: Descripteur [nom=nomdomaine, zone=fr, date=Fri Oct 11 00:00:00 CEST 2013, titulaire=OWN-4413-4431]
&lt;/pre&gt;

&lt;p&gt;Le code présenté n'est qu'un exemple de ce qu'il est possible de réaliser avec Apache-Camel. Le même type d'approche est utilisable avec d'autre &lt;a href=&quot;http://camel.apache.org/data-format.html&quot; hreflang=&quot;en&quot;&gt;formats de données&lt;/a&gt;, et il existe d'autre approches envisageables pour la transformation, notamment l'utilisation du pattern &lt;a href=&quot;http://camel.apache.org/message-translator.html&quot; hreflang=&quot;en&quot;&gt;Message Translator&lt;/a&gt;.&lt;/p&gt;


&lt;h2&gt;Le cas des gros fichiers.&lt;/h2&gt;


&lt;p&gt;A l'utilisation, le code précédent présente un défaut, qui n'apparaît que lors du traitement de &quot;gros&quot; fichiers. Dans ce cas, il est probable que l'on rencontre des erreurs de saturations mémoires. Par exemple, avec un fichier de 4 Mo environ, on obtient avec Java VisualVM le graphe mémoire suivant&amp;nbsp;:
&lt;img src=&quot;http://blog.netapsys.fr/public/memoire_camel/memory-csv-nostream.png&quot; alt=&quot;Evolution mémoire sans streaming&quot; style=&quot;display:block; margin:0 auto;&quot; /&gt;&lt;/p&gt;


&lt;p&gt;La consommation mémoire est beaucoup trop importante par rapport au traitement. C'est une conséquence de la façon dont le splitter fonctionne. Heureusement, il est possible de lire le fichier d'entrée par morceaux en mode &quot;streaming&quot;. En ajoutant streaming() à la route&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
from(&amp;quot;file:/tmp/csv?noop=true&amp;amp;fileName=test.csv&amp;quot;)
.split(body().tokenize(&amp;quot;
&amp;quot;))((/public/memoire_camel/memory-csv-nostream.png|Evolution mémoire sans streaming|C))
.streaming()
.log(&amp;quot;ligne: ${body}&amp;quot;)
.unmarshal().bindy(BindyType.Csv, &amp;quot;org.netapsys.csv.descripteur&amp;quot;)
.bean(ImportBindy.class).end();
&lt;/pre&gt;


&lt;p&gt;Le graphe montre une diminution très significative de la mémoire&amp;nbsp;:
&lt;img src=&quot;http://blog.netapsys.fr/public/memoire_camel/memory-csv2-stream1.png&quot; alt=&quot;Evolution mémoire avec streaming&quot; style=&quot;display:block; margin:0 auto;&quot; /&gt;&lt;/p&gt;


&lt;p&gt;Conclusion&amp;nbsp;: l'utilisation du streaming est toujours préférable dès que le volume des données à traiter est un peu important. De plus, en mode streaming, la charge CPU est moindre car le gestionnaire mémoire est moins sollicité.&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.netapsys.fr/index.php/post/2011/02/13/Apache-Camel-Transformation-des-donn%C3%A9es-d-un-fichier-texte#comment-form</comments>
      <wfw:comment>http://blog.netapsys.fr/index.php/post/2011/02/13/Apache-Camel-Transformation-des-donn%C3%A9es-d-un-fichier-texte#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.netapsys.fr/index.php/feed/atom/comments/220</wfw:commentRss>
      </item>
    
  <item>
    <title>Apache Camel - Configuration des timeouts HTTP</title>
    <link>http://blog.netapsys.fr/index.php/post/2011/01/18/Apache-Camel-Configuration-des-timeouts-HTTP</link>
    <guid isPermaLink="false">urn:md5:b3469c128db5872ca0016654ffff58b7</guid>
    <pubDate>Tue, 18 Jan 2011 12:35:00 +0100</pubDate>
    <dc:creator>Jean-Baptiste Defard</dc:creator>
        <category>Java J2EE</category>
        <category>camel</category><category>java</category><category>spring</category>    
    <description>    &lt;p&gt;La mise en œuvre d'une requête HTTP avec Camel est assez simple. Après avoir ajouté le composant &lt;strong&gt;camel-http.jar&lt;/strong&gt; au classpath, la route suivante fait une requête de recherche sur Google.&lt;/p&gt;

&lt;pre&gt;
from(&amp;quot;direct:start&amp;quot;).
to(&amp;quot;http://www.google.com/search?q=netapsys).to(&amp;quot;mock:result&amp;quot;);
&lt;/pre&gt;


&lt;p&gt;Un simple appel dans un test Camel permet de s'assurer que tout s'exécute comme prévu&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
@Test
public void test()
throws Exception
{
    this.template.sendBody(&amp;quot;direct:start&amp;quot;, null);
.../...
}
&lt;/pre&gt;


&lt;p&gt;Le composant s'appuie sur la librairie &lt;strong&gt;commons-httpclient-3.x.jar&lt;/strong&gt; pour l'implémentation. Pour la configuration des temporisations HTTP, un petit tour dans la &lt;a href=&quot;http://camel.apache.org/http.html&quot;&gt;documentation&lt;/a&gt;, montre qu'il existe (au moins) deux solutions&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Soit, on passe en paramètre la valeur du délai sur la route. Dans ce cas, la route précédente devient&amp;nbsp;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;
from(&amp;quot;direct:start&amp;quot;).
to(&amp;quot;http://www.google.com/search?q=netapsys&amp;amp;httpClient.soTimeout=5000&amp;quot;).to(&amp;quot;mock:result&amp;quot;);
&lt;/pre&gt;


&lt;ul&gt;
&lt;li&gt;Soit, on configure le composant directement en injectant un objet &lt;strong&gt;ClientConnectionManager&lt;/strong&gt; correctement paramétré. Si,  comme c'est mon cas, vous utilisez Spring comme registre Camel, il suffit de déclarer un bean correspondant au composant et de le paramétrer. Par analogie avec&amp;nbsp;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;
final MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
final HttpConnectionManagerParams params = connectionManager.getParams();
params.setConnectionTimeout(6000);
connectionManager.setParams(params);
HttpClient client = new HttpClient(connectionManager));
&lt;/pre&gt;

&lt;pre&gt;&lt;/pre&gt;


&lt;p&gt;Cela revient dans un contexte Spring, aux déclarations&amp;nbsp;:&lt;/p&gt;


&lt;pre&gt;
&amp;lt;bean id=&amp;quot;http.connection.manager.params&amp;quot;
	class=&amp;quot;org.apache.commons.httpclient.params.HttpConnectionManagerParams&amp;quot;&amp;gt;
    &amp;lt;property name=&amp;quot;connectionTimeout&amp;quot; value=&amp;quot;6000&amp;quot; /&amp;gt;
    &amp;lt;property name=&amp;quot;soTimeout&amp;quot; value=&amp;quot;4000&amp;quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&amp;quot;http.composant&amp;quot; class=&amp;quot;org.apache.camel.component.http.HttpComponent&amp;quot;&amp;gt;
    &amp;lt;property name=&amp;quot;httpConnectionManager&amp;quot;&amp;gt;
	&amp;lt;bean
		class=&amp;quot;org.apache.commons.httpclient.MultiThreadedHttpConnectionManager&amp;quot;&amp;gt;
		&amp;lt;property name=&amp;quot;params&amp;quot; ref=&amp;quot;http.connection.manager.params&amp;quot; /&amp;gt;
	&amp;lt;/bean&amp;gt;
    &amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;


&lt;p&gt;Il y a même plus court et surtout plus souple. La configuration précédente présente l'inconvénient de fixer les valeurs de temporisation pour l'ensemble des routes utilisant le composant HTTP. Pour une configuration &quot;par route&quot; à la demande, on peut utiliser le paramétrage par référence. Avec les déclarations Spring suivantes&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
&amp;lt;bean id=&amp;quot;clientConnectionManager2&amp;quot;
	class=&amp;quot;org.apache.commons.httpclient.MultiThreadedHttpConnectionManager&amp;quot;&amp;gt;
	&amp;lt;property name=&amp;quot;params&amp;quot;&amp;gt;
		&amp;lt;bean
			class=&amp;quot;org.apache.commons.httpclient.params.HttpConnectionManagerParams&amp;quot;&amp;gt;
			&amp;lt;property name=&amp;quot;connectionTimeout&amp;quot; value=&amp;quot;5000&amp;quot; /&amp;gt;
			&amp;lt;property name=&amp;quot;soTimeout&amp;quot; value=&amp;quot;2000&amp;quot; /&amp;gt;
		&amp;lt;/bean&amp;gt;
	&amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&amp;quot;clientConnectionManager3&amp;quot;
	class=&amp;quot;org.apache.commons.httpclient.MultiThreadedHttpConnectionManager&amp;quot;&amp;gt;
	&amp;lt;property name=&amp;quot;params&amp;quot;&amp;gt;
		&amp;lt;bean
			class=&amp;quot;org.apache.commons.httpclient.params.HttpConnectionManagerParams&amp;quot;&amp;gt;
			&amp;lt;property name=&amp;quot;connectionTimeout&amp;quot; value=&amp;quot;10000&amp;quot; /&amp;gt;
			&amp;lt;property name=&amp;quot;soTimeout&amp;quot; value=&amp;quot;12000&amp;quot; /&amp;gt;
		&amp;lt;/bean&amp;gt;
	&amp;lt;/property&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/pre&gt;


&lt;p&gt;Il suffit de configurer les routes avec la référence du bean souhaité&amp;nbsp;:&lt;/p&gt;

&lt;pre&gt;
from(&amp;quot;direct:start1&amp;quot;).
to(&amp;quot;http://www.google.com/search?q=netapsys&amp;amp;clientConnectionManager=#clientConnectionManager2&amp;quot;).to(&amp;quot;mock:result&amp;quot;);

from(&amp;quot;direct:start2&amp;quot;).
to(&amp;quot;http://www.google.com/search?q=netapsys&amp;amp;clientConnectionManager=#clientConnectionManager3&amp;quot;).to(&amp;quot;mock:result&amp;quot;);
&lt;/pre&gt;


&lt;p&gt;Il existe un composant Camel utilisant la version 4.x d'HttpClient que je n'ai pas encore eu l'occasion d'utiliser. La configuration doit être probablement légèrement différente.&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.netapsys.fr/index.php/post/2011/01/18/Apache-Camel-Configuration-des-timeouts-HTTP#comment-form</comments>
      <wfw:comment>http://blog.netapsys.fr/index.php/post/2011/01/18/Apache-Camel-Configuration-des-timeouts-HTTP#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.netapsys.fr/index.php/feed/atom/comments/207</wfw:commentRss>
      </item>
    
  <item>
    <title>Lightning pour Thunderbird 3.1.x sous Linux 64 bits.</title>
    <link>http://blog.netapsys.fr/index.php/post/2011/01/17/Lightning-pour-Thunderbird-3.1.x-sous-Linux-64-bits.</link>
    <guid isPermaLink="false">urn:md5:d7d2dc673719c2586dc2e246455eeb0c</guid>
    <pubDate>Mon, 17 Jan 2011 15:28:00 +0100</pubDate>
    <dc:creator>Jean-Baptiste Defard</dc:creator>
        <category>Infrastructure</category>
        <category>linux</category><category>Thunderbird</category>    
    <description>    &lt;p&gt;Suite à une mise à jour de Thunderbird (en version 3.1.7 chez moi), je n'ai plus accès à mes calendriers, car le composant Lightning refuse de se lancer. Après recherche, je n'ai pas trouvé une version toute prète du composant pour mon Ubuntu 64 bits. La solution restante et qui marche ;-) est de compiler le composant en local.
Voila la procédure.&lt;/p&gt;


&lt;p&gt;Installation des paquets nécessaires&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
sudo apt-get build-dep thunderbird firefox
sudo apt-get install mercurial libasound2-dev libcurl4-openssl-dev libnotify-dev libiw-dev autoconf2.13 cvs
sudo apt-get install yasm mesa-common-dev
&lt;/pre&gt;


&lt;p&gt;Récupération des sources&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
mkdir mozilla
cd mozilla
hg clone http://hg.mozilla.org/releases/comm-1.9.2/ src
cd src
python client.py checkout
&lt;/pre&gt;

&lt;p&gt;Dans le répertoire src, il faut créer le fichier .mozconfig avec le contenu suivant&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
ac_add_options --enable-application=mail
ac_add_options --enable-calendar
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/objdir-tb-release
&lt;/pre&gt;

&lt;p&gt;Et enfin, construire la chose&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;make -f client.mk&lt;/pre&gt;


&lt;p&gt;Après un bon café et un roulement de tambour, les binaires sont dans&amp;nbsp;: &lt;code&gt;src/objdir-tb-release/mozilla/dist&lt;/code&gt; Il ne reste plus qu'a installer les .xpi avec le gestionnaire de modules complémentaires de Thunderbird (Menu: /outil/modules complémentaires/ installer).&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.netapsys.fr/index.php/post/2011/01/17/Lightning-pour-Thunderbird-3.1.x-sous-Linux-64-bits.#comment-form</comments>
      <wfw:comment>http://blog.netapsys.fr/index.php/post/2011/01/17/Lightning-pour-Thunderbird-3.1.x-sous-Linux-64-bits.#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.netapsys.fr/index.php/feed/atom/comments/206</wfw:commentRss>
      </item>
    
  <item>
    <title>L'intégration continue au JUG de Tours</title>
    <link>http://blog.netapsys.fr/index.php/post/2008/08/30/Lintegration-continue-au-JUG-de-Tours</link>
    <guid isPermaLink="false">urn:md5:eba72eb8a98d4362b5fb34e8ee604165</guid>
    <pubDate>Sat, 30 Aug 2008 12:17:00 +0200</pubDate>
    <dc:creator>Jean-Baptiste Defard</dc:creator>
        <category>Java J2EE</category>
        <category>hudson</category><category>intégration continue</category><category>JUG</category><category>qualité</category><category>sonar</category>    
    <description>    &lt;p&gt;Pour sa rentrée, le JUG de Tours organise le 10 septembre une soirée autour de l'&lt;a href=&quot;http://www.toursjug.org/rencontre/20080910/presentation/&quot; hreflang=&quot;fr&quot;&gt;intégration continue&lt;/a&gt; que j'aurai le plaisir d'animer. La présentation se veut très pratique et orientée retour d'expériences. Après un rappel des principes, je présenterai l'infrastructure d'intégration articulée autour d'&lt;a href=&quot;https://hudson.dev.java.net/&quot; hreflang=&quot;en&quot;&gt;Hudson&lt;/a&gt; que nous utilisons chez Netapsys. Une dernière partie traitera de l'intégration de &lt;a href=&quot;http://sonar.codehaus.org/&quot; hreflang=&quot;en&quot;&gt;Sonar&lt;/a&gt; comme un exemple de la contribution potentielle de l'environnement à la mise en oeuvre d'une démarche d'assurance qualité.&lt;/p&gt;


&lt;p&gt;Si vous êtes intéressés par cette discussion, dans une ambiance détendue et conviviale, il ne vous reste plus qu'a vous &lt;a href=&quot;http://www.toursjug.org/rencontre/20080910/presentation/&quot; hreflang=&quot;fr&quot;&gt;inscrire&lt;/a&gt; ;-).&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.netapsys.fr/index.php/post/2008/08/30/Lintegration-continue-au-JUG-de-Tours#comment-form</comments>
      <wfw:comment>http://blog.netapsys.fr/index.php/post/2008/08/30/Lintegration-continue-au-JUG-de-Tours#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.netapsys.fr/index.php/feed/atom/comments/37</wfw:commentRss>
      </item>
    
  <item>
    <title>Nouvelle version de Sonar</title>
    <link>http://blog.netapsys.fr/index.php/post/2008/06/09/Nouvelle-version-de-Sonar</link>
    <guid isPermaLink="false">urn:md5:e1be1d86345a1bdc6ca33a9c9cfae99a</guid>
    <pubDate>Mon, 09 Jun 2008 14:22:00 +0200</pubDate>
    <dc:creator>Jean-Baptiste Defard</dc:creator>
        <category>Java J2EE</category>
        <category>java</category><category>qualité</category><category>sonar</category>    
    <description>    &lt;p&gt;La version 1.3RC1 est sortie la semaine dernière. J'ai retardé la mise en production car dans l'infrastructure test la migration depuis la version 1.2.1 supprimait des mesures. Renseignement pris (merci Simon), il semblerait qu'il faille relancer une collecte pour que les mesures réapparaissent.
Je suis vraiment très content de l'évolution du logiciel. Il s'améliore à chaque nouvelle version.&lt;/p&gt;


&lt;p&gt;Les nouveautés&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Possibilité de déployer sous Tomcat&lt;/li&gt;
&lt;li&gt;Détails des mesures par paquetage (très pratique :-))&lt;/li&gt;
&lt;li&gt;Nouveaux ratios pour les commentaires et le code dupliqué&lt;/li&gt;
&lt;li&gt;Amélioration des performances de l'ensemble&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour les notes de releases c'est par &lt;a href=&quot;http://jira.codehaus.org/browse/SONAR/fixforversion/14125&quot; hreflang=&quot;fr&quot;&gt;ici&lt;/a&gt;.
Un site de &lt;a href=&quot;http://nemo.hortis.ch&quot; hreflang=&quot;fr&quot;&gt;démonstration&lt;/a&gt; a été ouvert en même temps que la release de la 1.3RC1.&lt;/p&gt;


&lt;p&gt;La prochaine version (1.3) devrait sortir au début du mois de juin et semble bien &lt;a href=&quot;http://jira.codehaus.org/browse/SONAR?report=com.atlassian.jira.plugin.system.project:roadmap-panel&quot; hreflang=&quot;fr&quot;&gt;avancée&lt;/a&gt;.&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.netapsys.fr/index.php/post/2008/06/09/Nouvelle-version-de-Sonar#comment-form</comments>
      <wfw:comment>http://blog.netapsys.fr/index.php/post/2008/06/09/Nouvelle-version-de-Sonar#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.netapsys.fr/index.php/feed/atom/comments/26</wfw:commentRss>
      </item>
    
  <item>
    <title>Maven au ParisJUG</title>
    <link>http://blog.netapsys.fr/index.php/post/2008/05/14/Maven-au-Paris-JUG</link>
    <guid isPermaLink="false">urn:md5:b9136f319629391f70244cf8204b45f8</guid>
    <pubDate>Sun, 18 May 2008 12:19:00 +0200</pubDate>
    <dc:creator>Jean-Baptiste Defard</dc:creator>
        <category>Java J2EE</category>
        <category>java</category><category>JUG</category><category>maven</category><category>parisjug</category>    
    <description>&lt;p&gt;J'ai assisté mardi soir dernier à la présentation, par Arnaud Heritier, de Maven au Paris JUG . C'était une présentation vraiment intéressante. Arnaud a répondu avec beaucoup d'intelligence et de gentillesse à un certain nombre de questions concernant la communauté, l'organisation des repositories, les plugins et l'avenir du projet. Il a présenté les principales nouveautés de la version 2.0.9 et &lt;a href=&quot;http://blog.octo.com/index.php/2008/05/03/112-maven-community-news-avril-2008&quot; hreflang=&quot;fr&quot;&gt;l'actualité de la communauté.&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;Concernant les bonnes pratiques, des confirmations de ce que nous faisons déjà&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Utiliser Maven 2.0.9 sur lequel un gros travail d'amélioration a été réalisé, notamment en ce qui concerne la gestion des dépendances.&lt;/li&gt;
&lt;li&gt;Eviter les mises à jour automatiques des plugins en déclarant les versions des plugins utilisés dans le POM.&lt;/li&gt;
&lt;li&gt;Déclarer les versions des dépendances dans le POM parent et référencement dans les POM dérivés.&lt;/li&gt;
&lt;li&gt;Même si Maven 2.0.9 améliore les choses, ne pas faire (trop ) confiance à la résolution des dépendances transitives.&lt;/li&gt;
&lt;li&gt;Limiter l'utilisation des profils au strict minimum indispensable pour les configurations multi-environnements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour la configuration des plugins de rapport, il faut placer la configuration par défaut dans la section &amp;lt;report&amp;gt; et éventuellement modifier cette configuration par défaut dans la section &amp;lt;build&amp;gt; d'un POM dérivé. Autre information intéressante, configurer les plugins de rapport dans la section &amp;lt;pluginManagement&amp;gt; ne sert à rien, car cette configuration est ignorée.&lt;/p&gt;


&lt;p&gt;L'intégration à Eclipse devrait s'améliorer notablement d'ici à la fin de l'année. Eclipse prévoit d'intégrer dans ses distributions soit le plugin Q4e, soit le plugin m2Eclipse. Ces deux plugins sont actuellement en compétition pour l'intégration à Eclipse. Netbeans a, semble-t-il, un meilleur support de Maven 2.&lt;/p&gt;


&lt;p&gt;En passant, j'ai bien apprécié la nouvelle fonctionnalité de Maven 2.0.9 permettant de &lt;a href=&quot;http://blogs.sonatype.com/brian/2008/04/23/1208965980000.html&quot; hreflang=&quot;fr&quot;&gt;modifier les dépendances d'un plugin&lt;/a&gt;. Vraiment pratique, par exemple pour utiliser AspectJ 1.6.0 avec le plugin maven.&lt;/p&gt;


&lt;p&gt;En résumé, une bonne soirée au JUG et plein d'informations utiles pour améliorer les builds.&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.netapsys.fr/index.php/post/2008/05/14/Maven-au-Paris-JUG#comment-form</comments>
      <wfw:comment>http://blog.netapsys.fr/index.php/post/2008/05/14/Maven-au-Paris-JUG#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.netapsys.fr/index.php/feed/atom/comments/23</wfw:commentRss>
      </item>
    
  <item>
    <title>But Sierra 1, azimut 15, distance 3000...</title>
    <link>http://blog.netapsys.fr/index.php/post/2008/04/26/But-Serria-1-azimut-15-distance-3000</link>
    <guid isPermaLink="false">urn:md5:bdc09a1819a557b46cdff9123dbf6a25</guid>
    <pubDate>Sat, 26 Apr 2008 19:15:00 +0200</pubDate>
    <dc:creator>Jean-Baptiste Defard</dc:creator>
        <category>Java J2EE</category>
        <category>maven</category><category>sonar</category>    
    <description>    &lt;p&gt;Contrairement à ce que pourrait laisser suggérer le titre, je n'ai pas l'intention de commenter le dernier techno-thriller maritime sorti en librairie... &lt;a href=&quot;http://sonar.hortis.ch/&quot; hreflang=&quot;fr&quot;&gt;Sonar &lt;/a&gt; est un outil de suivi des indicateurs de qualité produits par des outils comme PMD, Checkstyle, Cobertura, JavaNCSS. C'est beau, c'est simple à installer et c'est facile à mettre en œuvre dans une intégration continue. Surtout, Sonar permet d'afficher et de comparer les évolutions chronologiques des indicateurs, sur autant de projets que l'on souhaite.
Un superbe outil, à mon avis. L'équipe de développement a décidé de sortir une release environ tous les mois, la version 1.3 ne devrait donc plus tarder.&lt;/p&gt;</description>
    
    
    
          <comments>http://blog.netapsys.fr/index.php/post/2008/04/26/But-Serria-1-azimut-15-distance-3000#comment-form</comments>
      <wfw:comment>http://blog.netapsys.fr/index.php/post/2008/04/26/But-Serria-1-azimut-15-distance-3000#comment-form</wfw:comment>
      <wfw:commentRss>http://blog.netapsys.fr/index.php/feed/atom/comments/18</wfw:commentRss>
      </item>
    
</channel>
</rss>
