Voici la suite de notre série d’articles sur le support REST de Spring 3.0 afin d’encapsuler des appels à un serveur Solr. Nous avons dans la première partie présenté le cas d’utilisation et mis en place le serveur Solr. Nous allons voir dans cette deuxième partie comment écrire un contrôleur REST avec Spring.
Configuration de l’infrastructure de Spring MVC
Le support REST de Spring est basé sur Spring MVC : les contrôleurs REST sont en fait des contrôleurs Spring MVC annotées d’une façon spécifique. La première étape consiste donc à configurer une DispatcherServlet traditionnelle :
-
<web-app xmlns:xsi=« http://www.w3.org/2001/XMLSchema-instance »
-
xmlns=« http://java.sun.com/xml/ns/javaee » xmlns:web=« http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd »
-
xsi:schemaLocation=« http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd »
-
id=« rest » version=« 2.5 »>
-
<display-name>Support REST de Spring avec Solr</display-name>
-
<servlet>
-
<servlet-name>catalogue</servlet-name>
-
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
-
</servlet>
-
<servlet-mapping>
-
<servlet-name>catalogue</servlet-name>
-
<url-pattern>/catalogue/*</url-pattern>
-
</servlet-mapping>
-
</web-app>
Par défaut, le fichier de configuration du contexte Spring de notre DispatcherServlet est /WEB-INF/catalogue-servlet.xml (établi selon le nom donné à la Servlet). Ce fichier va contenir pour l’instant la déclaration des contrôleurs Web, en utilisant du component scanning :
-
<?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 »
-
xsi:schemaLocation=«
-
<context:component-scan base-package=« com.zenika.springsolr.web » />
-
</beans>
Voyons maintenant comment implémenter une première version de notre contrôleur REST.
Contrôleur REST
Le contrôleur REST va permettre de consulter un catalogue de produit (les classes de domaine sont Catalogue et Produit). Comme dit précédemment, nous allons brancher notre contrôleur sur le serveur Solr, mais chaque chose en son temps, commençons par une version simplifiée, qui retourne une liste de produits en dur. Voici le code de ce premier jet :
-
package com.zenika.springsolr.web;
-
import org.springframework.stereotype.Controller;
-
import org.springframework.web.bind.annotation.RequestMapping;
-
import org.springframework.web.bind.annotation.RequestMethod;
-
import org.springframework.web.servlet.ModelAndView;
-
import com.zenika.springsolr.domain.Catalogue;
-
import com.zenika.springsolr.domain.Product;
-
@Controller
-
public class CatalogueController {
-
@RequestMapping(value=« /search/ »,method=RequestMethod.GET)
-
public ModelAndView search() throws Exception {
-
Catalogue catalogue = new Catalogue()
-
.add(new Product(« splp », « Spring par la pratique », « Cogoluegnes-Templier »))
-
.add(new Product(« sdmia », « Spring DM in action », « Cogoluegnes-Piper-Templier »));
-
return new ModelAndView(« default »,« catalogue »,catalogue);
-
}
-
}
Nous avons constitué notre « modèle », il est temps de s’atteler à la « vue ». Afin d’offrir la meilleure interopérabilité possible, les résultats seront rendus en XML. Nous allons compléter le contexte Spring de notre servlet avec la définition d’un Marshaller du module OXM de Spring, utilisant XStream pour sérialiser nos objets métier en XML :
-
<bean (…)>
-
(…)
-
<bean class=« org.springframework.web.servlet.view.BeanNameViewResolver » />
-
<bean id=« default » class=« org.springframework.web.servlet.view.xml.MarshallingView »>
-
<property name=« marshaller » ref=« marshaller » />
-
</bean>
-
<bean id=« marshaller » class=« org.springframework.oxm.xstream.XStreamMarshaller »>
-
<property name=« aliases »>
-
<map>
-
<entry key=« catalogue » value=« com.zenika.springsolr.domain.Catalogue » />
-
<entry key=« product » value=« com.zenika.springsolr.domain.Product » />
-
</map>
-
</property>
-
</bean>
-
</bean>
Une fois le serveur démarré, allons voir le résultat de notre travail à l’adresse http://localhost:8080/springsolr/catalogue/search/ :
-
<catalogue>
-
<products>
-
<product>
-
<id>splp</id>
-
<name>Spring par la pratique</name>
-
<manufacturer>Cogoluegnes-Templier</manufacturer>
-
</product>
-
<product>
-
<id>sdmia</id>
-
<name>Spring DM in action</name>
-
<manufacturer>Cogoluegnes-Piper-Templier</manufacturer>
-
</product>
-
</products>
-
</catalogue>
Nous avons mis en place l’ossature de notre contrôleur REST, branchons maintenant notre contrôleur REST sur Solr avec l’API client Java, SolrJ.
Implémentation du contrôleur REST avec SolrJ
Solr propose une API permettant d’accéder à une instance de Solr, qu’elle soit embarquée (dans le même process Java) ou distante (comme dans notre cas). Voici un exemple :
-
SolrServer solrServer = new CommonsHttpSolrServer(« http://localhost:8983/solr/ »);
-
SolrQuery solrQuery = new SolrQuery(« solr »);
-
SolrResponse response = solrServer.query(solrQuery);
Nous allons utiliser SolrJ dans le contrôleur REST, mais commençons par déclarer le SolrServer dans le contexte Spring de notre DispatcherServlet :
-
<bean id=« solrServer » class=« org.apache.solr.client.solrj.impl.CommonsHttpSolrServer »>
-
<constructor-arg value=« http://localhost:8983/solr/ » />
-
</bean>
Nous pouvons ensuite l’injecter dans le contrôleur, avec l’annotation standard @Inject :
-
import javax.inject.Inject;
-
import org.apache.solr.client.solrj.SolrServer;
-
@Controller
-
public class CatalogueController {
-
@Inject
-
private SolrServer solrServer;
-
(…)
-
}
Notez que l’utilisation de l’interface SolrServer nous permet de changer d’implémentation à tout moment : l’implémentation distante (celle que nous utilisons) pour la production, car elle permet d’attaquer un serveur Solr dédié, ou une implémentation locale pour le développement, qui attaquerait une instance embarquée de Solr.
Modifions maintenant la méthode search pour qu’elle récupère depuis l’URL la requête Solr et qu’elle utilise le SolrServer pour récupérer les documents et les transformer en Produit :
-
import org.apache.solr.client.solrj.SolrQuery;
-
import org.apache.solr.client.solrj.SolrResponse;
-
import org.apache.solr.client.solrj.SolrServer;
-
import org.apache.solr.common.SolrDocument;
-
import org.apache.solr.common.SolrDocumentList;
-
import org.springframework.web.bind.annotation.PathVariable;
-
(…)
-
@Controller
-
public class CatalogueController {
-
@Inject
-
private SolrServer solrServer;
-
@RequestMapping(value=« /search/{query} »,method=RequestMethod.GET)
-
public ModelAndView search(@PathVariable String query) throws Exception {
-
SolrQuery solrQuery = new SolrQuery(query);
-
SolrResponse response = solrServer.query(solrQuery);
-
SolrDocumentList docs = (SolrDocumentList) response.getResponse().get(« response »);
-
Catalogue catalogue = new Catalogue();
-
for(SolrDocument doc : docs) {
-
catalogue.add(map(doc));
-
}
-
return new ModelAndView(« default »,« catalogue »,catalogue);
-
}
-
private Product map(SolrDocument doc) {
-
return new Product(doc.get(« id »).toString(),doc.get(« name »).toString(),doc.get(« manu »).toString());
-
}
-
}
Notez l’utilisation de l’annotation @PathVariable, qui permet de récupérer une partie de l’URL afin de l’utiliser comme paramètre dans le contrôleur. On peut tester cette première fonction REST avec un navigateur Web en exécutant la requête ‘solr’ : http://localhost:8080/springsolr/catalogue/search/solr (attention de bien avoir le serveur Solr lancé comme vu dans la première partie ! ). La réponse est la suivante :
-
<catalogue>
-
<products>
-
<product>
-
<id>SOLR1000</id>
-
<name>Solr, the Enterprise Search Server</name>
-
<manufacturer>Apache Software Foundation</manufacturer>
-
</product>
-
</products>
-
</catalogue>
Pour avoir plus de résultats (limités à 10 toutefois, mais cela est réglable dans la SolrQuery), utiliser l’URL suivante : http://localhost:8080/springsolr/catalogue/search/*:*.
Et pourquoi ne pas permettre à notre contrôleur d’indexer des produits ? Suivons les principes de REST et implémentons cela dans le contrôleur sous la forme d’une requête POST :
-
@Controller
-
public class CatalogueController {
-
(…)
-
@RequestMapping(value=« /index/{id}/{name}/{manu} »,method=RequestMethod.POST)
-
public void index(@PathVariable String id,@PathVariable String name,@PathVariable String manu,
-
HttpServletResponse response) throws Exception {
-
SolrInputDocument doc = new SolrInputDocument();
-
doc.addField(« id »,id);
-
doc.addField(« name »,name);
-
doc.addField(« manu »,manu);
-
solrSe rver.add(doc);
-
solrServer.commit();
-
response.setStatus(HttpServletResponse.SC_OK);
-
}
-
}
Il n’est malheureusement pas possible de tester simplement l’indexation avec un navigateur Web, car la requête attendue doit être en POST. Nous allons tester l’indexation dans la troisième partie de l’article, grâce à la partie cliente REST de Spring, le RestTemplate.
