Skip to content

Alfresco in a Mashup World – Seconda parte

October 20, 2009

In questo secondo articolo, proseguiamo il lavoro sul ResteasyController.

Nel primo articolo, il controller esponeva dei soli metodi GET per restituire una rappresentazione XML o JSON degli articoli.

Ora aggiungiamo altri metodi per inserire, aggiornare ed eliminare articoli.

Al termine avremo dei metodi che corrisponderanno alle funzionalità CRUD.

POST     -> Create

GET       -> Read

PUT       ->Update

DELETE  ->Delete.

Ora aggiorniamo il RestEasyController aggiungendo i metodi di modifica/inserimento/eliminazione

package it.pronetics.web;

import it.pronetics.domain.Article;
import it.pronetics.exception.InvalidXmlException;
import it.pronetics.services.ArticleFacade;

import java.io.IOException;
import java.io.InputStream;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Response.ResponseBuilder;

import org.jboss.resteasy.annotations.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;

/**
 * @author Massimiliano Dessì (desmax74)
 * @since jdk 1.5.0
 */
@Path("/services")
@Controller
public class ResteasyController {

   private final String OK = Response.ok().build().toString();
   private ArticleFacade articleFacade;

   private String inputStreamToString(InputStream is) throws IOException {
      byte[] bytes = new byte[is.available()];
      is.read(bytes);
      return new String(bytes);
   }

   @Autowired
   public ResteasyController(@Qualifier("articleFacade") ArticleFacade articleFacade) {
      this.articleFacade = articleFacade;
   }
   @GET
   @Cache(maxAge = 3600)
   @Produces(MediaType.TEXT_XML)
   @Path("/xml/{id}")
   public Response getXmlView(@PathParam("id") String id) {
      ResponseBuilder builder = (id.equals(null)) ? Response.noContent() : Response.ok(articleFacade.getXmlArticle(id));
      return builder.build();
   }

   @GET
   @Cache(maxAge = 3600)
   @Produces(MediaType.APPLICATION_JSON)
   @Path("/json/{id}")
   public Response getJsonView(@PathParam("id") String id) {
      ResponseBuilder builder = (id.equals(null)) ? Response.noContent() : Response.ok(articleFacade.getJSONArticle(id));
      return builder.build();
   }

   @GET
   @Cache(maxAge = 3600)
   @Produces(MediaType.APPLICATION_XML)
   @Path("/xml/list/{numberArticles}")
   public Response getLastArticlesXmlView(@PathParam("numberArticles") int numberArticles) {
      ResponseBuilder builder = (numberArticles == 0) ? Response.noContent() : Response.ok(articleFacade.getXMLLastArticles(numberArticles));
      return builder.build();
   }

   @GET
   @Cache(maxAge = 3600)
   @Produces(MediaType.APPLICATION_JSON)
   @Path("/json/list/{numberArticles}")
   public Response getLastArticlesJsonView(@PathParam("numberArticles") int numberArticles) {
      ResponseBuilder builder = (numberArticles == 0) ? Response.noContent() : Response.ok(articleFacade.getJSONLastArticles(numberArticles));
      return builder.build();
   }

   @DELETE
   @Path("/article/{id}")
   public String deleteArticle(@PathParam("id") String id) {
      articleFacade.deleteArticle(id);
      return OK;
   }

   @POST
   @Path("/article")
   @Consumes("application/xml")
   public String insertArticle(InputStream is, @Context UriInfo uriInfo) throws IOException, InvalidXmlException {
      String xml = inputStreamToString(is);
      articleFacade.insertArticle(xml);
      return OK;
   }

   @PUT
   @Path("/article")
   @Consumes("application/xml")
   public String updateArticle(InputStream is) throws IOException, InvalidXmlException, InvalidXmlException {
      String xml = inputStreamToString(is);
      articleFacade.updateArticle(xml);
      return OK;
   }
}

Solo i metodi @GET in lettura saranno accessibili, gli altri metodi richiederanno che la chiamata avvenga da un utente autenticato
con il ruolo di amministratore.

Per fare questo richiederemo a Spring-Security che gli url “/services/article/*” richiedano il ROLE_ADMIN.

Nel web.xml mappiamo questo controller con

    <servlet-name>resteasy</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:resteasyApplicationContext.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>resteasy</servlet-name>
        <url-pattern>/services/*</url-pattern>
    </servlet-mapping>

Ora aggiungiamo alla nostra applicazione anche i controller “tradizionali”, utilizzando SpringMVC 3.0

Per primo vediamo il controller per inserire/aggiornare gli articoli, che utilizza un validator per la validazione lato server dei nostri oggetti Article, e il solito articleFacade per il salvataggio/recupero Articoli

package it.pronetics.web.ui;

import it.pronetics.domain.ArticleCommand;
import it.pronetics.services.ArticleFacade;
import it.pronetics.util.Constants;
import it.pronetics.validator.ArticleValidator;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

/**
 * @author Massimiliano Dessì (desmax74)
 * @since jdk 1.5.0
 */
@Controller("articleFormController")
@SessionAttributes("article")
public class ArticleFormController {

   private ArticleFacade articleFacade;
   private ArticleValidator validator;

   @Autowired
   public ArticleFormController(@Qualifier("articleFacade") ArticleFacade articleFacade, @Qualifier("articleValidator") ArticleValidator validator) {
     this.articleFacade = articleFacade;
     this.validator = validator;
   }

   @RequestMapping(value= "/article/edit/{id}", method = RequestMethod.POST)
   public String processSubmit(@ModelAttribute("article") ArticleCommand article, BindingResult result, SessionStatus status) {
      validator.validate(article, result);
      if (result.hasErrors()) {
          return "articleForm";
      } else {
          articleFacade.saveArticle(article);
          status.setComplete();
          return Constants.REDIRECT_LIST_ARTICLES_DEFAULT;
      }
   }

   @InitBinder()
   public void initBinder(WebDataBinder binder) throws Exception {
      binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
   }

   @RequestMapping(value= "/article/edit/{id}", method = RequestMethod.GET)
   public String setupForm(@PathVariable("id") String id, ModelMap model) {
      model.addAttribute(Constants.ARTICLE, !id.equals(Constants.ID_NEW) ? articleFacade.getArticle(id) : new ArticleCommand());
      return "articleForm";
   }
}

Questo controller richiederà il ruolo di amministratore, anche qui diremo s Spring-Security che di
gli url “/article/*  saranno accessibili solo con ROLE_ADMIN, invece quelli con in sola lettura “/articles/*”

saranno pubblici.

La jsp con i tag form che permettono l’inserimento/aggiornamento è la seguente

<%@ include file="/WEB-INF/jsp/taglibs.jsp"%>
<springf:form action="edit" commandName="article" method="post">
    <table>
       <tr>
           <td><spring:message code="article.title" />:</td>
           <td><springf:input path="title" maxlength="45"/></td>
           <td><springf:errors path="title" /></td>
       </tr>
       <tr>
           <td><spring:message code="article.author"/>:</td>
           <td><springf:input path="author"/></td>
           <td><springf:errors path="author"/></td>
       </tr>
       <tr>
           <td><spring:message code="article.textAbstract"/>:</td>
           <td><springf:textarea path="textAbstract"/></td>
           <td><springf:errors path="textAbstract"/></td>
       </tr>
       <tr>
           <td><spring:message code="article.content"/>:</td>
           <td><springf:textarea path="content"/></td>
           <td><springf:errors path="content"/></td>
       </tr>
       <tr>
           <td colspan="3">
              <input type="submit" value="<spring:message code="save"/>"/>
              <input type="hidden" id="id" name="id" value="${article.id}"/>
              <input type="button" onclick="location.href = 'articles'" value="<spring:message code="article.list"/>"/>
           </td>
       </tr>
    </table>
</springf:form>

Ora vediamo l’ArticleController che ci permette la visualizzazione in html
degli articoli singoli o meno e la cancellazione degi articoli.

package it.pronetics.web.ui;

import it.pronetics.services.ArticleFacade;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author Massimiliano Dessì (desmax74)
 * @since jdk 1.5.0
 */
@Controller("articleController")
public class ArticleController {

    private ArticleFacade articleFacade;

    @Autowired
    public ArticleController(@Qualifier("articleFacade") ArticleFacade articleFacade) {
       this.articleFacade = articleFacade;
    }

    @RequestMapping(value = "/articles/{id}", method = RequestMethod.GET)
    public ModelAndView getArticle(@PathVariable("id") String id) {
       return new ModelAndView("articleView", "article", articleFacade.getArticle(id));
    }

    @RequestMapping(value = "/articles/desc/{numberArticles}", method = RequestMethod.GET)
    public ModelAndView getLastArticle(@PathVariable("numberArticles") int numberArticles) {
       return new ModelAndView("articles", "articles", articleFacade.getLastArticles(numberArticles));
    }

    @RequestMapping("/article/delete/{id}")
    public ModelAndView delete(@PathVariable("id") String id) {
       articleFacade.deleteArticle(id);
       return new ModelAndView("redirect:articles");
    }

    @RequestMapping(value = "/article/confirm/{id}", method = RequestMethod.POST)
    public ModelAndView confirmDelete(@PathVariable("id") String id) {
       return new ModelAndView("confirmDelete", "article", articleFacade.getArticle(id));
    }
}

Nel web.xml mappiamo questo controller così

<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/articles/**</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/article/**</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Le jsp che mostrano il dettaglio dell’ articolo, l’elenco degli articoli e la richiesta di conferma eliminazione
le omettiamo per brevità, contengono la semplice visualizzazione dell’ArticleCommand e dei suo campi.

Ora iniziamo a vedere l’ArticleFacade.
Questa classe Facade svolge o delega due compiti essenziali e uno opzionale.

  • Colloquiare con Alfresco
  • Effettuare la conversione da e verso JSON/XML e oggetti ArticleCommand
  • Mantenere una cache che sgravi la cpu dal dover ricostruire/recuperare gli oggetti più richiesti

Vediamo il primo punto, colloquiare con Alfresco.

Alfresco come anticipato nel primo articolo può esporre i contenuti attraverso una interfaccia REST nella forma che più ci è utile,

Html, JSON o XML.

Vediamo per ora come possiamo richiamare l’interfaccia REST esposta da Alfresco.

Possiamo scegliere tra il client resteasy, il RestTemplate fornito da Spring 3.0 oppure utilizzare Jakarta Commons HttpClient.

Iniziamo a conoscere il RestTemplate, che come altre classi template di Spring  fornisce un alto livello di astrazione (in questo caso rispetto ad HttpClient).

I metodi forniti dal RestTemplate sono:

HTTP RestTemplate
DELETE delete(String, String...)
GET getForObject(String, Class, String...)
HEAD headForHeaders(String, String...)
OPTIONS optionsForAllow(String, String...)
POST postForLocation(String, Object, String...)
PUT put(String, Object, String...)

che corrispondono alle varie tipologie di chiamate REST.

Gli oggetti da e per i metodi del RestTemplate passano attraverso i messageconverters per essere convertiti.

II MessageConverters registrati e disponibili di default sono:

  • ByteArrayHttpMessageConverter,
  • StringHttpMessageConverter,
  • FormHttpMessageConverter
  • SourceHttpMessageConverter

Altri disponibili sono

  • MarshallingHttpMessageConverter
  • BufferedImageHttpMessageConverter

Il RestTemplate ci permetterà quindi di fare chiamate da e verso servizi REST in modo semplificato.

Nel prossimo articolo vedremo come si configura e come si utilizza.

Oltre alla spiegazione sull’ uso del RestTemplate vedremo finalmente come Alfresco salvati e recupera i nostri oggetti attraverso una interfaccia REST.

Author:Massimiliano Dessì

Advertisements
No comments yet

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: