SAP Document Management Service Node Js Client
2023-11-24 05:26:41 Author: blogs.sap.com(查看原文) 阅读量:5 收藏

Introduction

While using SAP Document Management Service, I have always struggled to write appropriate API call codes to upload and download files and folders.

A client library would facilitate me for interacting with the SAP Document Management Services (DMS) within my application. When integrating with SAP DMS, a dedicated client library becomes indispensable due to several crucial reasons.

  • A specialized client library encapsulates these intricate details that is required for calling different DMS APIs, providing a more straightforward and structured interface for developers to interact with the DMS.
  • You can also add an additional functionality reusing the library to make your life easy.

In this blog I have tried to show you how to use a client library to connect and use SAP Document Management Service, also how to manage different tenant sessions and how to reuse them.

Prerequisites:

We are going to use a library called CMIS. This library is going to help us with the basic operation of the DMS , we are going to extend this library to add an additional capability.

First install this library

npm install cmis

Once done you can refer this documentation for usage CMIS Documentation.

Other required libraries

const axios = require('axios').default;
const FormData = require('form-data');
const cmis = require("cmis");
const jwt = require('jsonwebtoken');
const mutexify = require('mutexify/promise')

Subscribe to SAP Document Management Service

Once you have created SDM you can create a service key or bind the service to an application to get the SDM credential.

The credentials looks like this.

{
    "endpoints": {
        "ecmservice": {
            "url": "https://api-sdm-di.cfapps.<region>.hana.ondemand.com/"
        },
        "migrationservice": {
            "url": "https://sdm-migration.cfapps.<region>.hana.ondemand.com"
        }
    },
    ...
    "uaa": {
        "clientid": "*****",
        "clientsecret": "*****",
        "url": "https://*****.authentication.<region>.hana.ondemand.com",
        ....
        "credential-type": "instance-secret"
    },
    "uri": "https://api-sdm-di.cfapps.<region>.hana.ondemand.com/"
}

Let’s assume we have Session Manager (which is implemented below) that will keep track of all the session, and refreshes them from time to time and is tenant aware. It may look like this:

// const sdmCredentials = VCAP_SERVICES.sdm[0].credentials;

// create session manager
let sm = new CmisSessionManager(sdmCredentials);

let REPOSITORY_ID = "com.sap.sdm.test"
let tenantId = "provider";

// create repository
await sm.createRepositoryIfNotExists(REPOSITORY_ID, tenantId, {
        "displayName": "Name of the repository",
        "description": "Description for the repository",
        "repositoryType": "internal",
        "isVersionEnabled": "false",
        "isVirusScanEnabled": "true",
        "skipVirusScanForLargeFile": "false",
        "isThumbnailEnabled": "false",
        "isEncryptionEnabled": "false",
        "hashAlgorithms": "SHA-256",
        "isContentBridgeEnabled": "true",
        "externalId": "sg.com.ncs.document.demo"
    });

// get or create a new Session using the repo id and tenant id
// make sure to call this method everytime you perform a action on DMS, then the manager will refresh the session
let session = await sm.getOrCreateConnection(REPOSITORY_ID, "provider");

Usage

Now let us see how to use the CMIS session to create and read some documents.

Create Document

let parentObj = await session.getObjectByPath("/");
const content = "dummy-content"; // string content
let result = await session.createDocument(parentObj.succinctProperties["cmis:objectId"], content, "test.txt");

console.log(result);

Create Document From Stream (optimized document upload)

Creating document from string is very memory intensive, so let us create a document from stream, which is much less memory intensive.

let stream = fs.createReadStream("./test.txt");
let result = await session.createDocumentFromStream("/", stream, `test.txt`);
console.log(result);

Append Content to Document

let obj = await session.getObjectByPath("/test.txt");
let content = "appended-content";
let result = await session.appendContentFromString(obj.succinctProperties["cmis:objectId"], content, "test.txt");
console.log(result);

Append Content to Document (with Stream)

let obj = await session.getObjectByPath("/test.txt");
let stream = fs.createReadStream("./test-2.txt");
let result = await session.appendContentFromStream(obj.succinctProperties["cmis:objectId"], stream);
console.log(result);

Download Document ( Stream )

let obj = await session.getObjectByPath("/abcd.txt");
let result = await session.getContentStream(obj.succinctProperties["cmis:objectId"]);
let writeStream = fs.createWriteStream("./abcd.txt");
result.body.pipe(writeStream);

Create Folder

let parentObj = await session.getObjectByPath("/");
let result = await session.createFolder(parentObj.succinctProperties["cmis:objectId"], "temp");
console.log(result);

Create entire Path

let parentObj = await session.getObjectByPath("/");
let result = await session.createPath(parentObj.succinctProperties["cmis:objectId"], "/a/b/c/d");
console.log(result);

Implementations

CMIS Connection Manager

class CmisSessionManager {

    tenantSessions = new Map()
    lock = mutexify()

    constructor(sdmCredentials) {
        this.apiEndpoint = `${sdmCredentials.endpoints.ecmservice.url}browser`;
        this.sdmCredentials = sdmCredentials;
    }

    async getOrCreateConnection(repositoryName, tenantId) {
        const mapKey = `${tenantId}.${repositoryName}`;
        // to improve performance, other tenants whose session are there should not wait in lock
        if (!this.tenantSessions.has(mapKey) || this.isTokenExpired(this.tenantSessions.get(mapKey).getToken())) {
            // allow only a single instance of this code to run
            const release = await this.lock();
            // check if session is created by some other blocked execution
            if (!this.tenantSessions.has(mapKey) || this.isTokenExpired(this.tenantSessions.get(mapKey).getToken())) {
                let token = await this._fetchJwtToken();
                let session = new ExtendedCmisSession(this.apiEndpoint);
                session.setToken(token);
                await session.loadRepositories();
                session.defaultRepository = session.repositories[repositoryName];
                this.tenantSessions.set(mapKey, session);
                console.log(`${mapKey} session created.`);
            }
            // release the lock
            release();
        }

        return this.tenantSessions.get(mapKey);
    }

    async _fetchJwtToken() {
        // This is to get the oauth token , which is used to create the folder ID
        // implement your own as per requirement
        const oauthUrl = this.sdmCredentials.uaa.url;
        const oauthClient = this.sdmCredentials.uaa.clientid;
        const oauthSecret = this.sdmCredentials.uaa.clientsecret;
        const tokenUrl = oauthUrl + '/oauth/token?grant_type=client_credentials&response_type=token'
        const config = {
            headers: {
                Authorization: "Basic " + Buffer.from(oauthClient + ':' + oauthSecret).toString("base64")
            }
        }
        const res = await axios.get(tokenUrl, config);
        return res.data.access_token;
    }

    isTokenExpired(token) {
        try {
            const decodedToken = jwt.decode(token);
            if (decodedToken && decodedToken.exp) {
                const currentTimestamp = Math.floor(Date.now() / 1000); // Get the current time in seconds
                return decodedToken.exp < currentTimestamp;
            }
            return true; // Token has no expiration information
        } catch (error) {
            return true; // Token decoding failed
        }
    }

    async createRepositoryIfNotExists(repoId, tenantId, config) {
        let token = await this._fetchJwtToken();

        let session = new ExtendedCmisSession(this.apiEndpoint);
        session.setToken(token);
        await session.loadRepositories();
        if (session.repositories.hasOwnProperty(repoId)) {
            return;
        }

        config["externalId"] = repoId;

        let axiosConf = {
            method: 'post',
            url: `${this.sdmCredentials.endpoints.ecmservice.url}rest/v2/repositories`,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer ' + token
            },
            data: JSON.stringify({
                "repository": config
            })
        };

        const response = await axios.request(axiosConf);
        return response.data;

    }

}

Session with Extra optimized operations

class ExtendedCmisSession extends cmis.CmisSession {

    getToken() {
        return this.token;
    }

    async createPath(parentFolderId, path) {
        const pathElements = path.split('/');
        let currentFolderId = parentFolderId;
        let createdFolder;
        for (const folderName of pathElements) {
            if (folderName) {
                createdFolder = await this.createFolder(currentFolderId, folderName);
                currentFolderId = createdFolder.succinctProperties['cmis:objectId'];
            }
        }
        return createdFolder;
    }

    async createDocumentFromString(parentPath, content, filename) {
        let parentObj = await this.getObjectByPath(parentPath);
        return await session.createDocument(parentObj.succinctProperties["cmis:objectId"], content, filename);
    }


    async createDocumentFromStream(parentPath, content, filename) {

        let data = new FormData();
        data.append('cmisaction', 'createDocument');
        data.append('succinct', 'true');
        data.append('propertyId[0]', 'cmis:name');
        data.append('propertyValue[0]', filename);
        data.append('propertyId[1]', 'cmis:objectTypeId');
        data.append('propertyValue[1]', 'cmis:document');
        data.append('filename', filename);
        data.append('_charset', 'UTF-8');
        data.append('includeAllowableActions', 'true');
        data.append('media', content);

        const accessToken = this.token;

        let config = {
            method: 'post',
            maxBodyLength: Infinity,
            url: `${this.url}/${this.defaultRepository.repositoryId}/root${parentPath}`,
            headers: {
                'Authorization': 'Bearer ' + accessToken,
                ...data.getHeaders()
            },
            data: data
        };

        const res = await axios.request(config);

        return res.data;

    }

    async appendContentFromString(objectId, content, filename) {
        return await this.appendContentStream(objectId, content, false, filename);
    }

    async appendContentFromStream(objectId, contentStream, isLastChunk = false) {
        let data = new FormData();
        data.append('cmisaction', 'appendContent');
        data.append('succinct', 'true');
        data.append('isLastChunk', isLastChunk.toString());
        data.append('objectId', objectId);
        data.append('media', contentStream);

        const accessToken = this.token;

        let config = {
            method: 'post',
            maxBodyLength: Infinity,
            url: `${this.url}/${this.defaultRepository.repositoryId}/root`,
            headers: {
                'Authorization': 'Bearer ' + accessToken,
                ...data.getHeaders()
            },
            data: data
        };

        const response = await axios.request(config);

        return response.data;
    }

}

Conclusion

In summary, a dedicated client library for SAP Document Management Services acts as a crucial middle-ware, simplifying integration, enhancing developer experience and ensuring robust and efficient utilization of DMS functionalities within an application.


文章来源: https://blogs.sap.com/2023/11/23/sap-document-management-service-node-js-client/
如有侵权请联系:admin#unsafe.sh