Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Rui-Nogueira
Advisor
Advisor

You might have watched the first DevTalks episode about Identity Management on SAP HANA Cloud.

Soon you'll get the next episode. It'll be around the Document Service. Until that happens I'd like to share with you some experience I've made and at the end also share some code with you to, hopefully, make it easier for you to use the document service in SAP HANA Cloud.

An OASIS In The Document Service?

Essentially the Document Service of SAP HANA Cloud is a Content Management System based on the OASIS standard  CMIS. SAP is part of the CMIS working group at OASIS and the colleague I'll be talking to in the DevTalks interview will be Florina Mueller, who's currently working in the working group to finalize version 1.1 of CMIS.

Having this open standard for our Document Service allows you to use other CMIS compliant tools, too, to access your documents stored in the cloud. This gives you the choice which tools you'd like to use to access your data stored in the cloud. And there are a lot of CMIS compliaant products out there for all kind of different devices that you can simply re-use to access your valuable data in your SAP HANA Cloud account.

First Contact

As always I started on the developer guide for SAP HANA Cloud and looked for the Document Service documentation. I know it's not always the best approach, but I just skipped the intro and started directly with the section about "Using the Document Service in a Web Application" because that's what I wanted to do.

The Borg - Resistance Is Futile?

When looking into the sample code I was a little bit scared. It really looked complex and I felt that a few highly sophisticated Borg drones developed it.

But when looking into it again you can see that the colleagues tried to provide a pretty comprehensive example in these few lines of code.

Not only does it show how you can create your own repository for your documents. The code shows you also how to create folders and documents in these folders and how to retrieve all that information back to your application.

The MyDocs Example

To give people an easier access to the document service I've started encapsulating the functionality into a helper class to help create an application that can  up- and download documents to and from the document service of SAP HANA Cloud. So you'll be able to store your own files in the cloud with SAP NetWeaver Cloud.


Please be aware that the code I'm putting here might be buggy and might not work on your system although I did my best to make it stable. So please use it at your own risk and don't blame me if it didn't work :smile: .


Oh. And by the way: I'm still building the app and it's not ready, yet. So you might feel some comments are missing in the code. In case you have that feeling you might be right :smile:


Once I finalize the app and fixed all issues I'll post it on the SAP HANA Cloud Github page.




Of course I've simplified the use case a lot. You only use one repository and one folder where you can dump your files. For some this might be a restriction, but if you see it from the perspective of trying to make use of the Document Service as quickly as possible you can start small to find out if you can make good use of the service. Once you want more you can think of other use cases and let the application grow with your needs.

But now, let's get started! For those of you who'd like to watch a video in parallel how to create this app in the Eclipse environment - here it is:

The CMIS Helper Class

This class encapsulates the low level functionality of the application. If you want to copy-and-paste it into your own application don't forget to adapt the package name at the top.

This helper class provides you with 5 public methods that you can use:

  • addDocument
  • getDocumentsList
  • deleteDocument
  • uploadDocument
  • streamOutDocument

These methods will help you later on in your servlet to add, delete and download files from your own document repository in SAP HANA Cloud. To make it easier in this example I've re-used two libraries. Please download these two libraries and copy the corresponding jar files into the WebContent/WEB-INF/lib directory of your "Dynamic Web Project" in Eclipse.


There are newer versions available for these two libraries, but these are the ones that I've used for this example and which worked.




And here comes the actual code. Just copy and paste it into the src directory of the Java Resources of your Dynamic Web Project. Ideally the name of the class should be CmisHelper under the package com.sap.cebit2013.sapnwcloud.doc.helper.


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.naming.InitialContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.Folder;
import org.apache.chemistry.opencmis.client.api.ItemIterable;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisNameConstraintViolationException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import com.sap.ecm.api.EcmService;
import com.sap.ecm.api.RepositoryOptions;
import com.sap.ecm.api.RepositoryOptions.Visibility;
public class CmisHelper {
    public Session cmisSession = null;
    // The folder name for the repository of all documents of your application
    static private String REPO_APPFOLDER = "myRepositoryAppFolder";
    // This is the unique name of that repository
    // REMEMBER: ON THE TRIAL ACCOUNT THE QUOTA FOR DOC SERVICE REPOS IS 3!
    // SO MAKE USE OF THEM WISELY AND REMEMBER THE  REPO_NAME!!!
    static private String REPO_NAME = "com.sap.saphanacloud.myScnUserId.myrepository";
    // This is the secret key that your application uses to access the repository.
    // DON'T FORGET THIS KEY!
    static private String REPO_PRIVATEKEY = "thisIsYourSecretKey";
    // Maximum size of a file (in Bytes) that you can upload at a time to your repository
    static private int MAX_FILE_SIZE = 10000000;
    // The prefix for the download URL of a file
    static private String SERVLET_DOWNLOAD_PREFIX = "?action=getfile&docid=";
    // The directory to store the uploaded files temporarily
    static private String TEMP_UPLOAD_DIR = "uploads";
    /**
     * Adds a document to the folder of your application
     *
     * @param file
     *            the file to be imported
     */
    public void addDocument(File file) throws IOException {
        // create a new file in the root folder
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document");
        properties.put(PropertyIds.NAME, file.getName());
        // Try to identify the mime type of the file
        String mimeType = URLConnection.guessContentTypeFromName(file.getName());
        if (mimeType == null || mimeType.length() == 0) {
            mimeType = "application/octet-stream";
        }
        if (file != null && file.isFile()) {
            InputStream stream = new FileInputStream(file);
            Session session = getRepositorySession();
            ContentStream contentStream = session.getObjectFactory().createContentStream(file.getName(), file.length(), mimeType, stream);
            try {
                Folder yourFolder = getFolderOfYourApp(REPO_APPFOLDER);
                yourFolder.createDocument(properties, contentStream, VersioningState.NONE);
            } catch (CmisNameConstraintViolationException e) {
                // Document exists already, nothing to do
            }
            stream.close();
        } else {
            System.out.println("Can't read-in folders, just files!");
        }
    }
    /**
     *
     * @return a list of all documents that are in the folder of your repository
     */
    public List<MyDocsDTO> getDocumentsList() {
        // Get the repository folder of your app
        Folder yourFolder = getFolderOfYourApp(REPO_APPFOLDER);
        ItemIterable<CmisObject> children = yourFolder.getChildren();
        List<Document> docs = new ArrayList<Document>();
        for (CmisObject o : children) {
            // Only add the object if it is a Document
            if (o instanceof Document) {
                Document doc = (Document) o;
                docs.add(doc);
            }
        }
        if (docs.size() > 0) {
            return convertCmisDocsToMyDocsDTOs(docs);
        } else {
            return null;
        }
    }
    /**
     * This method deletes a file via it's document id
     *
     * @param documentId
     *            the document id of the file in the CMIS system
     */
    public void deleteDocument(String documentId) {
        // Get the session
        Session session = getRepositorySession();
        if (session == null) {
            throw new IllegalArgumentException("Session must be set, but is null!");
        }
        Document doc = getDocumentById(documentId);
        doc.delete(true);
    }
    /**
     *
     * @param documentId
     *            the document id of the file in the CMIS system
     * @return The document as a CMIS Document object
     */
    private Document getDocumentById(String documentId) {
        Session session = getRepositorySession();
        if (session == null) {
            throw new IllegalArgumentException("Session must be set, but is null!");
        }
        if (documentId == null) {
            return null;
        }
        try {
            Document doc = (Document) session.getObject(documentId);
            return doc;
        } catch (CmisObjectNotFoundException onfe) {
            return null;
            // throw new Exception("Document doesn't exist!", onfe);
        } catch (CmisBaseException cbe) {
            // throw new Exception("Could not retrieve the document:" +
            // cbe.getMessage(), cbe);
            return null;
        }
    }
    /**
     * @param documentId
     *            the document id of the file in the CMIS system
     * @return the content of the file
     */
    private ContentStream getDocumentStreamById(String documentId) {
        ContentStream contentStream = null;
        Document doc = getDocumentById(documentId);
        if (doc != null) {
            contentStream = doc.getContentStream();
        }
        return contentStream;
    }
    /**
     * Initializes the document service repository
     *
     * @param uniqueName
     *            the unique name for the repository. Use a unique name with package semantics e.g. com.sap.foo.myrepo1
     * @param secretKey
     *            the secret key only known to your app.. Should be at least 10 chars long.
     * @param folder
     *            the one folder where all your documents will be stored
     * @return the corresponding CMIS session
     */
    private Session getRepositorySession() {
        try {
            try {
                // Only connect to the repository if a session hasn't been opened, yet
                if (cmisSession == null) {
                    InitialContext ctx = new InitialContext();
                    String lookupName = "java:comp/env/" + "EcmService";
                    EcmService ecmSvc = (EcmService) ctx.lookup(lookupName);
                    cmisSession = ecmSvc.connect(REPO_NAME, REPO_PRIVATEKEY);
                } else {
                    return cmisSession;
                }
            } catch (CmisObjectNotFoundException e) {
                // repository does not exist, so try to create it
                RepositoryOptions options = new RepositoryOptions();
                options.setUniqueName(REPO_NAME);
                options.setRepositoryKey(REPO_PRIVATEKEY);
                options.setVisibility(Visibility.PROTECTED);
                InitialContext ctx = new InitialContext();
                String lookupName = "java:comp/env/" + "EcmService";
                EcmService ecmSvc = (EcmService) ctx.lookup(lookupName);
                ecmSvc.createRepository(options);
                // should be created now, so connect to it
                cmisSession = ecmSvc.connect(REPO_NAME, REPO_PRIVATEKEY);
            }
            // String id = openCmisSession.getRepositoryInfo().getId();
            return cmisSession;
        } catch (Exception e) {
            System.out.println("Exception found when initializing the database. Error message:\n" + e.getMessage());
            return null;
        }
    }
    /**
     * Creates a folder. If the folder already exists nothing is done
     *
     * @param session
     *            the CMIS session
     * @param rootFolder
     *            the root folder of your repository
     * @param folderName
     *            the name of the folder to be created
     * @return returns true if the folder has been created and returns false if the folder already exists
     */
    private Folder getFolderOfYourApp(String folderName) {
        Session session = getRepositorySession();
        Folder rootFolder = session.getRootFolder();
        Folder appFolder = null;
        try {
            session.getObjectByPath("/" + folderName);
            appFolder = (Folder) session.getObjectByPath("/" + folderName);
        } catch (CmisObjectNotFoundException e) {
            // Create the folder if it doesn't exist, yet
            Map<String, String> newFolderProps = new HashMap<String, String>();
            newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder");
            newFolderProps.put(PropertyIds.NAME, folderName);
            appFolder = rootFolder.createFolder(newFolderProps);
        }
        return appFolder;
    }
    /**
     *
     * @param docs
     *            the CMIS documents that you want to convert
     * @return the representation of the documents as a List of MyDocsDTO
     */
    private List<MyDocsDTO> convertCmisDocsToMyDocsDTOs(List<Document> docs) {
        List<MyDocsDTO> result = null;
        if (docs != null) {
            result = new ArrayList<MyDocsDTO>();
            for (Document doc : docs) {
                MyDocsDTO pc = convertCmisDocToMyDocsDTO(doc);
                result.add(pc);
            }
        }
        return result;
    }
    /**
     *
     * @param doc
     *            the CMIS document that you want to convert
     * @return the MyDocsDTO representation of the document
     */
    private MyDocsDTO convertCmisDocToMyDocsDTO(Document doc) {
        MyDocsDTO result = null;
        if (doc != null) {
            result = new MyDocsDTO();
            result.setFilename(doc.getName());
            result.setMimeType(doc.getContentStreamMimeType());
            result.setAuthorName(doc.getCreatedBy());
            result.setDownloadLink(SERVLET_DOWNLOAD_PREFIX + doc.getId());
            result.setId(doc.getId());
            result.setCreationDate(doc.getCreationDate().getTime());
            result.setFileLength(doc.getContentStreamLength());
            // List<String> mydocProperties = doc.getPropertyValue("sap:tags");
        }
        return result;
    }
    /**
     * Enables the upload of a file from the client computer to the server
     *
     * @param realPathOfApp
     *            The physical path of the application on the server
     * @param request
     *            The HttpServletRquest
     * @return A file object is stored on the server in the TEMP_UPLOAD_DR
     */
    public File uploadDocument(String realPathOfApp, HttpServletRequest request) {
        File uploadedFile = null;
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        String uploadPath = realPathOfApp + File.separator + TEMP_UPLOAD_DIR;
        File path = new File(uploadPath);
        // If the path doesn't exist, yet, create it
        if (!path.exists()) {
            path.mkdir();
        }
        if (isMultipart) {
            FileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            try {
                List<?> items = upload.parseRequest(request);
                Iterator<?> iterator = items.iterator();
                while (iterator.hasNext()) {
                    FileItem item = (FileItem) iterator.next();
                    if (!item.isFormField()) {
                        String fileName = item.getName();
                        uploadedFile = new File(path + File.separator + fileName);
                        item.write(uploadedFile);
                    }
                }
            } catch (FileUploadException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return uploadedFile;
    }
    /**
     *
     * @param response
     *            The HttpServletResponse
     * @param docId
     *            The document id of the document in your repository
     */
    public void streamOutDocument(HttpServletResponse response, String docId) {
        try {
            ContentStream docStream = getDocumentStreamById(docId);
            if (docStream != null) {
                response.setContentType(docStream.getMimeType());
                IOUtils.copy(docStream.getStream(), response.getOutputStream());
                IOUtils.closeQuietly(docStream.getStream());
            } else {
                // If the document doesn't have any stream return "file-not-found" status code in http responce
                response.setStatus(404);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

The MyDocsDTO Class

In the same package you copy-and-paste this DTO class that I use to access a reduced version of all the properties that the Document class of CMIS provides.


import java.util.Date;
public class MyDocsDTO {
    public String authorName;
    public String authorEmail;
    public String id;
    public long fileLength;
    public Date creationDate;
    public String filename;
    public String downloadLink;
    public String stream;
    public String mimeType;
    public String getMimeType() {
        return mimeType;
    }
    public void setMimeType(String mimeType) {
        this.mimeType = mimeType;
    }
    public String getAuthorName() {
        return authorName;
    }
    public void setAuthorName(String authorName) {
        this.authorName = authorName;
    }
    public String getAuthorEmail() {
        return authorEmail;
    }
    public void setAuthorEmail(String authorEmail) {
        this.authorEmail = authorEmail;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public long getFileLength() {
        return fileLength;
    }
    public void setFileLength(long fileLength) {
        this.fileLength = fileLength;
    }
    public Date getCreationDate() {
        return creationDate;
    }
    public void setCreationDate(Date creationDate) {
        this.creationDate = creationDate;
    }
    public String getFilename() {
        return filename;
    }
    public void setFilename(String filename) {
        this.filename = filename;
    }
    public String getDownloadLink() {
        return downloadLink;
    }
    public void setDownloadLink(String downloadLink) {
        this.downloadLink = downloadLink;
    }
    public String getStream() {
        return stream;
    }
    public void setStream(String stream) {
        this.stream = stream;
    }
}

The HelloWorld Servlet

So let's make use of the helper class with a servlet that can add files and show a list of all files in your document repository. This is the servlet you can use. Just copy and paste it into a different package called com.sap.cebit2013.sapnwcloud.doc.servlets and name this servlet HelloWorld.


import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.sap.cebit2013.sapnwcloud.doc.helper.CmisHelper;
import com.sap.cebit2013.sapnwcloud.doc.helper.MyDocsDTO;
/**
* Servlet implementation class HelloWorld
*/
public class HelloWorld extends HttpServlet {
    private static final long serialVersionUID = 1L;
    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloWorld() {
        super();
        // TODO Auto-generated constructor stub
    }
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Read-in the action parameter
        String action = request.getParameter("action");
        // Show the list of all files
        if (action == null || action.equalsIgnoreCase("list")) {
            response.getWriter().println("<html><head><title>SAP HANA Cloud Platform: MyDocs App</title></head><body>");
            CmisHelper cmisHelper = retrieveCmisHelperClass(request);
            List<MyDocsDTO> docs = cmisHelper.getDocumentsList();
            response.getWriter().println(buildHtmlPageForShowingAllDocuments(docs));
            response.getWriter().println("</body></html>");
        }
        // If the user wants to get the file out of the repository...
        if (action != null && action.equalsIgnoreCase("getfile")) {
            String docId = request.getParameter("docid");
            CmisHelper cmisHelper = retrieveCmisHelperClass(request);
            cmisHelper.streamOutDocument(response, docId);
        }
    }
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String realPathOfApp = getServletContext().getRealPath("");
        CmisHelper cmis = new CmisHelper();
        File file = cmis.uploadDocument(realPathOfApp, request);
        cmis.addDocument(file);
        // Delete the file as it is in the temporary folder on your server
        file.delete();
        // Print out the list of all files
        // After having add the file above that file should now appear in the list
        response.getWriter().println("<html><head><title>SAP HANA Cloud Platform: MyDocs App</title></head><body>");
        CmisHelper cmisHelper = retrieveCmisHelperClass(request);
        List<MyDocsDTO> docs = cmisHelper.getDocumentsList();
        response.getWriter().println(buildHtmlPageForShowingAllDocuments(docs));
        response.getWriter().println("</body></html>");
    }
    /**
     *
     * @param docs
     * @return HTML string with the field for the file upload and the lkist of all files
     */
    private String buildHtmlPageForShowingAllDocuments(List<MyDocsDTO> docs) {
        String result = "";
        // Print the "Upload File" field prior to the list of files, so that the end user can upload files
        result += "<fieldset><legend>Upload File</legend><form action='HelloWorld'";
        result += "method='post' enctype='multipart/form-data'>";
        result += "<label for='filename'>File: </label><input id='filename' type='file' name='filename' size='50'/><br/>";
        result += "<input type='submit' value='Upload File'/></form></fieldset>";
        // And now print the list of files
        result += "<h1>List of all files</h1>";
        result += "<ul>";
        if (docs != null && docs.size() > 0) {
            for (int i = 0; i < docs.size(); i++) {
                MyDocsDTO doc = docs.get(i);
                String row = "<li>" + "<a href=" + '"' + doc.getDownloadLink() + '"' + ">" + doc.getFilename() + "<a> (" + doc.getFileLength() + " bytes)</li>";
                result += row;
            }
        }
        result += "</ul>";
        return result;
    }
    /**
     * This method ensures that an existing CmisHelper class that was saved in the HttpServletRequest is re-used whenever
     * possible instead of creating new instances of CmisHelper classes everytime you call the doGet or doPost from the
     * method
     *
     * @param request
     *            The HttpServletRequest
     * @return The CmisHelper class
     */
    private CmisHelper retrieveCmisHelperClass(HttpServletRequest request) {
        CmisHelper result = null;
        if (request != null) {
            HttpSession httpSession = request.getSession();
            if (httpSession != null) {
                CmisHelper cmisHelperHttpSession = (CmisHelper) httpSession.getAttribute("myCmisHelper");
                // If an instance of CmisHelper is already there, use it
                if (cmisHelperHttpSession != null) {
                    result = cmisHelperHttpSession;
                }
                // If there isn't one, create a new one and store it in the session of the HttpServletRequest so that it
                // can be re-used the next time
                else {
                    result = new CmisHelper();
                    httpSession.setAttribute("myCmisHelper", result);
                }
            }
        }
        return result;
    }
}

The web.xml

Under your WebContent/WEB-INF directory you'll find the web.xml file of your application.  Please add the xml snippet below into it. Ideally just before the closing web-app tag.


    <resource-ref>
        <res-ref-name>EcmService</res-ref-name>
        <res-type>com.sap.ecm.api.EcmService</res-type>
    </resource-ref>

Publish on your own SAP HANA Cloud Platform account

Now that the code is there you add this project either to your local SAP NetWeaver Cloud server or you deploy it e.g. to your SAP HANA Cloud trial account. In case you want to run it on your localhost first, you'll have to follow the instructions on the top of official documentation page "Using the Document Service in a Web Application" and install the MongoDB database on your machine (section "Infrastructure Prerequisites".

If you've sticked to my propals regardign the names and if you deployed the app to your localhost you'll find your application under http://localhost:8080/cebit2013/HelloWorld

If you click on the Browse... button a window will open up where you can select the file from your computer that you'd like to upload. Once selected click on "Upload File" and your file will show up in the list of files that are already there.

What Comes Next?

Now you can start playing around with the UI. Maybe you feel challenged and would like to implement a means to delete a file? Go ahead.

Or instead of streaming out an html page, why not creating a jQuery mobile output for your mobile device so that you can access your documents you've stored on your SAP HANA Cloud trial account wherever you are?

The upcoming DevTalk Interview

I hope you liked to play with the document service a bit. I'll be posting the next DevTalk interview with Florian Mueller next week.

Maybe until then you already got some questions that you'd like to ask. Write them down and add them as a comment to the blog that will come along with the video. Looking forward to it.

Best,

Rui

2 Comments