Skip to content

Alfresco in a Mashup World – Prima parte

October 14, 2009

Nel passato pubblicare e aggregare contenuti era un lavoro esclusivo per portali enterprise, portal server e portlet.
Spesso i portali erano il frontend di un Content Management System con cui nascevano e di cui non potevano fare a meno per essere alimentati.
Con l’avvento del web 2.0 e le tecniche AJAX, si è aperta una nuova modalità di fare business grazie alla maggiore interattività fornita.
Questo ha portato ad utilizzare il frontend come nuovo servizio che richiama altri servizi, come esempio basti pensare all’ utilizzo delle mappe geografiche, prima mai utilizzate se non nelle applicazioni cartografiche e quasi mai sul web.
Questo modo di aggregare informazioni ha portato ad applicazioni ibride denominate mashup.
Il frontend molto iterattivo diventa l’espositore di servizi diversificati anzichè dei soli dati presenti nel solo back-end.
I contenuti sono quindi diventati dei servizi, “content as a service”.
La modalità di colloquio verso i servizi è diventata quella Representational state transfer (REST) attraverso comandi GET, PUT, POST, DELETE.
Il formato di comunicazione avviene attraverso JSON o XML.
In questo contesto tecnologico possiamo quindi individuare alcune caratteristiche necessarie per utilizzare dei contenuti in questo nuovo scenario.

La sorgente deve esporre i contenuti attraverso una interfaccia REST, i contenuti possono essere restituiti sotto forma di XML/JSON/ATOM/HTML.
Uno dei pochi se non l’unico Enterprise Content Management Open Source che può essere utilizzato con queste specifiche è Alfresco.
Attraverso Alfresco possiamo permettere l’interrogazione dietro autenticazione dei contenuti ed esporli con l’interfaccia che più ci è utile.

Potremmo anche pensare di avere diverse istanze di Alfresco con contenuti e scopi diversi.

Inizieremo a vedere in questo primo articolo, come esporre dei nostri oggetti attraverso una applicazione che lavora come client di Alfresco.

Vediamo in questo tutorial come esporre il nostro domain object Article che viene persistito e gestito in una istanza di Alfresco ed esposto tramite un ReadRestController che per brevità avrà le sole operazioni di lettura.

Per completezza, diciamo che Alfresco potrebbe già presentare da solo i contenuti, ma scegliamo di non esporre Alfresco direttamente per permettere alla nostra mashup di effettuare operazioni di caching e di aggregazione con altri dati.

Iniziamo a vedere la composizione del nostro oggetto di dominio che rappresenta un Articolo.

Article espone i dati in sola lettura e i metodi di business. Article è immutabile, ciò ci permette di condividerlo tranquillamente senza preoccuparci di una possibile corruzione del suo stato.

package it.pronetics.domain;

import it.pronetics.util.Constants;
/**
 * @author Massimiliano Dessì (desmax74)
 * @since jdk 1.5.0
 */
public class Article {

   private ArticleCommand command;

   Article(){}

   public Article(ArticleCommand command){
      this.command = command;
   }

   public String getId() {
      return command.getId();
   }

   public String getTitle() {
      return command.getTitle();
   }

   public String getTextAbstract() {
      return command.getTextAbstract();
   }

   public String getContent() {
      return command.getContent();
   }

   public String getShortContent() {
     int firstOccurence = command.getContent().indexOf(Constants.DOT_WITH_SPACE);
     return command.getContent().substring(0, firstOccurence);
   }

   public int getVersion() {
      return command.getVersion();
   }

   public String getAuthor() {
      return command.getAuthor();
   }

   public boolean isPublic() {
      return command.isPublic();
   }

   public boolean isNew() {
      return Constants.ID_NEW.equals(command.getId());
   }

   public boolean isDraft() {
      return (command.getTitle().length() < 4  &&
              command.getTextAbstract().length() < 4  &&
              command.getContent().length() < 10)
              ? false : true;
   }
}

Al proprio interno Article contiene un command object, ArticleCommand che viene utilizzato per il web binding nelle operazioni di inserimento/aggiornamento.

Esso rappresenta i soli dati senza nessun comportamento.

package it.pronetics.domain;

import it.pronetics.util.Constants;
/**
 * @author Massimiliano Dessì (desmax74)
 * @since jdk 1.5.0
 */
public class ArticleCommand {

   private String title, textAbstract, content, author;
   private String id = Constants.ID_NEW;
   private boolean isDraft = true;
   private int version;
   private boolean isPublic = false;

   public String getTitle() {
      return title;
   }

   public String getTextAbstract() {
     return textAbstract;
   }

   public String getContent() {
      return content;
   }

   public String getAuthor() {
      return author;
   }

   public String getId() {
      return id;
   }

   public boolean isDraft() {
      return isDraft;
   }

   public int getVersion() {
      return version;
   }

   public boolean isPublic() {
      return isPublic;
   }

   public void setTitle(String title) {
      Validate.notNull(title);
      this.title = title;
   }

   public void setTextAbstract(String textAbstract) {
      Validate.notNull(textAbstract);
      this.textAbstract = textAbstract;
   }

   public void setContent(String content) {
      Validate.notNull(content);
      this.content = content;
   }

   public void setAuthor(String author) {
      Validate.notNull(author);
      this.author = author;
   }

   public void setId(String id) {
      Validate.notNull(id);
      this.id = id;
   }

   public void setDraft(boolean isDraft) {
      this.isDraft = isDraft;
   }

   public void setVersion(int version) {
      Validate.isTrue(version > -1);
      this.version = version;
   }

   public void setPublic(boolean isPublic) {
      this.isPublic = isPublic;
   }

   public ArticleCommand(){}

   public static class Builder {
      private String title, textAbstract,content, author;
      private String id= Constants.ID_NEW;
      private boolean isDraft = true;
      private int version= -1;
      private boolean isPublic = false;

      public Builder title(String title) {
         Validate.notNull(title);
         this.title = title;
         return this;
      }

      public Builder textAbstract(String textAbstract) {
         Validate.notNull(textAbstract);
         this.textAbstract = textAbstract;
         return this;
      }

      public Builder content(String content) {
         Validate.notNull(content);
         this.content = content;
         return this;
      }

      public Builder author(String author) {
         Validate.notNull(author);
         this.author = author;
         return this;
      }

      public Builder id(String id) {
        Validate.notNull(id);
        this.id = id;
        return this;
      }

      public Builder isDraft(boolean isDraft) {
         this.isDraft = isDraft;
         return this;
      }

      public Builder version(int version) {
         Validate.isTrue(version > -1);
         this.version = version;
         return this;
      }

      public Builder isPublic(boolean isPublic) {
         this.isPublic = isPublic;
         return this;
      }

      public ArticleCommand build() {
         return new ArticleCommand(this);
      }
   }

   private ArticleCommand(Builder builder) {
      this.title = builder.title;
      this.textAbstract = builder.textAbstract;
      this.content = builder.content;
      this.author = builder.author;
      this.id = builder.id;
      this.isDraft = builder.isDraft;
      this.version = builder.version;
      this.isPublic = builder.isPublic;
   }
}

ora vediamo una prima interfaccia con cui la nostra mashup può essere interrogata.

Il ResteasyController che in questa prima versione espone i soli metodi GET, per la lettura di singoli articoli in fomrato json o xml, oppure una lista con gli ultimi n- articoli.

I risultati in questo primo controller vengono esposti in formato XML o JSON.
Per implementare le funzionalità richieste, utilizziamo le annotazioni di SpringMVC e di RestEasy.

package it.pronetics.web;

import it.pronetics.services.ArticleFacade;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
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.stereotype.Controller;

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

   private ArticleFacade articleFacade;

   @Autowired
   public FrontController(@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();
   }

}

Per soddisfare le richieste viene utilizzato un ArticleFacade che provvede ad esaudire le richieste ricevute da questo controller.
Nel secondo articolo della serie continueremo la nostra mashup con altri controller e con l’applicazione
di API lightweight per avere le rappresentazioni XML e JSON dei nostri oggetti di dominio.

Author: Massimiliano Dessì

Advertisements

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: