Implementer une architecture de service web RESTfull avec Restlet et son extention Spring

Implementer une architecture de service web RESTfull avec Restlet et son extention Spring

Cet article fait suite mon article REST – Architecture Orientée Ressource en lui donnant une implémentation basé sur le framework Restlet.

Le principe de Restlet :

Restlet est un framework Java 5.0 qui obéit aux standard RESTful et permet de créer facilement des application RESTful. Il remplace la spécification J2EE par une API orientée REST : on développe des Restlet au lieu de développer des Servlet.

Il existe deux composant principaux:

  • Router : permet de router une requête HTTP vers une ressource.
  • Resource : la ressource REST d’un objet, une ressource permet plusieurs représentations différentes.
  • Représentation: la représentation visuelle de la ressource.

Dans cet exemple, je vais utiliser Velocity pour créer des représentation XML et XHTML et l’extension Spring de Restlet qui permet de définir un Router directement dans les fichier de configuration des beans de Spring et de faire de l’injection dans les classes de Resource.

Le fichier de configuration Spring :

C’est la base de l’utilisation de Restlet avec Spring, voici un exemple de fichier définissant un routeur et des ressources. Le routeur est définit par une map dont les entrée ont comme clé le pattern d’URL (les paramètres sont entre {}) et en valeur la ressource (dont le bean est localisé via le SpringFinder, pour assurer la création d’une ressource différente à chaque requête).
Il faut bien faire attention au fait qu’une resource soit de type ‘prototype’.

<bean id="/catalog" name="/catalog" class="org.restlet.ext.spring.SpringRouter">
    <property name="attachments">
        <map>
            <!--  product resource -->
            <entry key="/v{versionNumber}/product/{dcrName}.{language}.{representationType}">
                <bean class="org.restlet.ext.spring.SpringFinder">
                    <lookup-method name="createResource" bean="catalog.productResource"/>
                </bean>
            </entry>
            <entry key="/v{versionNumber}/product/{dcrName}/{format}.{language}.{representationType}">
                <bean class="org.restlet.ext.spring.SpringFinder">
                    <lookup-method name="createResource" bean="catalog.productResource"/>
                </bean>
            </entry>
            <!--  category resource -->
            <entry key="/v{versionNumber}/category/{catName}.{language}.{representationType}">
                <bean class="org.restlet.ext.spring.SpringFinder">
                    <lookup-method name="createResource" bean="catalog.categoryResource"/>
                </bean>
            </entry>
        </map>
    </property>
</bean>
<bean id="catalog.productResource" class="com.loicmathieu.resource.catalog.ProductResource"
        scope="prototype" parent="baseResource">
    <property name="modifiable" value="false"/>
    <property name="productService" ref="ProductService"/>
</bean>
<bean id="catalog.categoryResource" class="com.loicmathieu.resource.catalog.CategoryResource"
        scope="prototype" parent="baseResource">
    <property name="modifiable" value="false"/>
    <property name="categoryService" ref="CategoryService"/>
</bean>

J’utilise ici une BaseResource comme parent à toutes mes ressources (ce qui explique le parent= »baseResource », à ne pas mettre si vous ne créez pas de base ressource)
, celle-ci contient du code commun à toutes mes ressources pour gérer la langue, le numéro de version, les différentes représentation… Je vous conseil de faire de même en fonction de ce que vous voulez réaliser.

On peut remarquer que j’ai mappé deux URL vers la ressource product, c’est tout à fait possible. Je vous donnerais plus tard un exemple d’utilisation.

La classe org.restlet.resource.Resource

C’est la classe de base d’une ressource, voici les principales méthodes qu’il faut surcharger pour écrire vos propre ressources:

  1. onInit(): lire les paramètres de la request
  2. represent(Variant v):  pour la méthode http ‘GET‘: récupération d’une ressource
  3. removeRepresentation(): pour la méthode http ‘DELETE‘: suppression d’une ressource
  4. acceptRepresentation (): pour la méthode http ‘POST‘: création d’une nouvelle ressource
  5. storeRepresentation(): pour la méthode http ‘PUT‘: modification d’une ressource existante

Exemple: la classe ProductResource

/**
 * @author Loïc Mathieu
 */
public class ProductResource extends BaseResource {
    // request parameters
    private String productName;
    private String format;

    // IoC parameter
    private ProductService productService;

     //IoC Setter
    public void setProductService(ProductService productService) {
        this.productService = productService;
    }

    @Override protected void onInit(Context context, Request request, Response response) {
        productName = (String) request.getAttributes().get("productName");
        format = (String) request.getAttributes().get("format");
    }

    @Override public Representation represent(Variant variant) throws ResourceException {
        Product product = productService.findProductByName(productName);

        // select the desired template
        String velocityTpl = "catalog.product.vm"";
            if (format != null && "small".equals(format)) {
            velocityTpl = "catalog.product.small.vm";
        }

        Map<String, Object> data = new HashMap<String, Object>();
        data.put("product", product);

        switch (getExtension()) {
            case HTML:
                return new VelocityRepresentation(velocityTpl, data, getExtension().getMediaType());
            case XML:
                StringRepresentation sr = new StringRepresentation(product.getXmlRepresentation(), getExtension().getMediaType());
                return new DomRepresentation(sr);
            case TXT:
                return new StringRepresentation(product.getDisplayName());
            default:
                throw new RessourceException(Status.CLIENT_ERROR_BAD_REQUEST,
                        "a problem occured, please contact the administrator.",
                        " Unable to find a representation of type: " + getExtension());
        }
    }
}

Ce qu’il faut remarquer dans ce code source:

  • ligne 18 et 19: lecture des paramètre depuis la requête
  • ligne 26: celons la valeur du paramètre format j’utilise des templates Velocity différent
  • ligne 34: je switch sur l’extension de la ressource (la classe Extension est une enum qui lie .txt -> Extension.TXT, .html -> Extention.html, … Et qui est calculé dans ma base resource)
  • Pour chaque type d’extension, je génère un type de représentation différent
    • HTML: une représentation VelocityRepresentation qui utilise un template Velocity pour créer l’HTML. VelocityRepresentation est une sous classe de TemplateRepresentation qui facilite l’utilisation de Velocity. J’en donne l’implémentation plus bas.
    • TXT: une StringReprésentation
    • XML: pour plus de facilité je présume qu’un produit peut créer son propre XML. J’utilise ensuite une DomRepresentation.
    • ligne 42: très important, si l’extension n’est pas de type supporté pour la ressource, alors je lance une exception qui contient un code d’erreur HTTP. Ici une 400 Bad Request.

La classe VelocityRepresentation:

/**
 * @author Loïc Mathieu - LBI
 */
public class VelocityRepresentation extends TemplateRepresentation {

    public VelocityRepresentation(String templateName, Map<String, Object> dataModel, MediaType mediaType) {
        super(templateName, dataModel, mediaType);
        configureEngine();
    }

    public VelocityRepresentation(String templateName, MediaType mediaType) {
        super(templateName, mediaType);
        configureEngine();
    }

    /**
     * This method sets some configuration properties to the VelocityEngine.
     */
    protected void configureEngine() {
        getEngine().setProperty(RuntimeConstants.RESOURCE_LOADER, "class");
        getEngine().setProperty("class.resource.loader.class",
                "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        getEngine().setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new Log4JLogChute());
        getEngine().setProperty(Log4JLogChute.RUNTIME_LOG_LOG4J_LOGGER, "ELNINO");
    }
}

Utilisation de l’HTTP Component

Restlet peut utiliser un composant HTTP basique, très léger et très rapide qui permet de tester facilement une application, il peut aussi être utilisé en production bien que pour ma part je déploie l’application sur un serveur WebSphere en production donc je ne peut vous donner du feedback sur une utilisation en production.

<bean id="restletComponent" class="org.restlet.ext.spring.SpringComponent">
    <property name="server">
        <bean class="org.restlet.ext.spring.SpringServer">
            <constructor-arg value="http" />
            <constructor-arg value="3001" />
        </bean>
    </property>
    <property name="defaultTarget" ref="defaultRouter" />
</bean>

<bean id="defaultRouter" class="org.restlet.ext.spring.SpringRouter">
    <property name="attachments">
        <map>
            <entry key="/catalog">
                <bean class="org.restlet.ext.spring.SpringRouter" ref="/catalog"" />
            </entry>
        </map>
    </property>
</bean>

Ici, on définit un SpringComponent qui sera un serveur HTTP qui servira les ressource. Celui-ci a comme routeur par défaut un SpringRouter qui contient en unique attachement notre routeur de catalog. Mais on pourrais lui ajouter d’autre routeur pour définir un ensemble de ressources.

Il ne nous reste plus qu’à faire une classe java dont le main va charger nos fichier Spring, rechercher le bin du SpringComponent et le démarrer.

On accédera ensuite à l’application via une URL du type: http://localhost:3001/catalog/product/monProduct.en.html

Deployement comme une servlet

On peut aussi facillement inclure notre application restlet dans une servlet. Pour ceci il faut utiliser la RestletFrameworkServlet de l’extension Spring dans notre web.xml comme ci-dessous

<!-- Restlet adapter -->
<servlet>
    <servlet-name>api</servlet-name>
    <servlet-class>com.noelios.restlet.ext.spring.RestletFrameworkServlet</servlet-class>
    <init-param>
        <param-name>targetRestletBeanName</param-name>
        <param-value>restletApplication</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
     <servlet-name>api</servlet-name>
     <url-pattern>/*</url-pattern>
 </servlet-mapping>

Puis définir un context pour notre servlet qui va définir une application restlet qui utilisera, comme notre http component précédent, le défault routeur.

<bean id="restletApplication" class="org.restlet.Application">
    <property name="root" ref="defaultRouter" />
</bean>

<bean id="defaultRouter" class="org.restlet.ext.spring.SpringRouter">
    <property name="attachments">
        <map>
            <entry key="/catalog">
                <bean class="org.restlet.ext.spring.SpringRouter" ref="/catalog"" />
            </entry>
        </map>
    </property>
</bean>

On accédera ensuite à l’application via une URL du type: http://localhost:8080/<webContext>catalog/product/monProduct.en.html où webContext est le context du WAR de votre application.

J’espère que ceci vous aura montrer toute la puissance de Restlet et de son extension Spring. Le code montré ici est un exemple non fonctionnel tiré du code que j’ai créée pour ma société et qui tourne en production depuis plus d’un an.

Source des exemples

catalog-ctx.xml
ProductResource.java
VelocityRepresentation.java
restletComponent-ctx.xml
web.xml
api-servelt.xml

Les commentaires sont clos.