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.
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.
More details about all the supported CMIS clients available in the Apache Chemistry project can be found at the following URL:
The following are required for this recipe:
Let's start implementing a standard upload portlet for creating new documents in any CMIS-compliant repository:
com.packtpub.gatein.cookbook.chapter12
, Artifact ID: gatein-cmis-portlets
, and Packaging: war
.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>
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>
CmisUploadPortlet
that extends javax.portlet.GenericPortlet
within a package named com.packtpub.gatein.cookbook.ecm.cmis
.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>
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); }
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);
}
}
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); } }
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); } }
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;
}
<%@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>
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
:
The domain model of CMIS is based on these basic objects:
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.
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:
@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); } }
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;
}
CMIS_FULL_TEXT_QUERY
is based on this CMIS SQL statement:SELECT cmis:objectId, cmis:name FROM cmis:document WHERE CONTAINS('keyword')
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>
3.143.255.36