Novembre 15, 2019

Alfresco, gestire Sessioni CMIS con Spring Interceptor

Apache Chemistry OpenCMIS fornisce un’implmentazione open source delle specifiche CMIS cioè lo standard di interoperabilità tra i sistemi ECM. Le librerie OpenCMIS mettono a disposizione una serie di oggetti e interfacce per creare una sessione CMIS e connettersi a un repository ECM come per esempio Alfresco. Nella progettazione e sviluppo di applicazioni content-rich che fanno un uso estensivo dei principali servizi sui contenuti forniti da Alfresco, OpenCMIS ricopre un ruolo fondamentale a livello architetturale poichè permette il totale disaccopiamento tra l’applicazione e il Content Repository.

In un precedente articolo ho riportato un semplice esempio di come creare una sessione CMIS con le librerie client di OpenCMIS. Vediamo ora una caso più pratico in un contesto architetturale più strutturato. In questo articolo ho provato a gestire una sessione CMIS verso un Repository Alfresco a partire da un’applicazione web Spring MVC. L’utilizzo di Spring MVC, oltre a garantire un’implementazione robusta e affidabile del pattern MVC, permette di sfruttare l’oggetto HandlerInterceptor per verificare la sessione CMIS e quindi controllare l’autenticazione dell’utente verso Alfresco (Controllo dell’autenticazione con Spring MVC e Handler Interceptor). L’immagine seguente mostra una visione ad alto livello dell’architettura e dell’interazione client-server.

giuseppe-urso-alfresco-cmis-interceptor-00

L’utente accede al front-end sampleaci ed effettua il login per aprire una sessione. Se le credenziali sono corrette, la SessionFactory di OpenCMIS accede alla service URL CMIS di Alfresco, instaura una connessione e avvia una sessione CMIS. L’Interceptor di Spring controlla tutte le richieste verso il front-end e verifica la presenza o meno di una session CMIS valida.

Un’osservazione importante a proposito dell’oggetto Session di OpenCMIS. Il wiki ufficiale riporta quanto segue.

“In order to be effective, this Session object has to be reused as much as possible! Don’t throw it away. Keep it and reuse it! OpenCMIS is thread-safe. The Session object can and should be reused across thread boundaries.”

OpenCMIS è thread-safe, gli sviluppatori consigliano vivamente di riusare il più possibile l’oggetto Session una volta che si è creata un’istanza. Se un utente autenticato correttamente, avvia una sessione CMIS, la sua istanza mantiene uno stato valido in memoria e può essere riutilizzata in un contesto multi-thread, senza il rischio di conflitti o errori di concorrenza.

In questo esempio ho utilizzato ThreadLocal per salvare lo stato di una sessione CMIS autenticata. Di seguito lo stack applicativo utilizzato in questo esempio e una roadmap concettuale.

SOURCE CODE (/giuseu/alfresco)

GIT
git clone https://gitlab.com/giuseppeurso-eu/alfresco

APPLICATION STACK

– SO CentOS 6.5 x86_64
– Alfresco Community 4.2.f
– JDK 1.7.0_07
– Apache Maven 3.0.4
– Eclipse Helios

ROADMAP

STEP 1. Progetto web con Maven
STEP 2. Spring MVC Dispatcher
STEP 3. Session CMIS e ThreadLocal
STEP 4. Session CMIS Interceptor
STEP 5. Spring Controller
STEP 6. Logging e test

STEP 1. Progetto web con Maven

Creo con Maven lo skeleton di un progetto web e lo importo in Eclipse.

$ mvn archetype:generate -DgroupId=eu.giuseppeurso.sampleaci
 -DartifactId=sample-alfresco-cmis-interceptor
 -DarchetypeArtifactId=maven-archetype-webapp

$ mvn eclipse:eclipse

giuseppe-urso-alfresco-cmis-interceptor-01

STEP 2. Spring MVC Dispatcher

Aggiungo nel file pom.xml le dipendenze a Spring MVC e definisco la DispatcherServlet di Spring nel file web.xml

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>${org.springframework-version}</version>
 <exclusions>
  <!-- Exclude Commons Logging in favor of SLF4j -->
  <exclusion>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  </exclusion>
 </exclusions>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-webmvc</artifactId>
 <version>${org.springframework-version}</version>
</dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aspects</artifactId>
 <version>${org.springframework-version}</version>
</dependency>

 

<servlet>
 <servlet-name>spring-dispatcher</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <init-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>/WEB-INF/spring/spring-dispatcher.xml</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
 <servlet-name>spring-dispatcher</servlet-name>
 <url-pattern>/</url-pattern>
</servlet-mapping>

STEP 3. Session CMIS e ThreadLocal

Aggiungo le dipendenze a OpenCMIS nel file pom.xml

<dependency>
  <groupId>org.apache.chemistry.opencmis</groupId>
  <artifactId>chemistry-opencmis-commons-api</artifactId>
  <version>0.10.0</version>
</dependency>
<dependency>
  <groupId>org.apache.chemistry.opencmis</groupId>
  <artifactId>chemistry-opencmis-client-api</artifactId>
  <version>0.10.0</version>
</dependency>
<dependency>
  <groupId>org.apache.chemistry.opencmis</groupId>
  <artifactId>chemistry-opencmis-client-impl</artifactId>
  <version>0.10.0</version>
</dependency>

Creo la classe responsabile della creazione della Session CMIS. Il metodo createSession utilizza la service-url di tipo ATOMPUB per instaurare una connessione su Alfresco. Se l’autenticazione è corretta, la sessione CMIS viene salvata in local thread.

// A ThreadLocal to save a valid CMIS session
private static ThreadLocal currentSession = new ThreadLocal() {
  protected synchronized Object initialValue() {
  return null;
  }
};
// The method to create the CMIS Session
public void createSession(String username, String passwd, String url) throws Exception {

  Session session = null;
  SessionFactory sessionFactory = SessionFactoryImpl.newInstance();

  //Something here.....

  // Create session.
  try {
  Repository ecmRepository = sessionFactory.getRepositories(parameter).get(0);
  session = ecmRepository.createSession();
  session.getDefaultContext().setCacheEnabled(true);
  } catch (CmisConnectionException e) {
  throw new Exception(e);
}

  // Save session in local thread
  currentSession.set(session);
}

Utilizzo un file di configurazione per specificare alcune proprietà come l’url al service CMIS di Alfresco. Più avanti utilizzo un ResouceBundle per accedere alle proprietà. Da notare la differenza dell’endpoint tra la versione 4.0 (commentata) e la versione 4.2 di Alfresco.

### Alfresco base url
alfresco.url=http://localhost:8080

### CMIS Endpoint for Alfresco 4.0
#alfresco.cmis.binding=/alfresco/cmisatom

### CMIS Endpoint for Alfresco 4.2
alfresco.cmis.binding=/alfresco/api/-default-/public/cmis/versions/1.1/atom

STEP 4. Session CMIS Interceptor

Implemento l’HandlerInterceptor di Spring che è responsabile di intercettare tutte le URL mappate sull’applicazione e verificare la presenza o meno di una Session CMIS valida. Il controllo della sessione avviene in preHandle (ulteriori dettagli sull’utilizzo di Spring HandlerInterceptor si trovano in questo articolo).

@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {

  String username = request.getParameter("username");
  String password = request.getParameter("password");

  // Intercepting the requests when a CmisSession is valid.
  if (SessionCmis.getCurrentSession() != null) {
     if (request.getRequestURI().equals("/sampleaci/")) {
        log.debug("Redirect to the home page.");
        response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + "/home"));
     }
  }
  //Intercepting the requests except the login and login-failed, when a CimisSession is not valid.
  else {
     if( (!request.getRequestURI().equals("/sampleaci/")) &&
         (!request.getRequestURI().equals("/sampleaci/login-failed")) &&
         (username==null && password==null) ){
         response.sendRedirect(response.encodeRedirectURL(request.getContextPath()));
     }
  }
  return true;
}

Definisco l’HandlerInterceptor nel file spring-dispatcher.xml. Come si vede dal mapping path, l’interceptor è in ascolto su tutte le url relative dell’applicazione.

<interceptors>
   <interceptor>
     <mapping path="/*"/>
     <beans:bean class="eu.giuseppeurso.sampleaci.cmis.SessionCmisInterceptor" />
   </interceptor>
</interceptors>

STEP 5. Spring Controller

Definisco i due Controller per il login e la pagina di benvenuto (il sorgente e scaricabile qui). Di seguito un estratto delle url definite dal RequestMapping e dei metodi corrispondenti

# LoginController
Mapped "{[/login],methods=[POST] --> LoginController.login
Mapped "{[/logout],methods=[GET] --> LoginController.logout
Mapped "{[/],methods=[GET] --> LoginController.showLogin
Mapped "{[/login-failed],methods=[GET] --> LoginController.loginFailed

# HomeController
Mapped "{[/home],methods=[GET] --> HomeController.home

Il LoginController processa username e password passati dall’utente e invoca il metodo createSession per creare una sessione CMIS valida.

/**
* The POST method to submit login credentials.
* @throws Exception
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(Model model, LoginForm loginForm, Locale locale) throws Exception {

  String username = loginForm.getUsername();
  String password = loginForm.getPassword();
  String repourl = settings.getString("alfresco.url") + settings.getString("alfresco.cmis.binding");

  try {
    SessionCmis session = new SessionCmis();
    session.createSession(username, password, repourl);
  } catch (Exception e) {
    logger.info("Failed to created CMIS Session");
    e.printStackTrace();
  }

  if (SessionCmis.getCurrentSession()!=null) {
   logger.info("Successfully logged in for username '"+ username+"' at: "+formattedDate);
  return "home";
  }
  else {
   logger.info("Login failed for username '"+ username+"' at: "+formattedDate);
   return "redirect:/login-failed";
  }
}

STEP 6. Logging e test

Prima di avviare l’applicazione e fare un test di accesso, è possibile configurare il livello di logging nel file log4j.xml. Il package eu.giuseppeurso.sampleaci.cmis è impostato a debug il controller a info.

<!-- Application Loggers -->
<logger name="eu.giuseppeurso.sampleaci.cmis">
   <level value="debug" />
</logger>
<logger name="eu.giuseppeurso.sampleaci.web.controller">
   <level value="info" />
</logger>

giuseppe-urso-alfresco-cmis-interceptor-02

giuseppe-urso-alfresco-cmis-interceptor-03

giuseppe-urso-alfresco-cmis-interceptor-04

giuseppe-urso-alfresco-cmis-interceptor-05

Related posts

Leave a Reply

Your email address will not be published.