Project

General

Profile

Feature #11841

Proposer une implémentation à CMIS

Added by Miguel Moquillon 19 days ago. Updated 17 days ago.

Status:
Resolved
Priority:
Normal
Category:
-
Start date:
11/16/2020
Due date:
% Done:

0%

Estimated time:
Livraison en TEST:
Livraison en PROD:

Description

CMIS (Content Management Interoperability Services) est un standard ouvert gérée par l'OASIS (Organization for the Advancement of Structured Information Standards), un consortium mondial qui travaille pour la standardisation dans le domaine de la documentation. Son objectif est de permettre et faciliter l'interopérabilité entres les différents systèmes de gestion de contenu. La version actuelle de la spécification est la 1.1 et peut être consultée ici : http://docs.oasis-open.org/cmis/CMIS/v1.1/os/CMIS-v1.1-os.html

A l'image de WebDAV, CMIS est aussi utilisé par la plupart des suites bureautiques majeures (MS-Office, LibreOffice, ...) pour accéder à des documents hébergés au sein d'un service distant. Au contraire de WebDAV qui est une simple extension au protocole HTTP pour l'édition collaborative et pour la gestion de documents hébergés sur un serveur HTTP distant, CMIS fournit tout un modèle de données pour naviguer, accéder et opérer sur des ressources documentaires (dossiers, documents, ...)

L'objectif à terme de cette feature est de permettre à l'utilisateur, via sa suite bureautique, d'éditer des documents dans Silverpeas. Ceci se fera en plusieurs étapes. Un premier jalon sera de permettre à l'utilisateur de pouvoir pousser, via sa suite bureautique, des documents dans une GED (Kmelia) de Silverpeas.

#1

Updated by Miguel Moquillon 19 days ago

  • Status changed from New to In progress...
#2

Updated by Miguel Moquillon 19 days ago

L'objectif de Silverpeas est de fournir un connecteur CMIS à certaines de ses ressources. Ce connecteur prend la forme d'un dépôt CMIS dans lequel les ressources pouvant être accédées sont organisées et structurées sous forme d'un arbre (un arbre CMIS). La réalisation de ce dépôt consiste donc d'implémenter d'une part un tel arbre CMIS et de fournir d'autre part des services de navigation et de manipulation d'un tel arbre.

L'arbre dans Silverpeas aura donc comme nœuds :

  • les espaces de collaboration,
  • les applications (instances de composants) Kmelia,
  • les dossiers dans Kmelia,
  • les publications (les alias de publication ne seront pas implémentés dans l'arbre).
et comme feuilles :
  • les pièces jointes aux publications.

La racine de l'arbre est un espace de collaboration virtuel qui est le conteneur de tous les espaces racines de Silverpeas.

Pour plus d'information, cf. publication https://intranoo.silverpeas.com/silverpeas/Publication/23007

La spécification CMIS impose de bien définir les liens entre chaque type de nœud et de feuilles dans l'arbre.

Pour faciliter l'implémentation d'un tel connecteur, il existe un framework qui propose déjà une implémentation de CMIS et ceci dans différents langages : Apache Chemistry et dont l'implémentation en Java est OpenCMIS

OpenCMIS fournit déjà les implémentation des différents ponts protocolaires de CMIS 1.1 qui sont AtomPub, WebServices (SOAP), et Navigateur (pour les clients Web et basé sur le JSON). Les deux premiers ponts protocolaires sont obligatoires dans CMIS. OpenCMIS propose aussi des briques de bases pour permettre la communication entre ces ponts et l'implémentation propre des différents services CMIS par Silverpeas.

La spécification CMIS définit un modèle de données et les services d'accès et de manipulation d'un tel modèle. Mais il ne propose aucun protocole sous jacent à ces services. La difficulté principale est donc de découvrir quelles sont les propriétés des objets CMIS à valoriser, quelles sont leurs valeurs nécessaires selon les cas, et quels sont les services pré-requis pour la réalisation à terme d'un autre service de CMIS. En effet, la façon d'interagir avec un dépôt CMIS est laissé à la discrétion de chaque client ... Ainsi, pour invoquer un même service, deux clients peuvent faire appel auparavant à d'autres services mais pas nécesseraiment les même et pour chacun d'elles peuvent aussi requérir différents types d'informations. Heureusement, même si ce n'est pas suffisant, qu'Apache Chemistry propose un exemple d'implémentation d'un dépôt CMIS (FileShare Server) pour nous aider à débroussailler tout ça, et un Workbench CMIS qui permet de tester la validation de notre implémentation CMIS.

#3

Updated by Miguel Moquillon 19 days ago

L'implémentation du dépôt CMIS de Silverpeas s'est faite en plusieurs temps. D'abord, il a fallu définir un modèle de données dans lequel chaque objet exposé dans l'arbre CMIS est une représentation d'un type de ressource dans Silverpeas. Ensuite, il a fallu poser un pont entre le mécanisme d'authentification et d'autorisation de CMIS avec celui de Silverpeas, et pour finir un socle de base a été posé afin de permettre l'exploration de l'arbre CMIS au sein duquel sont exposés les espaces de collaboration, des instances de composant (plus exactement les applications Kmelia), des dossiers Kmelia, des publications dans Kmelia et enfin les pièces jointes à ces publications.

Le modèle de données

L'objectif de ce modèle de données est de fournir à OpenCMIS les objets de Silverpeas qui sont exposés dans l'arbre CMIS. OpenCMIS s'attend à ce que ce modèle soit conforme à la spécification CMIS. Pour ce faire, pour chaque type de ressource dans Silverpeas a été défini un type particulier d'objet CMIS, dont le type CMIS est dérivé d'un des types de base du modèle de données CMIS :
  • CmisObject est le type de base de tous les objets Silverpeas exposés et qui définit toutes les propriétés CMIS partagées par les objets de Silverpeas exposés. Ce dernier implémente la classe ObjectDataImpl d'OpenCMIS.
  • CmisFile , qui étend CmisObject , est le type de base de tous les objets qui soient enfichables dans un arbre CMIS. Il implémente les propriétés propres à ce type d'objet (quel est le parent, quel est le chemin dans l'arbre, ...),
  • CmisFolder , qui étend CmisFile , est le type de base de base de tous les objets enfichables qui peuvent contenir d'autres objets CmisFile,
  • Space , qui étend CmisFolder , représente un espace collaboratif dans Silverpeas et ne peut contenir comme objets enfants que d'autres espaces et des applications,
  • Application , qui étend CmisFolder , représente une instance de composant (une application) de Silverpeas et ne peut contenir comme objets enfants que des dossiers de contributions et des publications (cf. ci-après),
  • ContributionFolder , qui étend CmisFolder , représente un dossier de contributions dans une application de Silverpeas et ne peut contenir comme objets enfants que d'autres dossiers de contributions ou des publications,
  • Publication , qui étend CmisFolder , représente une publication dans une application de Silverpeas et ne peut contenir comme objets enfants que des documents,
  • DocumentFile , qui étend CmisFile , représente un document attaché à une publication. Actuellement, seuls les fichiers joints sont pris en compte.

Les applications qui utilisent des dossiers (NodeDetail) pour permettre la catégorisation des contributions des utilisateurs utilisent en général un dossier racine. Ce dossier racine est en fait virtuel est représente, sous forme de dossier, l'application même en tant que conteneur de contributions (ici des dossiers et des publications). Il est attendu que le dossier racine, virtuel, ne soit jamais exposé dans l'arbre CMIS.

Afin de faciliter la conversion entre les objets Silverpeas et les objets CMIS, une fabrique a été définie : CmisObjectFactory.

A côté de ce modèle de données propre à notre implémentation, un pont a été posé entre OpenCMIS et notre dépôt CMIS afin de permettre à celui-ci d'accéder à notre arbre d'objets. Ce pont comprend comme classes :
  • SilverpeasCmisService qui étend la classe OpenCMIS AbstractCmisService et dont le cycle de vie est gérée par CDI,
  • SilverpeasCmisServiceFactory qui étend la classe OpenCMIS AbstractServiceFactory et qui accède programmatiquement à CDI pour retourner une instance de SilverpeasCmisService,
  • SilverpeasCmisRepository qui est notre implémentation de dépôt CMIS et qui ne peut être accédée que par une instance de SilverpeasCmisService . Pour réaliser ses différentes fonctions, il utilise les services des classes ci-après,
  • SilverpeasCmisTypeManager gère les types CMIS (et leurs propriétés) supportés par notre dépôt dans Silverpeas,
  • SilverpeasCmisObjectManager gère l'accès aux ressources de Silverpeas et à leur conversion, via la fabrique CmisObjectFactory, en des objets CMIS.

La fabrique SilverpeasCmisServiceFactory est enregistrée auprès d'OpenCMIS. C'est à lui qu'OpenCMIS, pour chaque requête d'un client, demande une instance de AbstractCmisService afin de lui déléguer la requête.

Authentification et autorisation

Authentification

Le framework OpenCMIS est bien fait. S'il existe différente façon de pouvoir brancher OpenCMIS avec notre mécanisme d'authentification (la plus simple étant bien entendue via JAAS), celle qui a été retenue, au regard d'une réalisation maison du système d'authentification, est celle des wrappers d'objets CmisService (une interface d'OpenCMIS). SilverpeasCmisService étend indirectement cette interface. Le système de wrapper dans CMIS est en fait un pipeline de CmisService, chacun réalisant une tâche particulière avec en bout de chaîne notre SilverpeasCmisService ; c'est une implémentation du patron Chaîne de Responsabilités. Dans notre cas, c'est la classe CmisUserAuthenticator qui sera invoquée, par OpenCMIS, à chaque session ou invocation de services CMIS. Ce dernier invoque notre mécanisme d'authentification par l'intermédiaire d'un objet UserPrivilegeValidation.

Aussi, pour qu'un utilisateur s'authentifie, il doit donc passer ses crédences sous la forme suivante :
  • identifiant : <login de l'utilisateur>@domain<identifiant de domaine>
  • mot de passe : son mot de passe Silverpeas

Autorisation

Dans OpenCMIS, c'est à la charge de l'implémenteur du dépôt CMIS de s'assurer que les utilisateurs sont autorisés d'accéder aux objets exposés. Comme pour l'authentification, le mécanisme d'autorisation de Silverpeas est maison et ne s'inscrit à aucun standard d'autorisation de JEE. Heureusement, le système d'autorisation dans Silverpeas a peu à peu évolué pour offrir une interface d'autorisation générique, AccessController, qui se décline en une implémentation pour chaque type de ressource pour lequel une vérification d'accès doit être faite. Malheureusement, de par sa conception, il est nécessaire à l'appelant de connaître, pour un type de ressource donné dans Silverpeas, l'AccessController à invoquer. Toutefois, si l'objet implémente l'interface Securable, alors l'appel au bon AccessController peut lui être délégué. Afin de simplifier l'accès au contrôle d'accès, deux choses ont été faites :

  • pour notre implémentation CMIS, un registre de contrôleurs d'accès, AccessControllerRegister , a été défini. Ce dernier, à partir du type de ressource à accéder, retourne une instance d' AccessController. S'il n'existe aucun AccessController associé dans le registre pour un type de ressource donné, alors une instance de GrantedAccessController, qui autorise par défaut l'accès, est retournée. Afin de vérifier de façon uniforme qu'un utilisateur puisse accéder à une ressource donnée, quelque soit le contrôleur d'accès sous-jacent, une nouvelle méthode a été ajoutée à l'interface AccessController :
    /**
       * Checks if the specified user may access the object with the specified identifier.
       * @param userId the unique identifier of the user.
       * @param id the unique identifier of the object to be accessed in Silverpeas.
       * @return true if access is granted - false otherwise.
       */
      boolean isUserAuthorized(String userId, ResourceIdentifier id);
    

    Toutes les implémentations de l'interface AccessController ont été en conséquence modifiées pour implémenter cette nouvelle méthode.
  • les types de ressources dans Silverpeas qui sont exposées dans notre arbre CMIS et qui n'implémentent pas encore l'interface Securable satisfont désormais celle-ci : SpaceInst, SpaceInstLight et SilverpeasSharedComponentInstance.

Socle pour accéder aux objets exposés dans Silverpeas

Début d'homogénéisation dans la manipulation des objets dans Silverpeas

La difficulté principale dans l'implémentation d'un dépôt CMIS est qu'il n'y a pas, dans Silverpeas, d'interfaces génériques et transversales d'accès et de manipulation des différents types de ressources.

Afin de faciliter l'accès et la manipulation des ressources de Silverpeas et donc d'éviter de complexifier notre implémentation de CMIS à cause de l'hétérogénéité dans la manipulation de celles-ci, le plus gros travail a été d'introduire des interfaces génériques et d'y faire dériver les ressources de Silverpeas à exposer dans l'arbre CMIS.

  • D'abord l'interface Identifiable qui définit l'ensemble des objets dans Silverpeas qui soient uniquement et globalement identifiable :
    /**
     * An object in Silverpeas that is identifiable by a unique identifier encoded as a String. For a
     * better precision in the representation of the identifier, id est when such an identifier is a
     * complex object, then prefer to use the {@link SilverpeasResource} interface.
     * @author mmoquillon
     */
    public interface Identifiable {
    
      /**
       * Gets the unique identifier of the object in Silverpeas. If he's also identified by a local
       * identifier, then this method should returns the one global to Silverpeas.
       * @return the identifier encoded as a String. If the identifier is a complex one, that is made up
       * of several identification parts, then the returned representation should take care of such
       * a structure.
       */
      String getId();
    }
    

    La plupart des objets dans Silverpeas étendent cette interface. La conséquence pour certains d'entre eux, comme NodeDetail , est que leur identifiant n'est plus sous forme d'un nombre (qui est un détail technique) mais sous forme de chaîne de caractères, ce qui permet d'encoder différentes formes d'identifant de manière générique.
  • l'interface Nameable qui définit l'ensemble des objets de Silverpeas qui comportent un nom et une description courte :
    /**
     * A nameable object in Silverpeas is an object that has both a name and a short textual description
     * about it.
     * @author mmoquillon
     */
    public interface Nameable {
    
      /**
       * Gets the name of the object in Silverpeas.
       * @return the name or the title of the object.
       */
      String getName();
    
      /**
       * Gets the description about the object in Silverpeas.
       * @return a short description about the object.
       */
      String getDescription();
    }
    

    La classe AbstractI18NBean et l'interface Contribution, entre autre, satisfont cette interface. Ce qui fait que les ressources dans Silverpeas qui ont donc un nom et une description comme les espaces, les instances de composant, les publication, etc. implémentent celle-ci.
  • l'interface SilverpeasResource qui représentent l'ensemble des ressources dans Silverpeas, contributions comprises :
    /**
     * A resource managed in Silverpeas that is uniquely identifiable. This interface is the more
     * generic representation of a resource of any type managed in Silverpeas. All conceptual
     * resources in use in Silverpeas should implement this interface. It encapsulates the more
     * generic methods a unique identifiable resource in Silverpeas should satisfy.
     * @author mmoquillon
     */
    public interface SilverpeasResource extends Nameable {
    
      /**
       * Gets the unique identifier of this resource.
       * @return the {@link ResourceIdentifier} object representing the unique identifier of the
       * resource.
       */
      <T extends ResourceIdentifier> T getIdentifier();
    
      /**
       * Gets the date at which the resource has been created.
       * @return the date of creation of the resource.
       */
      Date getCreationDate();
    
      /**
       * Gets the date at which the resource has been lastly updated. If the resource doesn't have such
       * an information, then this method should return the date of the resource creation.
       * @return the date of the last update of the resource.
       */
      Date getLastUpdateDate();
    
      /**
       * Gets the user that has created the resource.
       * @return a {@link User} in Silverpeas.
       */
      User getCreator();
    
      /**
       * Gets the user that has lastly updated the resource. If the resource doesn't have such an
       * information, then this method should return the user that has created the resource.
       * @return a {@link User} in Silverpeas.
       */
      User getLastUpdater();
    }
    

    L'interface Contribution étend cette interface. Aussi certaines de ces méthodes ont été renommées (par exemple, getLastModifier() en getLastUpdater() )
  • l'interface Translatable qui représente l'ensemble des ressources qui supportent le l10n et qui peuvent donc avoir une ou plusieurs traductions dans différentes langues de certaines de leurs propriétés :
    /**
     * An object in Silverpeas whose the textual properties are translatable in different languages.
     * In this case, the object supports the localization (l10n).
     * @author mmoquillon
     */
    public interface Translatable {
    
      /**
       * Gets a translation in the specified language about some textual properties of the object.
       * If no such translation exists, then returns the default translation of the object.
       * @param language the ISO 631-1 code of a language.
       * @param <T> the concrete type of the translation.
       * @return a translation of the object in the given language. Can be never null.
       */
      <T extends Translation> T getTranslation(final String language);
    }
    

    Les interfaces I18NBean , I18nContribution étendent cette interface.
  • l'interface LocalizedResource qui représente l'ensemble des ressources de Silverpeas (SilverpeasResource) dotée de traductions de certaines de leurs propriétés :
    /**
     * An identifiable resource in Silverpeas with localization support.
     * @author mmoquillon
     */
    public interface LocalizedResource extends SilverpeasResource, Translatable {
    
      @Override
      ResourceTranslation getTranslation(final String language);
    }
    
  • l'interface ResourceTranslation qui représente la traduction identifiable d'une ressource donnée et qui étend donc Translation et Identifiable :
    /**
     * Translation about the properties of a resource in Silverpeas.
     * @author mmoquillon
     */
    public interface ResourceTranslation extends Translation, Identifiable {
    
      /**
       * Gets the name of the resource in the underlying language.
       * @return the localized name or title of the resource.
       */
      String getName();
    
      /**
       * Gets the description about the resource in the underlying language.
       * @return a localized short description about the resource.
       */
      String getDescription();
    }
    
  • l'interface Folder qui représente une contribution dans Silverpeas pouvant contenir d'autres contributions :
    /**
     * A folder is a contribution with folding capabilities that can contain others contributions,
     * folders included. The folder is dedicated to be used to categorize others
     * contributions, with each sub-folders being a refinement of the categorization.
     * <p>
     * The links between each folders in a given application shape a tree of folders in which the leaves
     * are the non-folder contributions. Such a tree must be rooted to a single folder, a virtual one,
     * representing in fact the application itself as a container of its contributions.
     * </p>
     * @author mmoquillon
     */
    public interface Folder extends Contribution {
    
      /**
       * Is this folder the root one? A root folder represents the application itself as the container
       * of its contributions.
       * @return true if this folder is a root one. False otherwise.
       */
      boolean isRoot();
    
      /**
       * Is this folder a child of another folder? If no, then the folder is either an orphan one or
       * a root.
       * @return true if this folder is a child of another one. False otherwise.
       */
      boolean isChild();
    
      /**
       * Is this folder a dedicated one for unclassified, uncategorized contributions?
       * @return true if this folder is for gathers all the uncategorized contributions. False
       * otherwise.
       */
      boolean isUnclassified();
    
      /**
       * Is this folder a dedicated one for removed contributions? In some applications supporting the
       * categorization of contributions with folders, the deletion of contributions is just a moving of
       * them into a special folder acting then like a bin of removed contributions.
       * @return true if this folder is a bin of removed contributions. False otherwise.
       */
      boolean isBin();
    }
    

    NodeDetail implémente cette interface
  • l'interface Attachment qui represente une contribution jointe à une autre, comme par exemple un document (SimpleDocument) à une publication :
    /**
     * An attachment is a document file that is attached to a contribution in Silverpeas. The document
     * file is also a user contribution.
     * @author mmoquillon
     */
    public interface Attachment extends Contribution {
    
      /**
       * Gets the title of the document. The title is either the name set explicitly by the user to the
       * attachment or the title fetched from the document metadata itself or the filename whether the
       * previous data weren't provided.
       * @return the title of the document.
       */
      @Override
      String getTitle();
    
      /**
       * Gets the content type of this attached document file as a predefined MIME type.
       * @return the MIME type of the document.
       */
      String getContentType();
    
      /**
       * Gets the path of the icon representing either the attachment itself or its content type.
       * @return the path of the icon representing this attachment.
       */
      String getDisplayIcon();
    
      /**
       * Gets the name of the document file as stored in the filesystem. The filename can differ from
       * the contribution name that is the title of the document.
       * @return the name of the document file in the filesystem.
       */
      String getFilename();
    
      /**
       * Gets the size of the document file in the filesystem.
       * @return the size in bytes.
       */
      long getSize();
    
      /**
       * Gets the absolute path of the document file in the filesystem.
       * @return the path of the attachment in the filesystem.
       */
      String getAttachmentPath();
    
      /**
       * Is this attachment versioned? A document is versioned if each change is historized and comes to
       * a new minor or major version.
       * @return true if this attachment is versioned, false otherwise.
       */
      boolean isVersioned();
    
      /**
       * Gets the minor part of the document version. If the attachment isn't versioned, then zero
       * value is returned.
       * @return the minor version part of the document.
       */
      int getMinorVersion();
    
      /**
       * Gets the major part of the document version. If the attachment isn't versioned, then zero
       * value is returned.
       * @return the major version part of the document.
       */
      int getMajorVersion();
    
      /**
       * Gets the version of the document. It is a concatenation of the major and of the minor version
       * with a dot as separator.
       * @return the version of the attachment or "0.0" if the document isn't versioned.
       */
      default String getVersion() {
        return getMajorVersion() + "." + getMinorVersion();
      }
    
    }
    

    La classe SimpleDocument, et donc HistorisedDocument, implémente cette interface
  • l'interface LocalizedAttachment qui représente un Attachment avec support du l10n :
    /**
     * An attachment localized in a given language.
     * @author mmoquillon
     */
    public interface LocalizedAttachment extends Attachment, LocalizedContribution {
    
    }
    

    la classe SimpleDocument, et donc HistorisedDocument, implémente cette interface mais aussi LocalizedResource et ResourceTranslation, ce qui en fait à la fois une contribution traduisible dotée de sa propre traduction dans une langue donnée.
  • la classe Document qui représente un document joint à une autre contribution et qui centralise l'ensemble de ses traductions possibles, chacune dans un fichier document dédié et représentée par un SimpleDocument :
    /**
     * A document as an attachment to a given contribution and gathering for a same document all of its
     * translations, each of them represented by a different ({@link SimpleDocument} instances. These
     * different translations can be get with the {@link Document#getTranslation(String)} method.
     * The properties of a {@link Document} instance are those of the document master that is either the
     * single document file (in the case there is only one translation) or the document file written in
     * the default language of Silverpeas (see {@link I18n#getDefaultLanguage()} or the first
     * translation found (if no translation exists for the default language).
     * <p>
     * The {@link Document} class is a way to get and use attachments of a contribution without any
     * knowledge about the language in which it is written.
     * </p>
     * @author mmoquillon
     */
    public class Document implements LocalizedAttachment, LocalizedResource {
    
      private final ContributionIdentifier id;
      private final SimpleDocument master;
    
      /**
       * Constructs a new document with the specified identifier.
       * @param id a unique identifier of a document.
       */
      public Document(final ContributionIdentifier id) {
        this.id = id;
        this.master = selectTranslation(null);
      }
    
      /**
       * Constructs a new document from the specified document file to be used as the master of the
       * document.
       * @param master document file to use as master.
       */
      public Document(final SimpleDocument master) {
        this.id = master.getIdentifier();
        this.master = master;
      }
    
      @Override
      public ContributionIdentifier getIdentifier() {
        return id;
      }
    
      /**
       * Gets the contribution to which this document is attached.
       * @return a {@link ContributionIdentifier} instance about an unknown contribution type.
       */
      public ContributionIdentifier getSourceContribution() {
        SimpleDocument document = getMasterDocument();
        return ContributionIdentifier.from(document.getInstanceId(), document.getForeignId(),
            CoreContributionType.UNKNOWN);
      }
    
      @Override
      public String getTitle() {
        return getMasterDocument().getTitle();
      }
    
      @Override
      public String getContentType() {
        return getMasterDocument().getContentType();
      }
    
      @Override
      public String getDisplayIcon() {
        return getMasterDocument().getDisplayIcon();
      }
    
      @Override
      public String getFilename() {
        return getMasterDocument().getFilename();
      }
    
      @Override
      public long getSize() {
        return getMasterDocument().getSize();
      }
    
      @Override
      public String getAttachmentPath() {
        return getMasterDocument().getAttachmentPath();
      }
    
      @Override
      public boolean isVersioned() {
        return getMasterDocument().isVersioned();
      }
    
      @Override
      public int getMinorVersion() {
        return getMasterDocument().getMinorVersion();
      }
    
      @Override
      public int getMajorVersion() {
        return getMasterDocument().getMajorVersion();
      }
    
      @Override
      public SimpleDocument getTranslation(final String language) {
        SimpleDocument translation;
        if (getMasterDocument().getLanguage().equals(language)) {
          translation = master;
        } else {
          translation = selectTranslation(language);
        }
        return translation == null ? master : translation;
      }
    
      /**
       * Gets all the translations available for this document.
       * @return a list of {@link SimpleDocument} instances, each of them being a document file of this
       * document written in a given language. If no translations exist, otherwise if this document
       * doesn't exist yet, then an empty list is returned.
       */
      public List<SimpleDocument> getAllTranslations() {
        AttachmentService service = AttachmentService.get();
        SimpleDocumentPK pk = new SimpleDocumentPK(id.getLocalId(), id.getComponentInstanceId());
        return I18NHelper.getLanguages()
            .stream()
            .map(l -> service.searchDocumentById(pk, l))
            .collect(Collectors.toList());
      }
    
      @Override
      public Date getCreationDate() {
        return getMasterDocument().getCreationDate();
      }
    
      @Override
      public Date getLastUpdateDate() {
        return getMasterDocument().getLastUpdateDate();
      }
    
      @Override
      public User getCreator() {
        return getMasterDocument().getCreator();
      }
    
      @Override
      public User getLastUpdater() {
        return getMasterDocument().getLastUpdater();
      }
    
      private SimpleDocument getMasterDocument() {
        return this.master;
      }
    
      private SimpleDocument selectTranslation(final String language) {
        String lang = StringUtil.isDefined(language) ? language : I18NHelper.defaultLanguage;
        AttachmentService service = AttachmentService.get();
        SimpleDocumentPK pk = new SimpleDocumentPK(id.getLocalId(), id.getComponentInstanceId());
        SimpleDocument document = service.searchDocumentById(pk, lang);
        if (document == null) {
          return I18NHelper.getLanguages()
              .stream()
              .map(l -> service.searchDocumentById(pk, l))
              .filter(Objects::nonNull)
              .findFirst()
              .orElseThrow(() -> new CmisObjectNotFoundException(
                  "No such document " + id.asString() + " in whatever language"));
        }
        return document;
      }
    }
    

Grâce à l'introduction de ces interfaces, il est plus aisé à notre implémentation CMIS de parcourirs les objets de Silverpeas qui sont exposés sans avoir à déterminer leur type concrêt. Par exemple, pour accéder au nom et à la description d'une ressource dans la langue de l'utilisateur courant :

LocalizedResource myResource = ...
Translation theTranslation = myResource.getTranslation(userLanguage);
objData.setName(theTranslation.getName());
objData.setDescription(theTranslation.getDescription());

Socle d'accès aux objets de Silverpeas

Dans CMIS, l'accès aux objets d'un dépôt se fait en naviguant le long d'un arbre où chaque nœud est un objet CMIS de type dossier et chaque feuille un document (entre autre mais pas que). Aussi, il a été nécessaire d'implémenter un socle de navigation et d'accès qui soit générique et simple à utiliser par notre implémentation CMIS. L'accès aux objets métiers de Silverpeas se fait, pour SilverpeasCmisRepository , avec une instance de SilverpeasCmisObjectManager. En fait, ce dernier ne fait que déléguer la navigation à notre socle représenté par l'interface CmisObjectsTreeWalker dont les méthodes évolueront avec le support croissant des services CMIS dans Silverpeas :

/**
 * A walker of a CMIS objects tree whose each node is mapped to a given Silverpeas organizational
 * resource or to a given user contribution. The root of the tree is a virtual container of all of
 * the Silverpeas root spaces. The walker provides a functional view to access the CMIS objects but
 * behind de scene it walks across the organizational schema of Silverpeas by using its different
 * services. Because the way to access an object in Silverpeas (and hence to browse the CMIS objects
 * tree) depends on the types of the Silverpeas objects implied in a walk of the CMIS tree, the
 * implementation of the walker is left to more concrete walkers specifically designed to handle a
 * peculiar type of a Silverpeas object.
 * @author mmoquillon
 */
public interface CmisObjectsTreeWalker {

  /**
   * Gets an instance of a {@link CmisObjectsTreeWalker}.
   * @return a {@link CmisObjectsTreeWalker} instance
   */
  static CmisObjectsTreeWalker getInstance() {
    return ServiceProvider.getSingleton(CmisObjectTreeWalkerDelegator.class);
  }

  /**
   * Gets the CMIS data of the Silverpeas object uniquely identified by the specified identifier.
   * Only the data satisfying the given filtering rules are returned.
   * @param objectId the unique identifier of a Silverpeas object.
   * @param filtering the filtering rules to apply on the data.
   * @return an {@link ObjectData} instance that provides the CMIS data of the specified Silverpeas
   * object.
   */
  CmisObject getObjectData(String objectId, Filtering filtering);

  /**
   * Gets the CMIS data of the Silverpeas object that is located at the specified path in the CMIS
   * objects tree. Only the data satisfying the given filtering rules are returned. The root of
   * the CMIS objects tree is the {@link org.silverpeas.core.cmis.model.CmisFolder#PATH_SEPARATOR}.
   * @param path the path of the object in Silverpeas to get.
   * @param filtering the filtering rules to apply on the data.
   * @return an {@link ObjectData} instance that provides the CMIS data of the specified Silverpeas
   * object.
   */
  CmisFile getObjectDataByPath(String path, Filtering filtering);

  /**
   * Gets a list of the CMIS data of the Silverpeas objects that are the direct parents of the
   * CMIS object uniquely identified by the specified identifier. Only the data satisfying the given
   * filtering rules are returned. A file-able CMIS object can have one or more parents but in
   * Silverpeas, only publications in an EDM can have more than one parent (through the aliasing
   * mechanism), so unless the object referred by the specified identifier is an aliased
   * publication, this method returns usually a list with one element.
   * @param objectId the unique identifier of a Silverpeas resource or contribution.
   * @param filtering the filtering rules to apply on the CMIS data to return.
   * @return a list of {@link ObjectParentData} elements, each of them being a wrapper of an
   * {@link ObjectData} instance with the path of the specified object relative to this parent. The
   * CMIS data are carried by the {@link ObjectData} object. If the specified object isn't
   * file-able or it is the root folder (the virtual root space in Silverpeas), then an empty list
   * is returned.
   */
  List<ObjectParentData> getParentsData(String objectId, Filtering filtering);

  /**
   * Gets a list of CMIS data of the Silverpeas objects that are children of the CMIS folder
   * uniquely identified by the specified identifier. The CMIS folder represents a Silverpeas
   * resource that is a container of others Silverpeas resources (space, component instance, ...).
   * If this method is invoked for a document instead of a folder, then an empty
   * {@link ObjectInFolderList} list should be returned.
   * @param folderId the unique identifier of a Silverpeas resource.
   * @param filtering the filtering rules to apply on the CMIS data to return.
   * @param paging the paging to apply on the elements of the list.
   * @return an {@link ObjectInFolderList} instance that is a list of {@link ObjectInFolderData}
   * elements, each of them being a decorator of an {@link ObjectData} instance with its path in
   * the CMIS repository tree (if asked by the filtering). The CMIS data are carried by the
   * {@link ObjectData} object. If the folder has no children, then an empty
   * {@link ObjectInFolderList} instance is returned.
   */
  ObjectInFolderList getChildrenData(String folderId, Filtering filtering, Paging paging);

  /**
   * Gets the CMIS objects subtree rooted to the CMIS folder uniquely identified by the specified
   * identifier. The CMIS folder represents a Silverpeas resource that is a container of others
   * Silverpeas resources (space, component instance, ...). A list of the direct children of the
   * folder ({@link ObjectInFolderContainer} instances) is returned with, for each of them,
   * if any, their own direct children and so on. Each child is described by their CMIS data
   * (a decorator {@link ObjectInFolderData} of an {@link ObjectData} instance) filtered with the
   * given filtering rules. If this method is invoked for a document instead of a folder, then
   * an empty list should be returned.
   * @param folderId the unique identifier of a Silverpeas resource.
   * @param filtering the filtering rules to apply on the CMIS data to return.
   * @param depth the maximum depth of the subtree to return from the specified folder. If -1, the
   * subtree will contain all the descendents at all depth levels. If 0, nothing. If 1, only the
   * direct children. If greater than 1 only children of the folder and descendants up to the given
   * levels deep. Any other values throws an {@link IllegalArgumentException} error.
   * @return a list of {@link ObjectInFolderContainer} elements (the direct children), each of them
   * being a container of others {@link ObjectInFolderContainer} objects (recursive walk of
   * children) and described by an {@link ObjectInFolderData} instance that is a decorator of an
   * {@link ObjectData} instance (the CMIS data) with its path in the CMIS repository tree (if asked
   * by the filtering). The CMIS data are carried by the {@link ObjectData} object. If the folder
   * has no children, then an empty list is returned.
   */
  List<ObjectInFolderContainer> getSubTreeData(String folderId, Filtering filtering, long depth);

  /**
   * Gets a stream on the content of the specified object, starting at the given offset position and
   * upto the given length.
   * @param objectId the unique identifier of the object in the CMIS objects tree.
   * @param language the ISO 631-1 code of the language of the content to fetch. If no content
   * exists in the specified language, then it is the content for the first language found that will
   * be returned (see {@link org.silverpeas.core.i18n.I18NHelper#allContentLanguageCodes}).
   * @param offset the position in bytes in the content to start the stream.
   * @param length the length in bytes of the stream, id est the length of the content to read by
   * the stream.
   * @return a {@link ContentStream} instance.
   */
  ContentStream getContentStream(String objectId, String language, long offset, long length);
}

Un CmisObjectsTreeWalker est un objet qui sait comment naviguer un sous-arbre enraciné à un type de nœud particulier ; autrement dit comment passer du noeud racine du sous-arbre courant aux noeuds enfants.
Cette interface se décline donc en autant de classes qu'il y a de types de ressources Silverpeas à exposer dans l'arbre CMIS. En effet, dans CMIS, la navigation d'un nœud à l'autre dépend du type de chacun des nœuds et chaque nœud se doit de savoir quels sont le ou les types des ressources enfant qu'il accepte. D'où une telle conception. Nous avons donc, pour chacun des types de ressources Silverpeas exposés dans l'arbre CMIS :
  • TreeWalkerForSpaceInst pour les espaces collaboratifs,
  • TreeWalkerForComponentInst pour les instances de composants (les applications),
  • TreeWalkerForNodeDetail pour les dossiers des applications,
  • TreeWalkerForPublicationDetail pour les publications,
  • TreeWalkerForSimpleDocument pour les fichiers joints aux publications.
Une classe supplémentaire, TreeWalkerSelector, est chargée de sélectionner le bon walker à partir de l'identifiant de l'objet accédé. Pour ce faire, toute une politique d'encodage d'un identifiant des ressources de Silverpeas a été mise en place pour identifier uniquement un objet dans l'arbre CMIS. Celle-ci consiste à formater l'identifiant d'un objet CMIS comme suit :
  • les espaces de collaboration et les applications : leurs identifiants propres et uniques sont pris comme tel, via l'interface ResourceIdentifier ,
  • les contributions (publication, dossier, document joint, ...) : leur identifiant ContributionIdentifier sérialisé sous forme de chaîne de caractères.

Même au sein des walkers , lorsqu'ils doivent passer le contrôle au nœuds enfants de type différent, délèguent celui-ci au bon walker via le sélecteur.

Reste que pour le walker en charge des applications, il lui faut connaître, pour une application donnée, quels sont les contributions que celle-ci gère. Pour ce faire une interface a été introduite. Cette interface doit être implémentée par chaque composant de Silverpeas pour lequel les contributions doivent être exposées dans l'arbre CMIS :

/**
 * A provider of user contributions in order to be exposed through the Silverpeas implementation of
 * the CMIS objects tree. Each application that has to exposed some of its contributions must
 * implements this interface by a CDI managed bean. The bean will be then discovered by the CMIS
 * system in order to get some of the contributions managed by the application. For doing, the
 * bean has to be annotated with the @{@link javax.inject.Named} qualifier with as value the name
 * of the application following by the suffix {@code ContributionsProvider}. The way the
 * contributions are handled in the application or the concrete type of the contribution is left
 * to the implementation details of the bean implementing this interface.
 * @author mmoquillon
 */
public interface CmisContributionsProvider {

  class Constants {
    private Constants() {
    }

    public static final String NAME_SUFFIX = "ContributionsProviderForCMIS";
  }

  /**
   * Gets the contributions that are rooted at the specified application and that are accessible to
   * the given user. For applications using {@link org.silverpeas.core.contribution.model.Folder}s
   * to categorize the content, the root contributions are those that are contained directly in the
   * root folder (id est the folder representation of the application).
   * @param appId the unique identifier of a component instance. Should throw
   * {@link org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException} exception
   * if there is no such application.
   * @param user a user in Silverpeas.
   * @return a list with the localized contributions directly accessible at
   * the root level of the application. If there is no contributions, then an empty list is
   * returned.
   */
  List<I18nContribution> getAllowedRootContributions(final ResourceIdentifier appId,
      final User user);

  /**
   * Gets the contributions that are directly in the specified folder and that are accessible to the
   * given user. If the folder or the application doesn't
   * exist, a {@link org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException}
   * exception has to be thrown.
   * @param folder a folder in the application.
   * @param user a user in Silverpeas.
   * @return a list with the localized contributions contained in the given folder. If the folder
   * doesn't have any contributions, then an empty list is returned.
   */
  List<I18nContribution> getAllowedContributionsInFolder(final ContributionIdentifier folder,
      final User user);
}

Cette interface sera amenée à s'enrichir au fur et à mesure de l'avancement du support des services CMIS par Silverpeas.

TreeWalkerForComponentInst, à partir de l'identifiant d'une application, regarde, via CDI, s'il existe une implémentation de cette interface par ladite application. Si oui, alors il considère que l'application même est exposée avec ses contributions. Sinon, l'application n'est pas exposée. Actuellement, seule Kmelia fournit une implémentation à cette interface : CmisKmeliaContributionsProvider. Aussi seule les applications Kmelia sont exposées.

#4

Updated by Miguel Moquillon 17 days ago

  • Status changed from In progress... to Resolved

Première étape accomplie. Elle comprend :

  • le dépôt CMIS de Silverpeas,
  • le modèle objet CMIS de Silverpeas dans lequel chaque objet représente une ressource de Silverpeas à exposer via CMIS :
  • les espaces de collaboration,
  • les applications Kmelia,
  • les dossiers et les publications des applications Kmelia,
  • les documents joints aux publications.
  • le mécanisme d'authentification et d'autorisation,
  • les services de navigation dans l'arbre d'objets CMIS,
  • l'accès (en lecture seule pour l'instant) du contenu des documents joints aux publications.

Dans le cadre de cette implémentation, afin de faciliter et donc d'éviter toute complexité inutile, un certains nombres d'interfaces métiers ont été introduits et l'existant a donc été modifié afin de satisfaire ces interfaces.

L'objectif de ces premiers PR est :

  • intégrer un socle de base de l'implémentation de CMIS dans Silverpeas,
  • et intégrer les améliorations dans le code existant afin qu'elle soit utilisable le plus tôt possible par les prochaines features.

Les PRs :
https://github.com/Silverpeas/silverpeas-dependencies-bom/pull/14
https://github.com/Silverpeas/Silverpeas-Core/pull/1114
https://github.com/Silverpeas/Silverpeas-Components/pull/716

Also available in: Atom PDF