Creating a portlet to integrate a CMIS repository

In this recipe, we will describe how to implement standard portlets to integrate GateIn with an existing CMIS-compliant repository for creating and searching documents.

CMIS stands for Content Management Interoperability Services. CMIS is the latest standard arising from the Organization for the Advancement of Structured Information Standards (OASIS) consortium to account for different integration scenarios among ECM vendors.

CMIS could be considered as the SQL language dedicated to repositories. Now, for the first time, there is a unique standard to exchange contents between ECM systems and client applications; it is also independent from the programming language because it is defined with two protocol bindings: REST and SOAP.

The technical committee that wrote this specification includes people behind ECM vendors such as Alfresco, Microsoft, SAP, OpenText, Adobe, IBM, Nuxeo, and so on.

Note

For more information about CMIS, please see

http://www.oasis-open.org/committees/cmis

The CMIS implementation that we will use in this recipe is based on the Java language. We will use the OpenCMIS library provided by the reference implementation of the CMIS protocol of the Apache Software Foundation: the Apache Chemistry project.

Note

More details about all the supported CMIS clients available in the Apache Chemistry project can be found at the following URL:

http://chemistry.apache.org/

Getting ready

The following are required for this recipe:

  • Maven
  • An IDE of your choice
  • GateIn-3.2.0.Final-jbossas7-preview

How to do it...

Let's start implementing a standard upload portlet for creating new documents in any CMIS-compliant repository:

  1. Create a new Maven project within your IDE, specifying Group ID: com.packtpub.gatein.cookbook.chapter12, Artifact ID: gatein-cmis-portlets, and Packaging: war.
  2. Inside the project's pom.xml, add the following dependencies:
      <dependencies>
        <dependency>
          <groupId>javax.portlet</groupId>
          <artifactId>portlet-api</artifactId>
          <version>2.0</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.apache.chemistry.opencmis</groupId>
          <artifactId>chemistry-opencmis-client-impl
          </artifactId>
          <version>0.7.0</version>
        </dependency>
        <dependency>
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.2.1</version>
        </dependency>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>servlet-api</artifactId>
          <version>2.5</version>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
          <version>3.0</version>
        </dependency>
      </dependencies>
  3. Add the following to a new file in the WEB-INF folder named web.xml:
    <?xml version="1.0"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd"
            version="2.5">
    </web-app>
  4. Create a class named CmisUploadPortlet that extends javax.portlet.GenericPortlet within a package named com.packtpub.gatein.cookbook.ecm.cmis.
  5. Add the following portlet definition with all the portlet preferences in your portlet.xml:
    <portlet>
        <portlet-name>CmisUpload</portlet-name>
        <portlet-class>com.packtpub.gatein.cookbook.ecm.cmis.CmisUploadPortlet</portlet-class>
        <supports>
          <mime-type>text/html</mime-type>
          <portlet-mode>view</portlet-mode>
          <portlet-mode>edit</portlet-mode>
        </supports>
        <portlet-info>
          <title>CMIS Upload Portlet</title>
        </portlet-info>
        <portlet-preferences>
          <preference>
            <name>endpoint</name>
            <read-only>false</read-only>
          </preference>
          <preference>
            <name>username</name>
            <read-only>false</read-only>
          </preference>
          <preference>
            <name>password</name>
            <read-only>false</read-only>
          </preference>
          <preference>
            <name>repositoryId</name>
            <read-only>false</read-only>
          </preference>
          <preference>
            <name>binding</name>
            <read-only>false</read-only>
          </preference>
        </portlet-preferences>
      </portlet>
  6. We want to manage four main actions: two for the CMIS configuration and two for the upload. This means that you need to implement a display method similar to the following:
    @RenderMode(name = "view")
        public void display(RenderRequest request, RenderResponse response)
          throws PortletException, IOException {
        response.setContentType("text/html");
        
        String forward = request.getParameter(CmisPortletConstants.FORWARD_PARAM);
        String forwardJsp = StringUtils.EMPTY;
        
        if(StringUtils.isEmpty(forward) 
            || forward.equals(CmisPortletConstants.UPLOAD_VIEW)){
          forwardJsp = CmisPortletConstants.UPLOAD_FORM_JSP_PATH;
        } else if(forward.equals(CmisPortletConstants.EDIT_OK_VIEW)){
          forwardJsp = CmisPortletConstants.CONFIG_OK_JSP_PATH;
          request.setAttribute(CmisClientConfig.ATTRIBUTE_NAME, cmisClientConfig);
        } else if(forward.equals(CmisPortletConstants.UPLOAD_OK_VIEW)){
          forwardJsp = CmisPortletConstants.UPLOAD_OK_JSP_PATH;
          String downloadUrl = request.getParameter(CmisPortletConstants.UPLOAD_OK_DOWNLOAD_URL_PARAM);
          request.setAttribute(CmisPortletConstants.UPLOAD_OK_DOWNLOAD_URL_PARAM, downloadUrl);
          request.setAttribute(CmisPortletConstants.DOC_PARAM, currentDocument);
        } else if(forward.equals(CmisPortletConstants.EDIT_VIEW)){
          forwardJsp = CmisPortletConstants.CONFIG_FORM_JSP_PATH;
          request.setAttribute(CmisClientConfig.ATTRIBUTE_NAME, cmisClientConfig);
        } else if(forward.equals(CmisPortletConstants.ERROR_VIEW)){
          forwardJsp = CmisPortletConstants.ERROR_JSP_PATH;
          String errorMessage = (String) request.getParameter(CmisPortletConstants.ERROR_MESSAGE_PARAM);
          request.setAttribute(CmisPortletConstants.ERROR_MESSAGE_PARAM, errorMessage);
        }
        
        PortletRequestDispatcher requestDispacther = getPortletContext()
            .getRequestDispatcher(forwardJsp);
        requestDispacther.include(request, response);
        }
  7. Once added to a page, the upload portlet will show an HTML form based on a Configure CMIS server button and the upload form.
  8. During the first step of the web flow, the user has to provide the CMIS configuration. The editAction will be used to change the CMIS parameters that will be stored as portlet preferences in the portal. Accordingly, the feature behind the button Configure CMIS server will store preferences as user-specific information and so the next time the CMIS settings will be taken from the portal without the user needing to retype all the parameters. The following code excerpt details the method:
    @ProcessAction(name = "editAction")
      public void editAction(ActionRequest request, ActionResponse response) 
          throws PortletException {
        
        String endpoint = request.getParameter(CmisPortletConstants.ENDPOINT_PARAM);
        String username = request.getParameter(CmisPortletConstants.USERNAME_PARAM);
        String password = request.getParameter(CmisPortletConstants.PASSWORD_PARAM);
        String repositoryId = request.getParameter(CmisPortletConstants.REPO_ID_PARAM);
        String binding = request.getParameter(CmisPortletConstants.BINDING_PARAM);
        
        //configure the CMIS client
        cmisClientConfig.setEndpoint(endpoint);
        cmisClientConfig.setUsername(username);
        cmisClientConfig.setPassword(password);
        cmisClientConfig.setRepositoryId(repositoryId);
        cmisClientConfig.setBinding(binding);
        
        PortletPreferences prefs = request.getPreferences();
        prefs.setValue(CmisPortletConstants.ENDPOINT_PARAM, endpoint);
        prefs.setValue(CmisPortletConstants.USERNAME_PARAM, username);
        prefs.setValue(CmisPortletConstants.PASSWORD_PARAM, password);
        prefs.setValue(CmisPortletConstants.REPO_ID_PARAM, repositoryId);
        prefs.setValue(CmisPortletConstants.BINDING_PARAM, binding);
        
        try {
          prefs.store();
        } catch (IOException e) {
          response.setRenderParameter(CmisPortletConstants.ERROR_MESSAGE_PARAM, e.getMessage());
          response.setRenderParameter(CmisPortletConstants.FORWARD_PARAM, CmisPortletConstants.ERROR_VIEW);
        }
        
        //setup CMIS client
        try {
          session = CmisUtils.getCmisSession(cmisClientConfig);
          request.setAttribute(CmisClientConfig.ATTRIBUTE_NAME, cmisClientConfig);
          response.setRenderParameter(CmisPortletConstants.FORWARD_PARAM, CmisPortletConstants.EDIT_OK_VIEW);
        } catch (RuntimeException e) {
          response.setRenderParameter(CmisPortletConstants.ERROR_MESSAGE_PARAM, e.getMessage());
          response.setRenderParameter(CmisPortletConstants.FORWARD_PARAM, CmisPortletConstants.ERROR_VIEW);
        }
      }
  9. All the CMIS-specific operations can be encapsulated inside a utility class named CmisUtils . The implementation of getCmisSession(config) consists of the typical creation of the repository session. It can be based on either the REST or the SOAP binding, depending on the value of the binding parameter:
    public static Session getCmisSession(CmisClientConfig cmisClientConfig) throws RuntimeException{
        try {
          SessionFactory factory = SessionFactoryImpl.newInstance();
          Map<String, String> parameters = new HashMap<String, String>();
              // Create a session
              parameters.clear();
    
              // user credentials
              parameters.put(SessionParameter.USER, cmisClientConfig.getUsername());
              parameters.put(SessionParameter.PASSWORD, cmisClientConfig.getPassword());
              
              // connection settings
              if(cmisClientConfig.getBinding().equals(BINDING_ATOM_VALUE)){
                //AtomPub protocol
                parameters.put(SessionParameter.ATOMPUB_URL, cmisClientConfig.getEndpoint());
                parameters.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
              } else if(cmisClientConfig.getBinding().equals(BINDING_SOAP_VALUE)){
                String endpoint = cmisClientConfig.getEndpoint();
                //Web Services - SOAP - protocol
                parameters.put(SessionParameter.BINDING_TYPE, BindingType.WEBSERVICES.value());
                parameters.put(SessionParameter.WEBSERVICES_ACL_SERVICE, endpoint+"/ACLService?wsdl");
                parameters.put(SessionParameter.WEBSERVICES_DISCOVERY_SERVICE, endpoint+"/DiscoveryService?wsdl");
                parameters.put(SessionParameter.WEBSERVICES_MULTIFILING_SERVICE, endpoint+"/MultiFilingService?wsdl");
                parameters.put(SessionParameter.WEBSERVICES_NAVIGATION_SERVICE, endpoint+"/NavigationService?wsdl");
                parameters.put(SessionParameter.WEBSERVICES_OBJECT_SERVICE, endpoint+"/ObjectService?wsdl");
                parameters.put(SessionParameter.WEBSERVICES_POLICY_SERVICE, endpoint+"/PolicyService?wsdl");
                parameters.put(SessionParameter.WEBSERVICES_RELATIONSHIP_SERVICE, endpoint+"/RelationshipService?wsdl");
                parameters.put(SessionParameter.WEBSERVICES_REPOSITORY_SERVICE, endpoint+"/RepositoryService?wsdl");
                parameters.put(SessionParameter.WEBSERVICES_VERSIONING_SERVICE, endpoint+"/VersioningService?wsdl");
              }
              // create session
              if (StringUtils.isEmpty(cmisClientConfig.getRepositoryId())) {
                // get a session from the first CMIS repository exposed
                List<Repository> repos = null;
                try {
                  repos = factory.getRepositories(parameters);
                  return repos.get(0).createSession();
                } catch (Exception e) {
                  throw new RuntimeException("Error during the creation of the CMIS session", e);
                }
                
              } else {
                // get a session from a specific repository
                parameters.put(SessionParameter.REPOSITORY_ID, cmisClientConfig.getRepositoryId());
                try {
                  return factory.createSession(parameters);
                } catch (Exception e) {
                  throw new RuntimeException("Error during the creation of the CMIS session", e);
                }
              }
    
            } catch (Throwable e) {
              throw new RuntimeException("Error during the creation of the CMIS session", e);
            }
      }
  10. Finally, once the CMIS session is configured and correctly instantiated, we can implement the upload feature with a specific uploadAction:
    @ProcessAction(name = "uploadAction")
      public void uploadAction(ActionRequest request, ActionResponse response) 
          throws PortletException {
          
        //retrieve a CMIS session
        if(session==null){  
          cmisClientConfig = CmisUtils.getConfigFromPrefs(request);
          if(cmisClientConfig.isEmpty()){
            editFormAction(request, response);
          } else {
            session = CmisUtils.getCmisSession(cmisClientConfig);
          }
        } else {
        
          //get content information from the upload form
          
          DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
          PortletFileUpload portletFileUpload = new PortletFileUpload(
              diskFileItemFactory);
          try {
            List fileItemList = portletFileUpload.parseRequest(request);
            Iterator fileIt = fileItemList.iterator();
            Document document = null;
            while (fileIt.hasNext()) {
              FileItem fileItem = (FileItem) fileIt.next();
              if(!fileItem.isFormField()){
                document = CmisUtils.createDocument(session, fileItem);
                break;
              }
            }
            
            currentDocument = document;
            response.setRenderParameter(CmisPortletConstants.FORWARD_PARAM, CmisPortletConstants.UPLOAD_OK_VIEW);
            
            if(document!=null){
              String downloadUrl = CmisUtils.getDocumentURL(session, document);
              response.setRenderParameter(CmisPortletConstants.UPLOAD_OK_DOWNLOAD_URL_PARAM, downloadUrl);
            }
            
          } catch (Exception e){
            response.setRenderParameter(CmisPortletConstants.ERROR_MESSAGE_PARAM, e.getMessage());
            response.setRenderParameter(CmisPortletConstants.FORWARD_PARAM, CmisPortletConstants.ERROR_VIEW);
          }
          
          response.setRenderParameter(
              CmisPortletConstants.FORWARD_PARAM, CmisPortletConstants.UPLOAD_OK_VIEW);
        }
      }
  11. The creation of a new document is based on the OpenCMIS library using the createDocument(session,fileItem) method; it will return a Document instance for the new content:
    public static Document createDocument(Session session, FileItem fileItem) throws IOException {
        String fileName = fileItem.getName();
        
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document");
        properties.put(PropertyIds.NAME, fileName);
    
        // content
        InputStream is = fileItem.getInputStream();
        String mimetype = fileItem.getContentType();
        ContentStream contentStream = new ContentStreamImpl(fileName, BigInteger.valueOf(fileItem.getSize()), mimetype, is);
    
        // create a major version
        Folder parent = session.getRootFolder();
        Document document = parent.createDocument(properties, contentStream, VersioningState.MAJOR);
        return document;
      }

    Note

    Notice that you need to provide a parent in order to create any new document, because in the CMIS model, the new content will be a child of an existing parent.

  12. The new document created in the CMIS repository will be presented in the JSP template with some details showing (path, name, ID, base type, and the download URL):
    <%@page import="com.packtpub.gatein.cookbook.ecm.cmis.CmisPortletConstants"%>
    <%@page import="org.apache.chemistry.opencmis.client.api.Document"%>
    <%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
    <%
    Document document = (Document) request.getAttribute(CmisPortletConstants.DOC_PARAM);
    String downloadUrl = (String) request.getAttribute(CmisPortletConstants.UPLOAD_OK_DOWNLOAD_URL_PARAM);
    %>
    <div class="portlet-section-header">
    <%if(document!=null){%>
    CMIS - Document uploaded correctly</div>
    <br />
    <div class="portlet-section-body">
      <p>The new document was uploaded correctly, here the details:</p>
      <p>Path: <%=document.getPaths().get(0)%></p>
      <p>Name: <%=document.getName()%></p>
      <p>Id: <%=document.getId()%></p>
      <p>Base type: <%=document.getBaseType().getQueryName()%></p>
      <p><a style="text-decoration: underline;" href="<%=downloadUrl%>" target="_blank">Download URL</a></p>
    <%} else {%>
    CMIS - Error during document upload</div>
    <br />
    <div class="portlet-section-body">
    <%}%>
    <form action="<portlet:actionURL name="uploadFormAction"/>" method="POST">
        <table>
          <tr>
            <td><input type="submit" name="submit" value="Upload another document" /></td>
          </tr>
        </table>
      </form>
    </div>

How it works...

The CMIS API is based on a set of services exposed by the CMIS repository. We have implemented the upload feature using a CMIS client. As you now know, in order to get a CMIS session, you need to provide the same information stored by the POJO named CmisClientConfig:

  • Endpoint of the repository: This is the HTTP URL of the CMIS endpoint for the specific binding.
  • Username: This is the username of the user session that you want to use.
  • Password: This is the password for creating the user session.
  • Repository ID (optional): This is the identifier of one of the repositories available from the CMIS endpoint. If not provided, the CMIS server will return the first repository available.
  • Binding: This is used to specify the protocol (REST or SOAP).

The domain model of CMIS is based on these basic objects:

  • Document: This type of content can contain metadata and content streams
  • Folder: This is a container for multiple documents
  • Relationship: This manages the association between a target and a source document
  • Policy: This helps to manage administrative policies for retention and similar needs

There's more...

Now let's look at how to implement a full text search portlet for your portal in order to provide an advanced search feature against a CMIS repository.

Searching documents

The search portlet that we are going to implement is based on the same CMIS configuration feature that we have described in the previous upload portlet, so we will describe only the search action.

The search portlet is based on a starting view based on an HTML form for inserting the keyword that will be used for the full text search against the CMIS repository:

  1. First, we need to get the keyword parameter, check the CMIS session, and execute the search as follows:
    @ProcessAction(name = "searchResultsAction")
      public void searchResultsAction(ActionRequest request, ActionResponse response) 
          throws PortletException {
        
        //get the keyword parameter
        String keyword = (String) request.getParameter(CmisPortletConstants.SEARCH_KEYWORD_PARAM);
        
        //check the CMIS session, if session is null we need to forward to the editAction
        if(session==null){
          cmisClientConfig = CmisUtils.getConfigFromPrefs(request);
          if(cmisClientConfig.isEmpty()){
            editFormAction(request, response);
          } else {
            session = CmisUtils.getCmisSession(cmisClientConfig);
          }
        }
        
        //execute the search against the repository
        if(session!=null){
          searchResults = CmisUtils.fullTextSearch(session, keyword);
          request.setAttribute(CmisPortletConstants.SEARCH_RESULTS_PARAM, searchResults);
          response.setRenderParameter(CmisPortletConstants.SEARCH_KEYWORD_PARAM, keyword);
          response.setRenderParameter(CmisPortletConstants.FORWARD_PARAM, CmisPortletConstants.SEARCH_RESULTS_VIEW);
        }
        
        if(StringUtils.isEmpty(keyword)){
          response.setRenderParameter(CmisPortletConstants.ERROR_MESSAGE_PARAM, "Please insert a keyword to execute the full text search");
          response.setRenderParameter(CmisPortletConstants.FORWARD_PARAM, CmisPortletConstants.ERROR_VIEW);
        }
      }
  2. The encapsulated method fullTextSearch for executing the search is based on the query method exposed by the OpenCMIS API:
    public static List<DocumentVO> fullTextSearch(Session session, String keyword){
        String statement = StringUtils.replace(CMIS_FULL_TEXT_QUERY, REPLACER, keyword);
        ItemIterable<QueryResult> searchResults = session.query(statement, false);
        List<DocumentVO> results = new ArrayList<DocumentVO>();
        for(QueryResult hit: searchResults) {
          String name = hit.getPropertyById(PropertyIds.NAME).getValues().get(0).toString();
          String objectId = hit.getPropertyById(PropertyIds.OBJECT_ID).getValues().get(0).toString();
          Document document = (Document) session.getObject(objectId);
          String url = CmisUtils.getDocumentURL(session, document);
          DocumentVO documentVo = new DocumentVO();
          documentVo.setName(name);
          documentVo.setUrl(url);
          results.add(documentVo);
        }
        return results;
      }
  3. The CMIS_FULL_TEXT_QUERY is based on this CMIS SQL statement:
    SELECT cmis:objectId, cmis:name FROM cmis:document WHERE CONTAINS('keyword')
  4. Finally, all the results in the searchResults attribute can be presented in the JSP template iterating each element in a similar way:
    <%@page import="com.packtpub.gatein.cookbook.ecm.cmis.vo.DocumentVO"%>
    <%@page import="java.util.List"%>
    <%@page import="com.packtpub.gatein.cookbook.ecm.cmis.CmisPortletConstants"%>
    <%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
    <%
    String keyword = (String) request.getAttribute(CmisPortletConstants.SEARCH_KEYWORD_PARAM);
    List<DocumentVO> searchResults = (List<DocumentVO>) request.getAttribute(CmisPortletConstants.SEARCH_RESULTS_PARAM);
    %>
    <div class="portlet-section-header">CMIS - Search results</div>
    <br/>
    <div class="portlet-section-body">
    <%if(searchResults!=null && keyword != null){%>
    <p>Results for the keyword: <strong><%=keyword%></strong></p>
    <p>Click on an item to download the document</p>
    <form action="<portlet:actionURL name="searchFormAction" />" method="POST">
      <table id="results">
    <%for(DocumentVO hit: searchResults) {%>
      
      <tr>
        <td><a style="text-decoration: underline;" href="<%=hit.getUrl()%>" target="_blank"><%=hit.getName()%></a></td>
      </tr>
    
    <%}%>
      </table>
    </form>
    </div>

See also

  • The Creating a portlet to integrate JBoss ModeShape
  • The Creating a portlet to integrate Apache JackRabbit
  • The Creating a portlet to integrate Alfresco using Spring WebScripts recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.255.36