From 79cb3282052731d145d212635f35fddf872ae046 Mon Sep 17 00:00:00 2001
From: dhrn <14145706+dhrn@users.noreply.github.com>
Date: Mon, 6 Apr 2020 19:51:54 +0530
Subject: [PATCH] [ADF-1881] Consolidate NodeService and NodesApiService
 (#5591)

* [ADF-1881] Consolidate NodeService and NodesApiService

* * docs added
---
 docs/core/services/node.service.md            |  14 +-
 docs/core/services/nodes-api.service.md       |  19 ++
 lib/core/form/services/node.service.spec.ts   |   2 +-
 lib/core/form/services/node.service.ts        |  65 ++-----
 .../{form => }/models/node-metadata.model.ts  |   0
 lib/core/models/public-api.ts                 |   1 +
 lib/core/services/nodes-api.service.spec.ts   | 164 ++++++++++++++++++
 lib/core/services/nodes-api.service.ts        | 121 +++++++++----
 8 files changed, 297 insertions(+), 89 deletions(-)
 rename lib/core/{form => }/models/node-metadata.model.ts (100%)
 create mode 100644 lib/core/services/nodes-api.service.spec.ts

diff --git a/docs/core/services/node.service.md b/docs/core/services/node.service.md
index b127152f11..6574c31c87 100644
--- a/docs/core/services/node.service.md
+++ b/docs/core/services/node.service.md
@@ -5,7 +5,9 @@ Status: Active
 Last reviewed: 2018-11-20
 ---
 
-# [Node Service](../../../lib/core/form/services/node.service.ts "Defined in node.service.ts")
+# [Node Service](../../../lib/core/form/services/node.service.ts "Defined in node.service.ts") **Deprecated**
+
+use [Nodes Api service](./nodes-api.service.md) instead of this.
 
 Gets Alfresco Repository node metadata and creates nodes with metadata. 
 
@@ -14,24 +16,24 @@ Gets Alfresco Repository node metadata and creates nodes with metadata.
 ### Methods
 
 -   **createNode**(name: `string`, nodeType: `string`, properties: `any`, path: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>`<br/>
-    Create a new Node from form metadata
+    (**Deprecated:** in 3.8.0, use `createNodeInsideRoot` method from NodesApiService instead. Create a new Node from form metadata) 
     -   _name:_ `string`  - [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) name
     -   _nodeType:_ `string`  - [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) type
     -   _properties:_ `any`  - [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) body properties
     -   _path:_ `string`  - Path to the node
     -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>` - The created node
 -   **createNodeMetadata**(nodeType: `string`, nameSpace: `any`, data: `any`, path: `string`, name?: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>`<br/>
-    Create a new Node from form metadata.
+    (**Deprecated:** in 3.8.0, use NodesApiService instead. Create a new Node from form metadata.) 
     -   _nodeType:_ `string`  - [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) type
     -   _nameSpace:_ `any`  - Namespace for properties
     -   _data:_ `any`  - [Property](../../../lib/content-services/src/lib/content-metadata/interfaces/property.interface.ts) data to store in the node under namespace
     -   _path:_ `string`  - Path to the node
     -   _name:_ `string`  - (Optional) [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) name
     -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>` - The created node
--   **getNodeMetadata**(nodeId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeMetadata`](../../../lib/core/form/models/node-metadata.model.ts)`>`<br/>
-    Get the metadata and the nodeType for a nodeId cleaned by the prefix.
+-   **getNodeMetadata**(nodeId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeMetadata`](../../../lib/core/models/node-metadata.model.ts)`>`<br/>
+    (**Deprecated:** in 3.8.0, use NodesApiService instead. Get the metadata and the nodeType for a nodeId cleaned by the prefix.) 
     -   _nodeId:_ `string`  - ID of the target node
-    -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeMetadata`](../../../lib/core/form/models/node-metadata.model.ts)`>` - Node metadata
+    -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeMetadata`](../../../lib/core/models/node-metadata.model.ts)`>` - Node metadata
 
 ## Details
 
diff --git a/docs/core/services/nodes-api.service.md b/docs/core/services/nodes-api.service.md
index 7715704909..23601c1148 100644
--- a/docs/core/services/nodes-api.service.md
+++ b/docs/core/services/nodes-api.service.md
@@ -36,6 +36,21 @@ Accesses and manipulates ACS document nodes using their node IDs.
     -   _nodeBody:_ `any`  - Data for the new node
     -   _options:_ `any`  - Optional parameters supported by JS-API
     -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`MinimalNode`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeMinimalEntry.md)`>` - Details of the new node
+-   **createNodeInsideRoot**(name: `string`, nodeType: `string`, properties: `any`, path: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>`<br/>
+    Create a new Node inside `-root-` folder
+    -   _name:_ `string`  - [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) name
+    -   _nodeType:_ `string`  - [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) type
+    -   _properties:_ `any`  - [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) body properties
+    -   _path:_ `string`  - Path to the node
+    -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>` - The created node
+-   **createNodeMetadata**(nodeType: `string`, nameSpace: `any`, data: `any`, path: `string`, name?: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>`<br/>
+    Create a new Node from form metadata.
+    -   _nodeType:_ `string`  - [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) type
+    -   _nameSpace:_ `any`  - Namespace for properties
+    -   _data:_ `any`  - [Property](../../../lib/content-services/src/lib/content-metadata/interfaces/property.interface.ts) data to store in the node under namespace
+    -   _path:_ `string`  - Path to the node
+    -   _name:_ `string`  - (Optional) [Node](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) name
+    -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>` - The created node
 -   **deleteNode**(nodeId: `string`, options: `any` = `{}`): [`Observable`](http://reactivex.io/documentation/observable.html)`<any>`<br/>
     Moves a node to the trashcan.
     -   _nodeId:_ `string`  - ID of the target node
@@ -51,6 +66,10 @@ Accesses and manipulates ACS document nodes using their node IDs.
     -   _nodeId:_ `string`  - ID of the target node
     -   _options:_ `any`  - Optional parameters supported by JS-API
     -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/NodePaging.md)`>` - List of child items from the folder
+-   **getNodeMetadata**(nodeId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeMetadata`](../../../lib/core/models/node-metadata.model.ts)`>`<br/>
+    Get the metadata and the nodeType for a nodeId cleaned by the prefix.
+    -   _nodeId:_ `string`  - ID of the target node
+    -   **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeMetadata`](../../../lib/core/models/node-metadata.model.ts)`>` - Node metadata
 -   **restoreNode**(nodeId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`MinimalNode`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeMinimalEntry.md)`>`<br/>
     Restores a node previously moved to the trashcan.
     -   _nodeId:_ `string`  - ID of the node to restore
diff --git a/lib/core/form/services/node.service.spec.ts b/lib/core/form/services/node.service.spec.ts
index 39d574668c..16ba3871fd 100644
--- a/lib/core/form/services/node.service.spec.ts
+++ b/lib/core/form/services/node.service.spec.ts
@@ -16,7 +16,7 @@
  */
 
 import { TestBed } from '@angular/core/testing';
-import { NodeMetadata } from '../models/node-metadata.model';
+import { NodeMetadata } from '../../models/node-metadata.model';
 import { EcmModelService } from './ecm-model.service';
 import { NodeService } from './node.service';
 import { setupTestBed } from '../../testing/setup-test-bed';
diff --git a/lib/core/form/services/node.service.ts b/lib/core/form/services/node.service.ts
index be942956a1..591d12f5d5 100644
--- a/lib/core/form/services/node.service.ts
+++ b/lib/core/form/services/node.service.ts
@@ -15,32 +15,34 @@
  * limitations under the License.
  */
 
-import { AlfrescoApiService } from '../../services/alfresco-api.service';
 import { Injectable } from '@angular/core';
-import { Observable, from } from 'rxjs';
-import { NodeMetadata } from '../models/node-metadata.model';
-import { map } from 'rxjs/operators';
-import { AlfrescoApiCompatibility, NodeEntry } from '@alfresco/js-api';
+import { Observable } from 'rxjs';
+import { NodeEntry } from '@alfresco/js-api';
+import { NodeMetadata } from '../../models/node-metadata.model';
+import { NodesApiService } from '../../services/nodes-api.service';
 
 @Injectable({
     providedIn: 'root'
 })
+/**
+ * @deprecated in 3.8.0, use NodesApiService instead.
+ */
 export class NodeService {
 
-    constructor(private apiService: AlfrescoApiService) {
-    }
+    constructor(private nodesApiService: NodesApiService) {}
 
     /**
+     * @deprecated in 3.8.0, use NodesApiService instead.
      * Get the metadata and the nodeType for a nodeId cleaned by the prefix.
      * @param nodeId ID of the target node
      * @returns Node metadata
      */
     public getNodeMetadata(nodeId: string): Observable<NodeMetadata> {
-        return from(this.apiService.getInstance().nodes.getNode(nodeId))
-            .pipe(map(this.cleanMetadataFromSemicolon));
+        return this.nodesApiService.getNodeMetadata(nodeId);
     }
 
     /**
+     * @deprecated in 3.8.0, use NodesApiService instead.
      * Create a new Node from form metadata.
      * @param path Path to the node
      * @param nodeType Node type
@@ -50,17 +52,11 @@ export class NodeService {
      * @returns The created node
      */
     public createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable<NodeEntry> {
-        const properties = {};
-        for (const key in data) {
-            if (data[key]) {
-                properties[nameSpace + ':' + key] = data[key];
-            }
-        }
-
-        return this.createNode(name || this.generateUuid(), nodeType, properties, path);
+        return this.nodesApiService.createNodeMetadata(nodeType, nameSpace, data, path, name);
     }
 
     /**
+     * @deprecated in 3.8.0, use `createNodeInsideRoot` method from NodesApiService instead.
      * Create a new Node from form metadata
      * @param name Node name
      * @param nodeType Node type
@@ -69,39 +65,6 @@ export class NodeService {
      * @returns The created node
      */
     public createNode(name: string, nodeType: string, properties: any, path: string): Observable<NodeEntry> {
-        const body = {
-            name: name,
-            nodeType: nodeType,
-            properties: properties,
-            relativePath: path
-        };
-
-        const apiService: AlfrescoApiCompatibility = this.apiService.getInstance();
-        return from(apiService.nodes.addNode('-root-', body, {}));
-    }
-
-    private generateUuid() {
-        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
-            const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
-            return v.toString(16);
-        });
-    }
-
-    private cleanMetadataFromSemicolon(nodeEntry: NodeEntry): NodeMetadata {
-        const metadata = {};
-
-        if (nodeEntry && nodeEntry.entry.properties) {
-            for (const key in nodeEntry.entry.properties) {
-                if (key) {
-                    if (key.indexOf(':') !== -1) {
-                        metadata [key.split(':')[1]] = nodeEntry.entry.properties[key];
-                    } else {
-                        metadata [key] = nodeEntry.entry.properties[key];
-                    }
-                }
-            }
-        }
-
-        return new NodeMetadata(metadata, nodeEntry.entry.nodeType);
+        return this.nodesApiService.createNodeInsideRoot(name, nodeType, properties, path);
     }
 }
diff --git a/lib/core/form/models/node-metadata.model.ts b/lib/core/models/node-metadata.model.ts
similarity index 100%
rename from lib/core/form/models/node-metadata.model.ts
rename to lib/core/models/node-metadata.model.ts
diff --git a/lib/core/models/public-api.ts b/lib/core/models/public-api.ts
index 5f402d7933..0516710427 100644
--- a/lib/core/models/public-api.ts
+++ b/lib/core/models/public-api.ts
@@ -34,3 +34,4 @@ export * from './identity-user.model';
 export * from './identity-role.model';
 export * from './identity-group.model';
 export * from './search-text-input.model';
+export * from './node-metadata.model';
diff --git a/lib/core/services/nodes-api.service.spec.ts b/lib/core/services/nodes-api.service.spec.ts
new file mode 100644
index 0000000000..810fed225d
--- /dev/null
+++ b/lib/core/services/nodes-api.service.spec.ts
@@ -0,0 +1,164 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { NodesApiService } from './nodes-api.service';
+import { setupTestBed } from '../testing/setup-test-bed';
+import { CoreModule } from '../core.module';
+import { AlfrescoApiService } from './alfresco-api.service';
+import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
+import { NodeMetadata } from '../models/node-metadata.model';
+
+describe('NodesApiService', () => {
+    let service: NodesApiService;
+    let apiService: AlfrescoApiServiceMock;
+
+    const MODEL_NAMESPACE = 'activitiForms';
+    const responseBody = {
+        entry: {
+            id: '111-222-33-44-1123',
+            nodeType: 'typeTest',
+            properties: {
+                test: 'test',
+                testdata: 'testdata'
+            }
+        }
+    };
+    const mockSpy = {
+        core: {
+            nodesApi: {
+                getNode: jasmine.createSpy('getNode'),
+                getNodeChildren: jasmine.createSpy('getNodeChildren'),
+                addNode: jasmine.createSpy('addNode')
+            }
+        }
+    };
+
+    setupTestBed({
+        imports: [
+            NoopAnimationsModule,
+            CoreModule.forRoot()
+        ],
+        providers: [
+            { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
+        ]
+    });
+
+    beforeEach(() => {
+        service = TestBed.get(NodesApiService);
+        apiService = TestBed.get(AlfrescoApiService);
+        spyOn(apiService, 'getInstance').and.returnValue(mockSpy);
+    });
+
+    afterEach(() => {
+        mockSpy.core.nodesApi.getNode.calls.reset();
+        mockSpy.core.nodesApi.getNodeChildren.calls.reset();
+        mockSpy.core.nodesApi.addNode.calls.reset();
+    });
+
+    it('Should return the node information', (done) => {
+        mockSpy.core.nodesApi.getNode.and.returnValue(Promise.resolve(responseBody));
+
+        service.getNode('-nodeid-').subscribe((result) => {
+            const args = [
+                '-nodeid-',
+                { 'include': ['path', 'properties', 'allowableOperations', 'permissions'] }
+            ];
+            expect(mockSpy.core.nodesApi.getNode.calls.mostRecent().args).toEqual(args);
+            expect(result).toEqual(<any> responseBody.entry);
+            done();
+        });
+    });
+
+    it('Should return the node child information', (done) => {
+        const fakeNodeList = {
+            list: {
+                entries: [
+                    { entry: { id: 'fake-node-id', name: 'fake-node-name', isFolder: true } },
+                    { entry: { id: 'fake-file-id', name: 'fake-file-name', isFolder: false } }
+                ]
+            }
+        };
+        mockSpy.core.nodesApi.getNodeChildren.and.returnValue(Promise.resolve(fakeNodeList));
+
+        service.getNodeChildren('-nodeid-', {}).subscribe((result) => {
+            const args = [
+                '-nodeid-',
+                {
+                    'include': ['path', 'properties', 'allowableOperations', 'permissions'],
+                    maxItems: 25,
+                    skipCount: 0
+                }
+            ];
+            expect(mockSpy.core.nodesApi.getNodeChildren.calls.mostRecent().args).toEqual(args);
+            expect(result).toBe(<any> fakeNodeList);
+            done();
+        });
+    });
+
+    it('Should fetch and node metadata', (done) => {
+        mockSpy.core.nodesApi.getNode.and.returnValue(Promise.resolve(responseBody));
+
+        service.getNodeMetadata('-nodeid-').subscribe((result) => {
+            const node = new NodeMetadata({
+                test: 'test',
+                testdata: 'testdata'
+            }, 'typeTest');
+            expect(result).toEqual(node);
+            done();
+        });
+    });
+
+    it('Should create a node with metadata', (done) => {
+        const data = {
+            test: 'test',
+            testdata: 'testdata'
+        };
+        mockSpy.core.nodesApi.addNode.and.returnValue(Promise.resolve(responseBody));
+
+        service.createNodeMetadata('typeTest', MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary', 'testNode').subscribe((response) => {
+            const args = [
+                '-root-',
+                {
+                    'name': 'testNode',
+                    'nodeType': 'typeTest',
+                    'properties': {
+                        'activitiForms:test': 'test',
+                        'activitiForms:testdata': 'testdata'
+                    },
+                    'relativePath': '/Sites/swsdp/documentLibrary'
+                },
+                {}
+            ];
+            expect(mockSpy.core.nodesApi.addNode.calls.mostRecent().args).toEqual(args);
+            expect(response).toBe(<any> responseBody);
+            done();
+        });
+    });
+
+    it('Should create a random name node with metadata', (done) => {
+        const uuidRegex = /[0-9a-z]{8}-[0-9a-z]{4}-4[0-9a-z]{3}-[0-9a-z]{4}-[0-9a-z]{12}/;
+        mockSpy.core.nodesApi.addNode.and.returnValue(Promise.resolve(responseBody));
+
+        service.createNodeMetadata('typeTest', MODEL_NAMESPACE, {}, '/Sites/swsdp/documentLibrary').subscribe(() => {
+            expect(mockSpy.core.nodesApi.addNode.calls.mostRecent().args[0]).toEqual('-root-');
+            expect(uuidRegex.test(mockSpy.core.nodesApi.addNode.calls.mostRecent().args[1].name)).toBe(true);
+            done();
+        });
+    });
+});
diff --git a/lib/core/services/nodes-api.service.ts b/lib/core/services/nodes-api.service.ts
index af9d8778a1..2ce0cb26bf 100644
--- a/lib/core/services/nodes-api.service.ts
+++ b/lib/core/services/nodes-api.service.ts
@@ -16,20 +16,20 @@
  */
 
 import { Injectable } from '@angular/core';
-import { NodeEntry, MinimalNode, NodePaging } from '@alfresco/js-api';
-import { Observable, from, throwError } from 'rxjs';
+import { MinimalNode, NodeEntry, NodePaging } from '@alfresco/js-api';
+import { from, Observable, throwError } from 'rxjs';
 import { AlfrescoApiService } from './alfresco-api.service';
 import { UserPreferencesService } from './user-preferences.service';
-import { catchError } from 'rxjs/operators';
+import { catchError, map } from 'rxjs/operators';
+import { NodeMetadata } from '../models/node-metadata.model';
 
 @Injectable({
     providedIn: 'root'
 })
 export class NodesApiService {
 
-    constructor(
-        private api: AlfrescoApiService,
-        private preferences: UserPreferencesService) {}
+    constructor(private api: AlfrescoApiService,
+                private preferences: UserPreferencesService) {}
 
     private get nodesApi() {
         return this.api.getInstance().core.nodesApi;
@@ -50,11 +50,9 @@ export class NodesApiService {
             include: [ 'path', 'properties', 'allowableOperations', 'permissions' ]
         };
         const queryOptions = Object.assign(defaults, options);
-        const promise = this.nodesApi
-            .getNode(nodeId, queryOptions)
-            .then(this.getEntryFromEntity);
 
-        return from(promise).pipe(
+        return from(this.nodesApi.getNode(nodeId, queryOptions)).pipe(
+            map(this.getEntryFromEntity),
             catchError((err) => throwError(err))
         );
     }
@@ -72,10 +70,8 @@ export class NodesApiService {
             include: [ 'path', 'properties', 'allowableOperations', 'permissions' ]
         };
         const queryOptions = Object.assign(defaults, options);
-        const promise = this.nodesApi
-            .getNodeChildren(nodeId, queryOptions);
 
-        return from(promise).pipe(
+        return from(this.nodesApi.getNodeChildren(nodeId, queryOptions)).pipe(
             catchError((err) => throwError(err))
         );
     }
@@ -88,11 +84,8 @@ export class NodesApiService {
      * @returns Details of the new node
      */
     createNode(parentNodeId: string, nodeBody: any, options: any = {}): Observable<MinimalNode> {
-        const promise = this.nodesApi
-            .addNode(parentNodeId, nodeBody, options)
-            .then(this.getEntryFromEntity);
-
-        return from(promise).pipe(
+        return from(this.nodesApi.addNode(parentNodeId, nodeBody, options)).pipe(
+            map(this.getEntryFromEntity),
             catchError((err) => throwError(err))
         );
     }
@@ -122,11 +115,8 @@ export class NodesApiService {
         };
         const queryOptions = Object.assign(defaults, options);
 
-        const promise = this.nodesApi
-            .updateNode(nodeId, nodeBody, queryOptions)
-            .then(this.getEntryFromEntity);
-
-        return from(promise).pipe(
+        return from(this.nodesApi.updateNode(nodeId, nodeBody, queryOptions)).pipe(
+             map(this.getEntryFromEntity),
             catchError((err) => throwError(err))
         );
     }
@@ -138,9 +128,7 @@ export class NodesApiService {
      * @returns Empty result that notifies when the deletion is complete
      */
     deleteNode(nodeId: string, options: any = {}): Observable<any> {
-        const promise = this.nodesApi.deleteNode(nodeId, options);
-
-        return from(promise).pipe(
+        return from(this.nodesApi.deleteNode(nodeId, options)).pipe(
             catchError((err) => throwError(err))
         );
     }
@@ -151,12 +139,83 @@ export class NodesApiService {
      * @returns Details of the restored node
      */
     restoreNode(nodeId: string): Observable<MinimalNode> {
-        const promise = this.nodesApi
-            .restoreNode(nodeId)
-            .then(this.getEntryFromEntity);
-
-        return from(promise).pipe(
+        return from(this.nodesApi.restoreNode(nodeId)).pipe(
+            map(this.getEntryFromEntity),
             catchError((err) => throwError(err))
         );
     }
+
+    /**
+     * Get the metadata and the nodeType for a nodeId cleaned by the prefix.
+     * @param nodeId ID of the target node
+     * @returns Node metadata
+     */
+    public getNodeMetadata(nodeId: string): Observable<NodeMetadata> {
+        return from(this.nodesApi.getNode(nodeId))
+            .pipe(map(this.cleanMetadataFromSemicolon));
+    }
+
+    /**
+     * Create a new Node from form metadata.
+     * @param path Path to the node
+     * @param nodeType Node type
+     * @param name Node name
+     * @param nameSpace Namespace for properties
+     * @param data Property data to store in the node under namespace
+     * @returns The created node
+     */
+    public createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable<NodeEntry> {
+        const properties = {};
+        for (const key in data) {
+            if (data[key]) {
+                properties[nameSpace + ':' + key] = data[key];
+            }
+        }
+
+        return this.createNodeInsideRoot(name || this.generateUuid(), nodeType, properties, path);
+    }
+
+    /**
+     * Create a new Node inside `-root-` folder
+     * @param name Node name
+     * @param nodeType Node type
+     * @param properties Node body properties
+     * @param path Path to the node
+     * @returns The created node
+     */
+    public createNodeInsideRoot(name: string, nodeType: string, properties: any, path: string): Observable<NodeEntry> {
+        const body = {
+            name: name,
+            nodeType: nodeType,
+            properties: properties,
+            relativePath: path
+        };
+        return from(this.nodesApi.addNode('-root-', body, {}));
+    }
+
+    private generateUuid() {
+        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+            const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
+            return v.toString(16);
+        });
+    }
+
+    private cleanMetadataFromSemicolon(nodeEntry: NodeEntry): NodeMetadata {
+        const metadata = {};
+
+        if (nodeEntry && nodeEntry.entry.properties) {
+            for (const key in nodeEntry.entry.properties) {
+                if (key) {
+                    if (key.indexOf(':') !== -1) {
+                        metadata [key.split(':')[1]] = nodeEntry.entry.properties[key];
+                    } else {
+                        metadata [key] = nodeEntry.entry.properties[key];
+                    }
+                }
+            }
+        }
+
+        return new NodeMetadata(metadata, nodeEntry.entry.nodeType);
+    }
+
 }