Un client Java pour Nuxeo

Nuxeo est une application de Gestion Électronique de Documents (GED) qui permet, notamment, de manipuler son contenu via un client HTTP (REST) par l'usage du Nuxeo Automation Client.

La documentation étant très succincte à ce sujet et après de longues recherches pour finaliser un petit client capable de gérer les quelques opérations de base (upload d'un document, publication, création de relations), j'ai décidé de publier le résultat de mes trouvailles. Voici donc la classe du client en question :

import java.io.File;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.automation.client.Session;
import org.nuxeo.ecm.automation.client.adapters.DocumentService;
import org.nuxeo.ecm.automation.client.jaxrs.impl.HttpAutomationClient;
import org.nuxeo.ecm.automation.client.model.Document;
import org.nuxeo.ecm.automation.client.model.FileBlob;
import org.nuxeo.ecm.automation.client.model.PropertyMap;

/**
 * Used to upload files to Nuxeo
 * 
 */
public class NuxeoAutomationClient {

 // logger
 private static Log LOGGER = LogFactory.getLog("camel");

 // Nuxeo's automation client
 private HttpAutomationClient client;

 // Connection's session
 private Session session;

 private DocumentService dc;

 public NuxeoAutomationClient(String login, String mdp) throws Exception {
  client = new HttpAutomationClient(SERVER_URL);
  session = client.getSession(login, mdp);
  dc = new DocumentService(session);
 }

 public Session getSession() {
  return session;
 }

 /**
  * Upload specified file into specified folder in Nuxeo
  * 
  * @param file the file
  * @param folderName the folder
  * @param publishFile publish File?
  * @throws NuxeoServiceException all
  */
 public void upload(File file, String folderName, boolean publishFile) throws Exception {
  // First get the root document and create a new File document at
  PropertyMap pMap = new PropertyMap();
  pMap.set("dc:title", folderName);
  String uploadFolder = CAMEL_NUXEO_WS_URL + "/" + folderName;
  Document deptFolder = null;
  LOGGER.info("Looking for folder : " + uploadFolder);
  deptFolder = dc.getDocument(uploadFolder);
  LOGGER.info("Uploading to folder : " + deptFolder.getPath());

  // upload file
  // check if file exists
  Document deptDoc = null;
  String dataFileName = file.getName();
  try {
   deptDoc = dc.getDocument(uploadFolder + "/" + dataFileName);
   // If exists, delete it
   if (deptDoc != null) {
    dc.remove(deptDoc);
   }
  } catch (Exception e) { // Document not found
   // foolish exception, do nothing
  }

  pMap = new PropertyMap();
  pMap.set("dc:title", dataFileName);
  pMap.set("file:filename", dataFileName);
  deptDoc = dc.createDocument(deptFolder, "File", dataFileName, pMap);
  LOGGER.info("Uploading file : " + deptDoc.getPath());

  // upload blob
  FileBlob fb = new FileBlob(file);
  fb.setFileName(dataFileName);
  String extension = dataFileName.substring(dataFileName.indexOf(".") + 1, dataFileName.length());
  String mime = "";
  if ("txt".equals(extension)) {
   mime = "text/plain";
  } else {
   mime = "application/" + extension;
  }
  fb.setMimeType(mime);
  dc.setBlob(deptDoc, fb);

  // publish file
  if (publishFile) {
   String pubPath = NUXEO_PUB + "/" + folderName;
   LOGGER.info("Publishing to : " + pubPath);
   deptFolder = dc.getDocument(pubPath);
   // if already published, remove it
   try {
    Document old = dc.getDocument(pubPath + "/" + dataFileName);
    if (old != null) {
     dc.remove(old);
    }
   } catch (Exception e) {
    // Document not found
    // foolish exception, do nothing
   }
   dc.publish(deptDoc, deptFolder, true);
  }

  // manage metadata relations
  String metadataFile = "META_FILE.doc";
  // Create relation
  Document metaDoc = dc.getDocument(CAMEL_NUXEO_WS_URL + "/Métadonnées/" + metadataFile);
  String predicate = "http://purl.org/dc/terms/ConformsTo";
  dc.createRelation(deptDoc, predicate, metaDoc);
  LOGGER.info("Created relation : " + deptDoc.getPath() + " " + predicate + " " + metaDoc.getPath());
 }
}

Son exécution est très simple : création d'un document (avec effacement de l'existant), envoi du blob correspondant (données), publication, liaison à un autre document.

Cela peut paraître simple, mais l'usage du DocumentService n'est pas très explicite dans les documentations.

Le principal écueil ici réside dans la création de la relation entre les documents. Ici le but est de créer les liens du type (basé sur la norme DCMI) :

  • Doc1 "Est conforme à" Doc2
  • Doc2 "A pour conformité" Doc1

où, conceptuellement, "Est conforme à" et "A pour conformité" sont des liens inverses issus du même prédicat de la relation.

Il faut savoir que Nuxeo se base sur un système de vocabulaire permettant, lors de la création de la relation, de gérer automatiquement les libellés des liens inverses issus des prédicats. Ainsi, dans cet exemple, ces liens sont calculés à partir du prédicat (normé selon DCMI) : http://purl.org/dc/terms/ConformsTo

J'espère vous avoir fait économiser quelques heures de recherche!

Sources :


Fichier(s) joint(s) :

5 commentaires:

Unknown a dit…

Bonjour,

Merci pour ce billet.
A Nuxeo nous allons clairement essayer d'améliorer la doc. sur ce sujet et si vous avez des commentaires plus précis n'hésitez pas!
Par contre votre billet fait référence à la documentation de la version 5.3 qui date. Avez vous vu les versions plus récentes telles que http://doc.nuxeo.com/x/6AAz ?

Roland

Paul-Emmanuel Faidherbe a dit…

Bonjour,

Effectivement je n'avais pas vu ce lien, mais je trouve encore une fois que ces exemples manquent de clarté... L'orientation JSON + CMIS (avec l'appel direct à Document.Query) rendent les choses complexes lorsqu'il s'agit de sortir des sentiers battus. Un article complet sur DocumentService serait à mon avis plus efficace car offrirait plus de perspectives aux développeurs par l'utilisation d'API plus "verbeuses" et explicites.

Concernant la documentation, je pense qu'il y a pas mal de choses à ajouter même si je me doute que ce n'est pas une mince affaire. L'exemple du prédicat cité dans mon article est assez révélateur : à défaut de documentation, une API plus complète avec par exemple des énumérations serait appréciable...
J'en profite également pour glisser un mot sur Nuxeo Studio : je me suis fait les mêmes remarques. La documentation n'est pas très claire et l'IHM manque de petits outils tous bêtes mais indispensables pour des utilisateurs lambda (et néophytes) comme moi.

Je serai ravi de participer à cet effort de documentation et d'accessibilité, tenez moi informé si besoin! ;)

François a dit…

Salut Pef,

Tu devrais faire un petit billet sur CMIS.

Capitaine Capibara

Paul-Emmanuel Faidherbe a dit…

Salut Capitaine!

Effectivement CMIS est plutôt intéressant, notamment par l'aspect interopérabilité entre les GED. Mais c'est malheureusement à peu près tout ce que je sais à ce sujet... Je n'ai pas trop eu le temps d'approfondir.

Je suis plutôt en train de travailler sur Camel (optimisations) et plus particulièrement les interactions avec JMX.

PS: Et Capibara m'occupe encore beaucoup, c'est toujours aussi captivant! ;-)

Anonyme a dit…

Bonjour,

La documentation Nuxeo Automation Client a été remise à jour:
http://doc.nuxeo.com/x/vwIz

Merci!