Merge branch 'mkoller/atfxadapter' into mkoller/contextdata

Change-Id: I2ec85261050d783741c45645330fd76e0b65dc30
diff --git a/org.eclipse.mdm.application/src/main/webapp/package.json b/org.eclipse.mdm.application/src/main/webapp/package.json
index 4241d26..64fcfc6 100644
--- a/org.eclipse.mdm.application/src/main/webapp/package.json
+++ b/org.eclipse.mdm.application/src/main/webapp/package.json
@@ -34,12 +34,12 @@
     "core-js": "2.6.0",
     "file-saver": "1.3.3",
     "font-awesome": "4.7.0",
-    "ng2-split-pane": "1.3.1",
     "ngx-bootstrap": "3.1.2",
     "primeicons": "1.0.0",
     "primeng": "7.0.1",
     "rxjs": "6.3.3",
     "rxjs-compat": "6.3.3",
+    "split.js": "1.5.11",
     "tslib": "1.9.0",
     "zone.js": "0.8.26"
   },
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.module.ts
new file mode 100644
index 0000000..a3920c7
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.module.ts
@@ -0,0 +1,26 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+import { NgModule } from '@angular/core';
+import { AuthenticationService } from './authentication.service';
+import { AuthenticationStateService } from './authenticationstate.service';
+
+@NgModule({
+providers: [
+    AuthenticationStateService,
+    AuthenticationService
+]
+})
+export class AuthenticationModule {
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.service.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.service.ts
new file mode 100644
index 0000000..c94221a
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authentication.service.ts
@@ -0,0 +1,55 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Router } from '@angular/router';
+import { AuthenticationStateService, User } from './authenticationstate.service';
+import { PropertyService } from '../core/property.service';
+
+@Injectable({
+    providedIn: 'root'
+  })
+export class AuthenticationService {
+  readonly loginURL: string;
+  readonly logoutURL: string;
+  readonly currentUserURL: string;
+
+  constructor(private http: HttpClient, private router: Router, private _prop: PropertyService,
+              private authService: AuthenticationStateService) {
+
+    this.currentUserURL = _prop.getUrl('mdm/user/current');
+
+      this.loadUser().subscribe(
+          user => this.authService.setLoginUser(user),
+          error => this.authService.unsetLoginUser());
+  }
+
+  private loadUser() {
+      return this.http.get<User>(this.currentUserURL);
+  }
+
+  isLoggedIn() {
+      return this.authService.isLoggedIn();
+  }
+
+  getLoginUser() {
+      return this.authService.getLoginUser();
+  }
+
+  hasRole(role: string) {
+    return this.authService.isUserInRole(role);
+  }
+
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authenticationstate.service.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authenticationstate.service.ts
new file mode 100644
index 0000000..377ecbd
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/authentication/authenticationstate.service.ts
@@ -0,0 +1,64 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+import { Injectable } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+export class User {
+    username: string;
+    roles: string[];
+}
+
+@Injectable({
+    providedIn: 'root'
+  })
+export class AuthenticationStateService {
+    readonly currentUserKey = 'currentUser';
+
+    loginUser = new BehaviorSubject<User>(null);
+
+    getLoginUserValue() {
+        return this.loginUser.value;
+    }
+
+    isUserInRole(roles: string | string[]) {
+        if (typeof roles === 'string') {
+            roles = [ roles ];
+        }
+      return this.isLoggedIn() ? this.loginUser.value.roles.filter(x => roles.includes(x)).length > 0 : false;
+    }
+
+    getLoginUser() {
+        return this.loginUser.asObservable();
+    }
+
+    isLoggedIn() {
+        return this.loginUser.asObservable().pipe(map(user => user !== null));
+    }
+
+    setLoginUser(user: User) {
+        this.loginUser.next(user);
+        localStorage.setItem(this.currentUserKey, JSON.stringify(user));
+    }
+
+    unsetLoginUser() {
+        this.loginUser.next(null);
+        localStorage.removeItem(this.currentUserKey);
+    }
+
+    clearLoginState() {
+        this.unsetLoginUser();
+    }
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.css b/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.css
new file mode 100644
index 0000000..03e0f0c
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.css
@@ -0,0 +1,15 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+.attreditor {width: 80%;}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.html
new file mode 100644
index 0000000..cc9347b
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.html
@@ -0,0 +1,22 @@
+<!--******************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ *******************************************************************************-->
+
+<input *ngIf="attribute != null && attribute.dataType === 'STRING'" pInputText type="text" [(ngModel)]="attribute.value[index]" class="attreditor">
+<input *ngIf="attribute != null && attribute.dataType === 'FLOAT'" pInputText type="number" [(ngModel)]="attribute.value[index]" class="attreditor">
+<input *ngIf="attribute != null && attribute.dataType === 'SHORT'" pInputText type="number" [(ngModel)]="attribute.value[index]" class="attreditor">
+<input *ngIf="attribute != null && attribute.dataType === 'LONG'" pInputText type="number" [(ngModel)]="attribute.value[index]" class="attreditor">
+<input *ngIf="attribute != null && attribute.dataType === 'LONGLONG'" pInputText type="number" [(ngModel)]="attribute.value[index]" class="attreditor">
+<input *ngIf="attribute != null && attribute.dataType === 'DOUBLE'" pInputText type="number" [(ngModel)]="attribute.value[index]" class="attreditor">
+<p-calendar *ngIf="attribute != null && attribute.dataType === 'DATE'" dataType="string" dateFormat="{{'details.mdm-detail-descriptive-data.input-dateformat' | translate}}" [(ngModel)]="attribute.value[index]" class="attreditor"></p-calendar>
+<input *ngIf="attribute != null && attribute.dataType === 'BOOLEAN'" pInputText type="checkbox" [(ngModel)]="attribute.value[index]" class="attreditorbool">
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.ts
new file mode 100644
index 0000000..e01811a
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/attribute-editor/attribute-editor.component.ts
@@ -0,0 +1,41 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+
+import { DetailViewService } from '../detail-view.service';
+import { BasketService } from '../../basket/basket.service';
+import { Node, Attribute } from '../../navigator/node';
+import { MDMItem } from '../../core/mdm-item';
+import { TreeNode } from 'primeng/api';
+import { CalendarModule } from 'primeng/calendar';
+
+@Component({
+  selector: 'attribute-editor',
+  templateUrl: './attribute-editor.component.html',
+  styleUrls: ['./attribute-editor.component.css']
+})
+export class AttributeEditorComponent {
+
+  // attributes of display node
+  @Input() attribute: Attribute;
+
+  @Input() index: number;
+
+  constructor(
+    private detailViewService: DetailViewService,
+    private basketService: BasketService) { }
+
+}
+
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/context.service.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/details/context.service.ts
index 0cf0114..986201c 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/details/context.service.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/context.service.ts
@@ -37,6 +37,21 @@
     this._contextUrl = _prop.getUrl('mdm/environments');
   }
 
+  putContext(node: Node, context: any) {
+    let url = this._contextUrl + '/' + node.sourceName;
+    url = url + '/' + node.type.toLowerCase() + 's/' + node.id + '/contexts';
+    let body = {
+      "data": [ context ]
+    };
+    return this.http.put(url, body).pipe(
+      map((res) => {
+        let data = res.json().data;
+        let context = this.mergeContextRoots([data[0].ordered, data[0].measured]);
+        return <{}>context;
+      }),
+      catchError(this.httpErrorHandler.handleError));
+  }
+
   getContext(node: Node) {
     let url = this._contextUrl + '/' + node.sourceName;
     url = url + '/' + node.type.toLowerCase() + 's/' + node.id + '/contexts';
@@ -90,7 +105,7 @@
       attr.dataType = ['', attr.dataType];
       attr.name = ['', attr.name];
       attr.unit = ['', attr.unit];
-      attr.value = ['', attr.unit];
+      attr.value = ['', attr.value];
     });
     return node;
   }
@@ -100,7 +115,7 @@
       attr.dataType = [attr.dataType, ''];
       attr.name = [attr.name, ''];
       attr.unit = [attr.unit, ''];
-      attr.value = [attr.unit, ''];
+      attr.value = [attr.value, ''];
     });
     return node;
   }
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail-descriptive-data.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail-descriptive-data.component.html
index 05925ab..6a40d62 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail-descriptive-data.component.html
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail-descriptive-data.component.html
@@ -19,74 +19,90 @@
   </div>
 </div>
 
-<div *ngIf="contexts">
-<accordion *ngIf="isUUT()">
-  <accordion-group *ngFor="let template of contexts['UNITUNDERTEST']" #UUT>
-    <div accordion-heading class="thinheader">{{template.name}}
-      <span class="pull-right fa" [ngClass]="{'fa-chevron-down': UUT?.isOpen, 'fa-chevron-right': !UUT?.isOpen}"></span>
-    </div>
-    <table class="table table-hover">
-      <thead>
+<div *ngIf="contexts" class="mdm-details-attributes">
+
+  <button (click)="changeTreeEdit(true, false)" *ngIf="!isEditMode() && canEdit()" style="margin-bottom: 5px;">{{ 'details.mdm-detail-descriptive-data.btn-edit' | translate }}</button>
+  <button (click)="changeTreeEdit(false, false)" *ngIf="isEditMode()" style="margin-bottom: 5px;">{{ 'details.mdm-detail-descriptive-data.btn-cancel' | translate }}</button>
+  <button (click)="changeTreeEdit(false, true)" *ngIf="isEditMode()" style="margin-bottom: 5px;margin-left:10px;">{{ 'details.mdm-detail-descriptive-data.btn-save' | translate }}</button>
+
+  <ng-template ngFor let-tbltype [ngForOf]="getTableTypes()" let-i="tblidx">
+    <p-treeTable #ttref [value]="getTreeNodesFor(tbltype)" *ngIf="isTableType(tbltype)" styleClass="table-hover">
+      <ng-template pTemplate="header">
         <tr>
-          <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-name' | translate }}</th>
+          <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-name' | translate }} <span class="inlinebuttons"><button (click)="changeTreeNodeState(tbltype, true, ttref)">+</button><button (click)="changeTreeNodeState(tbltype, false, ttref)">-</button></span></th>
           <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-ordered' | translate }}</th>
           <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-measured' | translate }}</th>
         </tr>
-      </thead>
-      <tbody>
-        <tr *ngFor="let attr of template.attributes" [ngClass]="diff(attr.value[0], attr.value[1])">
-          <td>{{attr.name}}</td>
-          <td>{{attr.value[0]}}</td>
-          <td>{{attr.value[1]}}</td>
-        </tr>
-      </tbody>
-    </table>
-  </accordion-group>
-</accordion>
-<accordion *ngIf="isTS()">
-  <accordion-group *ngFor="let template of contexts['TESTSEQUENCE']" #TS>
-    <div accordion-heading class="thinheader">{{template.name}}
-      <span class="pull-right fa" [ngClass]="{'fa-chevron-down': TS?.isOpen, 'fa-chevron-right': !TS?.isOpen}"></span>
-    </div>
-    <table class="table table-hover">
-      <thead>
+      </ng-template>
+      <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
         <tr>
-          <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-name' | translate }}</th>
-          <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-ordered' | translate }}</th>
-          <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-measured' | translate }}</th>
+          <td class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">
+            <p-treeTableToggler [rowNode]="rowNode"></p-treeTableToggler>
+            <span pTooltip="{{rowData.attribute != null ? rowData.attribute.description : ''}}" tooltipPosition="bottom">{{rowData.name}}</span>
+          </td>
+          <td ttEditableColumn class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">
+            <p-treeTableCellEditor>
+              <ng-template pTemplate="input">
+                <span *ngIf="rowData.attribute != null && isEditMode()"><attribute-editor [attribute]="rowData.attribute" [index]="0"></attribute-editor></span>
+                <span *ngIf="rowData.attribute != null && !isEditMode()">{{rowData.attribute.value[0]}}</span>
+              </ng-template>
+              <ng-template pTemplate="output"><span *ngIf="rowData.attribute != null">{{rowData.attribute.value[0]}}</span></ng-template>
+            </p-treeTableCellEditor>
+            <span *ngIf="rowData.attribute != null && rowData.attribute.unit != null && rowData.attribute.unit.length > 0" class="inlinecontent">{{rowData.attribute.unit}}</span>
+          </td>
+          <td ttEditableColumn class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">
+            <p-treeTableCellEditor>
+              <ng-template pTemplate="input">
+                <span *ngIf="rowData.attribute != null && rowData.attribute.value.length == 2 && isEditMode()"><attribute-editor [attribute]="rowData.attribute" [index]="1"></attribute-editor></span>
+                <span *ngIf="rowData.attribute != null && rowData.attribute.value.length != 2 && !isEditMode()">{{rowData.attribute.value[1]}}</span>
+              </ng-template>
+              <ng-template pTemplate="output"><span *ngIf="rowData.attribute != null && rowData.attribute.value.length == 2">{{rowData.attribute.value[1]}}</span></ng-template>
+            </p-treeTableCellEditor>
+            <span *ngIf="rowData.attribute != null && rowData.attribute.unit != null && rowData.attribute.unit.length > 0" class="inlinecontent">{{rowData.attribute.unit}}</span>
+          </td>
         </tr>
-      </thead>
-      <tbody>
-        <tr *ngFor="let attr of template.attributes" [ngClass]="diff(attr.value[0], attr.value[1])">
-          <td>{{attr.name}}</td>
-          <td>{{attr.value[0]}}</td>
-          <td>{{attr.value[1]}}</td>
-        </tr>
-      </tbody>
-    </table>
-  </accordion-group>
-</accordion>
-<accordion *ngIf="isTE()">
-  <accordion-group *ngFor="let template of contexts['TESTEQUIPMENT']" #TE>
-    <div accordion-heading class="thinheader">{{template.name}}
-      <span class="pull-right fa" [ngClass]="{'fa-chevron-down': TE?.isOpen, 'fa-chevron-right': !TE?.isOpen}"></span>
-    </div>
-    <table class="table table-hover">
-      <thead>
-        <tr>
-          <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-name' | translate }}</th>
-          <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-ordered' | translate }}</th>
-          <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-measured' | translate }}</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr *ngFor="let attr of template.attributes" [ngClass]="diff(attr.value[0], attr.value[1])">
-          <td>{{attr.name}}</td>
-          <td>{{attr.value[0]}}</td>
-          <td>{{attr.value[1]}}</td>
-        </tr>
-      </tbody>
-    </table>
-  </accordion-group>
-</accordion>
+      </ng-template>
+    </p-treeTable>
+  </ng-template>
+    <!--
+  <p-treeTable #ttts [value]="getTreeNodesFor('TESTSEQUENCE')" *ngIf="isTS()" styleClass="table-hover">
+    <ng-template pTemplate="header">
+      <tr>
+        <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-name' | translate }} <span class="inlinebuttons"><button (click)="changeTreeNodeState('TESTSEQUENCE', true, ttts)">+</button><button (click)="changeTreeNodeState('TESTSEQUENCE', false, ttts)">-</button></span></th>
+        <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-ordered' | translate }}</th>
+        <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-measured' | translate }}</th>
+      </tr>
+    </ng-template>
+    <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
+      <tr>
+        <td class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">
+          <p-treeTableToggler [rowNode]="rowNode"></p-treeTableToggler>
+          <span pTooltip="{{rowData.attribute != null ? rowData.attribute.description : ''}}" tooltipPosition="bottom">{{rowData.name}}</span>
+        </td>
+        <td class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">{{rowData.attribute != null ? rowData.attribute.value[0] : ''}} <span *ngIf="rowData.attribute != null && rowData.attribute.unit != null && rowData.attribute.unit.length > 0" class="inlinecontent">{{rowData.attribute.unit}}</span></td>
+        <td class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">{{rowData.attribute != null ? rowData.attribute.value[1] : ''}} <span *ngIf="rowData.attribute != null && rowData.attribute.unit != null && rowData.attribute.unit.length > 0" class="inlinecontent">{{rowData.attribute.unit}}</span></td>
+      </tr>
+    </ng-template>
+  </p-treeTable>
+  <p-treeTable #ttte [value]="getTreeNodesFor('TESTEQUIPMENT')" *ngIf="isTE()" styleClass="table-hover">
+    <ng-template pTemplate="header">
+      <tr>
+        <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-name' | translate }} <span class="inlinebuttons"><button (click)="changeTreeNodeState('TESTEQUIPMENT', true, ttte)">+</button><button (click)="changeTreeNodeState('TESTEQUIPMENT', false, ttte)">-</button></span></th>
+        <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-ordered' | translate }}</th>
+        <th>{{ 'details.mdm-detail-descriptive-data.tblhdr-measured' | translate }}</th>
+      </tr>
+    </ng-template>
+    <ng-template pTemplate="body" let-rowNode let-rowData="rowData">
+      <tr>
+        <td class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">
+          <p-treeTableToggler [rowNode]="rowNode"></p-treeTableToggler>
+          <span pTooltip="{{rowData.attribute != null ? rowData.attribute.description : ''}}" tooltipPosition="bottom">{{rowData.name}}</span>
+        </td>
+        <td class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">{{rowData.attribute != null ? rowData.attribute.value[0] : ''}} <span *ngIf="rowData.attribute != null && rowData.attribute.unit != null && rowData.attribute.unit.length > 0" class="inlinecontent">{{rowData.attribute.unit}}</span></td>
+        <td class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">{{rowData.attribute != null ? rowData.attribute.value[1] : ''}} <span *ngIf="rowData.attribute != null && rowData.attribute.unit != null && rowData.attribute.unit.length > 0" class="inlinecontent">{{rowData.attribute.unit}}</span></td>
+      </tr>
+    </ng-template>
+  </p-treeTable>
+      -->
+
 </div>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail-descriptive-data.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail-descriptive-data.component.ts
index 936ef94..7a6c5f6 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail-descriptive-data.component.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail-descriptive-data.component.ts
@@ -16,17 +16,24 @@
 import { Component, OnInit } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
 
+import { TreeNode } from 'primeng/api';
+
 import { LocalizationService } from '../localization/localization.service';
 
-import { ContextService } from './context.service';
+import { ContextService, ContextAttribute, MergedContextAttribute } from './context.service';
 import { Context, Sensor } from './context';
-import { Node } from '../navigator/node';
+import { Node, Attribute } from '../navigator/node';
 import { NavigatorService } from '../navigator/navigator.service';
 
 import { MDMNotificationService } from '../core/mdm-notification.service';
 
 import { TranslateService } from '@ngx-translate/core';
 import { TRANSLATE } from '../core/mdm-core.module';
+import { TreeTable } from 'primeng/primeng';
+import { Observable, BehaviorSubject } from 'rxjs';
+import { DatePipe } from '@angular/common';
+import { AuthenticationService } from '../authentication/authentication.service';
+import { isArray } from 'util';
 
 @Component({
   selector: 'mdm-detail-context',
@@ -36,9 +43,16 @@
 export class MDMDescriptiveDataComponent implements OnInit {
 
   readonly StatusLoading = TRANSLATE('details.mdm-detail-descriptive-data.status-loading');
+  readonly StatusSaving = TRANSLATE('details.mdm-detail-descriptive-data.status-saving');
   readonly StatusNoNodes = TRANSLATE('details.mdm-detail-descriptive-data.status-no-nodes-available');
   readonly StatusNoDescriptiveData = TRANSLATE('details.mdm-detail-descriptive-data.status-no-descriptive-data-available');
 
+  treeNodes: TreeNode[][];
+  bsTreeNodes: BehaviorSubject<TreeNode[][]> = new BehaviorSubject<TreeNode[][]>(undefined);
+
+  // this holds the type dependant original context data in case of cancelling the edit mode
+  tmpTreeNodes: Context;
+
   selectedNode: Node;
   context: String;
 
@@ -48,25 +62,36 @@
   status: string;
 
   msgsStatus: string;
+  editMode: boolean;
 
   constructor(private route: ActivatedRoute,
               private localService: LocalizationService,
               private _contextService: ContextService,
               private navigatorService: NavigatorService,
               private notificationService: MDMNotificationService,
-              private translateService: TranslateService) {}
+              private translateService: TranslateService,
+              private datePipe: DatePipe,
+              private authenticationService: AuthenticationService) {
+
+    this.bsTreeNodes.subscribe(value => {
+      this.treeNodes = value;
+    });
+  }
 
   ngOnInit() {
-   this.status = this.StatusLoading;
+    this.status = this.StatusLoading;
+    this.editMode = false;
 
     this.refreshDetailData(this.navigatorService.getSelectedNode());
 
+    // context tab changed from the toolbar navigation
     this.route.params
-        .subscribe(
-          params => this.setContext(params['context']),
+      .subscribe(
+        params => this.refreshContextData(params['context']),
           error => this.notificationService.notifyError(
             this.translateService.instant('details.mdm-detail-descriptive-data.err-cannot-load-scope'), error));
 
+    // node changed from the navigation tree
     this.navigatorService.selectedNodeChanged
         .subscribe(
           node => this.refreshDetailData(node),
@@ -78,11 +103,31 @@
     this.context = context;
   }
 
+  /**
+   * Listener method to change the context tab
+   * 
+   * @param contextType
+   */
+  refreshContextData(contextType: string) {
+    // revert before changing the context
+    this.undoEditChanges(undefined, this.editMode);
+    this.setContext(contextType);
+    this.editMode = false;
+  }
+
+  /**
+   * Listener method to change the node
+   * 
+   * @param node
+   */
   refreshDetailData(node: Node) {
     if (node != undefined) {
       this.selectedNode = node;
       this.status = this.StatusLoading;
-      if (node.type.toLowerCase() === 'measurement' || node.type.toLowerCase() === 'teststep') {
+      this.editMode = false;
+      this.contexts = undefined;
+      this.bsTreeNodes.next(undefined);
+      if (node.type.toLowerCase() === 'measurement' || node.type.toLowerCase() === 'teststep' || node.type.toLowerCase() === 'test') {
         this.loadContext(node);
       } else {
         this.status = this.StatusNoDescriptiveData;
@@ -92,14 +137,19 @@
     }
   }
 
+  /**
+   * Load the context data for the provided node
+   * 
+   * @param node
+   */
   private loadContext(node: Node) {
-    this.contexts = undefined;
     this._contextService.getContext(node).subscribe(
       contexts => {
           if (contexts.hasOwnProperty('UNITUNDERTEST')
               || contexts.hasOwnProperty('TESTEQUIPMENT')
               || contexts.hasOwnProperty('TESTSEQUENCE')) {
-                this.contexts =  <Context[]> contexts;
+                this.contexts = <Context[]>contexts;
+                this.bsTreeNodes.next(this.mapContexts(this.contexts));
               } else {
                 this.status = this.StatusNoDescriptiveData;
               }
@@ -130,4 +180,449 @@
   isTS() {
     return this.context.toLowerCase() === 'ts';
   }
+
+  getId(node: TreeNode) {
+    if (node && node.data) {
+      return 'node_' + node.data.source + '_' + node.data.type + '_' + node.data.id;
+    } else {
+      return '';
+    }
+  }
+
+  getNodeClass(item: Node) {
+    return 'icon ' + item.type.toLowerCase();
+  }
+
+  /**
+   * Get the tree nodes for the type
+   *
+   * @param type
+   */
+  getTreeNodesFor(type: string) {
+    for (let i in this.treeNodes) {
+      if (i == type) {
+        return this.treeNodes[i];
+      }
+    }
+    return [];
+  }
+
+  /**
+   * Change the tree state to expanded or collapsed
+   *
+   * @param type
+   * @param expand
+   */
+  changeTreeNodeState(type: string, expand: boolean, tt: TreeTable) {
+    for (var i in this.treeNodes) {
+      if (i == type) {
+        for (let j in this.treeNodes[i]) {
+          this.recursiveChangeNodes(this.treeNodes[i][j], expand);
+        }
+      }
+    }
+    // invoke table update when pressing the plus or minus buttons from the table header
+    tt.updateSerializedValue();
+    tt.tableService.onUIUpdate(tt.value);
+  }
+
+  /**
+   * Change the tree node state recursively
+   *
+   * @param node
+   * @param expand
+   */
+  recursiveChangeNodes(node: TreeNode, expand: boolean) {
+    node.expanded = expand;
+    if (node.children != undefined && node.children.length > 0) {
+      for (let i in node.children) {
+        this.recursiveChangeNodes(node.children[i], expand);
+      }
+    }
+  }
+
+  /**
+   * Get the nodes from the current context
+   *
+   * @param context
+   * @param parentId optional parent id which will get the child nodes
+   */
+  getNodes(context: Context, parentId: string) {
+    let list = [];
+    for (let j in context) {
+      let parentNodeId = context[j].relations != null && context[j].relations.length > 0 ? context[j].relations[0].parentId : null;
+      if (parentId == null && parentNodeId == null) {
+        list.push(context[j]);
+      } else if (parentId != null && parentNodeId != null && parentId == parentNodeId) {
+        list.push(context[j]);
+      }
+    }
+    return list;
+  }
+
+  /**
+   * Create a tree node based on the mdm entity
+   *
+   * @param node
+   * @param context
+   */
+  createTreeNode(node: Node, context: Context) {
+    return <TreeNode>{
+      label: node.name,
+      data: {
+        "name": node.name,
+        "attribute": null,
+        "header": true
+      },
+      children: this.createTreeChildren(node, context),
+      icon: this.getNodeClass(node),
+      expanded: true
+    };
+  }
+
+  /**
+   * Create the tree children nodes recursive based on the mdm attributes and subsequent mdm entities
+   *
+   * @param node the current node
+   * @param contexts the complete contexts
+   */
+  createTreeChildren(node: Node, context: Context) {
+    let list = [];
+
+    for (let i in node.attributes) {
+      let tmp = <TreeNode>{
+        data: {
+          "name": node.attributes[i].name,
+          "attribute": this.patchAttributeForDate(node.attributes[i] as any as MergedContextAttribute),
+          "header": false
+        },
+        expanded: true
+      };
+      list.push(tmp);
+    }
+    // call recursive child components with complete null checks
+    if (node.relations != null && node.relations.length > 0) {
+      for (let i in node.relations) {
+        if (node.relations[i].ids != null && node.relations[i].ids.length > 0) {
+          for (let j in node.relations[i].ids) {
+            let curTplId = node.relations[i].ids[j];
+            let nodes = this.getNodes(context, curTplId);
+            for (var k in nodes) {
+              let node = this.createTreeNode(nodes[k], context);
+              list.push(node);
+            }
+          }
+        }
+      }
+    }
+
+    return list;
+  }
+
+  /**
+   * Method to create a tree structure from the flat context entity and attribute map
+   *
+   * @param contexts
+   */
+  mapContexts(contexts: Context[]) {
+    let list = [];
+    let subList = [];
+
+    // generate all context component mappings
+    for (let i in contexts) {
+      let nodes = this.getNodes(contexts[i], null);
+      for (let j in nodes) {
+        let node = this.createTreeNode(nodes[j], contexts[i]);
+        subList.push(node);
+      }
+      list[i] = subList;
+      subList = [];
+    }
+
+    return list;
+  }
+
+  /**
+   * Convert the date for UI or backend
+   * 
+   * @param attribute
+   */
+  patchAttributeForDate(attribute: MergedContextAttribute) {
+    if (attribute.dataType != null && attribute.dataType.length > 0 && "DATE" === attribute.dataType
+      && attribute.value != null && attribute.value.length > 0) {
+      if (attribute.value[0] != null && attribute.value[0].length > 0)
+        attribute.value[0] = this.convertFixedDateStr(attribute.value[0], true);
+      if (attribute.value.length > 1 && attribute.value[1] != null && attribute.value[1].length > 0)
+        attribute.value[1] = this.convertFixedDateStr(attribute.value[1], true);
+    }
+    return attribute;
+  }
+  
+  /** *********************
+   * Edit functions start
+   ********************** */
+
+  convertFixedDateStr(dt: string, convertForUI: boolean) {
+    let newDt = dt;
+    let sourceFormat = "";
+    (this.translateService.get("details.mdm-detail-descriptive-data.input-dateformat") as Observable<string | any>).subscribe(val => sourceFormat = val);
+    sourceFormat = sourceFormat.replace("yy", "yyyy");
+
+    if (dt != null && dt.length > 0 && convertForUI && dt.indexOf('T') > -1) {
+      let tmpDt = this.parseISOString(dt);
+      newDt = this.datePipe.transform(tmpDt, sourceFormat.replace('mm', 'MM'));
+    } else if (dt != null && dt.length > 0 && !convertForUI && dt.indexOf('T') == -1) {
+      // re-convert date to server date
+      if (newDt.indexOf('-') == -1) {
+        // find dd, mm and yyyy and grab the according index positions
+        let startDay = sourceFormat.indexOf('d');
+        let endDay = sourceFormat.lastIndexOf('d');
+        let startMonth = sourceFormat.indexOf('m');
+        let endMonth = sourceFormat.lastIndexOf('m');
+        let startYear = sourceFormat.indexOf('y');
+        let endYear = sourceFormat.lastIndexOf('y');
+        // manually attach the time as toISOString() will not properly transform the winter/summer time
+        newDt = dt.substring(startYear, endYear + 1) + '-' + dt.substring(startMonth, endMonth + 1) + '-' + dt.substring(startDay, endDay + 1) + 'T00:00:00Z';
+        if (newDt.length != 20) {
+          newDt = "";
+        }
+      }
+    }
+    return newDt;
+  }
+
+  /**
+   * Fixed parsing for ISO date string
+   * 
+   * @param s
+   */
+  parseISOString(s: string) {
+    let b: any[] = s.split(/\D+/);
+    return new Date(Date.UTC(b[0], --b[1], b[2], b[3], b[4], b[5], b[6]));
+  }
+
+  /**
+   * Send the changed data to the backend
+   * 
+   * @param node
+   * @param type
+   */
+  private putContext(node: Node, type: string) {
+    if (type == null) {
+      return;
+    }
+
+    this.status = this.StatusSaving;
+    this.bsTreeNodes.next(undefined);
+
+    let data = { "ordered": {}, "measured": {} };
+
+    for (let i in this.contexts) {
+      // only the requested type
+      if (i == type) {
+        // workaround to initialize as map or we get a list in the backend
+        data["ordered"][i] = [];
+        data["ordered"][i] = this.getNodesWithUpdatedContent(this.contexts[i], type, true);
+        data["measured"][i] = [];
+        data["measured"][i] = this.getNodesWithUpdatedContent(this.contexts[i], type, false);
+      }
+    }
+
+    // clear for status display
+    this.contexts = undefined;
+
+    this._contextService.putContext(node, data).subscribe(
+      contexts => {
+        if (contexts.hasOwnProperty('UNITUNDERTEST')
+          || contexts.hasOwnProperty('TESTEQUIPMENT')
+          || contexts.hasOwnProperty('TESTSEQUENCE')) {
+          this.contexts = <Context[]>contexts;
+          this.bsTreeNodes.next(this.mapContexts(this.contexts));
+        } else {
+          this.status = this.StatusNoDescriptiveData;
+        }
+      },
+      error => this.notificationService.notifyError(
+        this.translateService.instant('details.mdm-detail-descriptive-data.err-cannot-load-context'), error)
+    );
+  }
+
+  /**
+   * Get the table types
+   */
+  getTableTypes() {
+    return ["UNITUNDERTEST", "TESTEQUIPMENT", "TESTSEQUENCE"];
+  }
+
+  /**
+   * Check if the provided type matches with the url path
+   * 
+   * @param type
+   */
+  isTableType(type: string) {
+    return "UNITUNDERTEST" === type && this.isUUT()
+      || "TESTEQUIPMENT" === type && this.isTE()
+      || "TESTSEQUENCE" === type && this.isTS();
+  }
+
+  /**
+   * Is edit mode enabled
+   */
+  isEditMode() {
+    return this.editMode;
+  }
+
+  /**
+   * Can edit attributes
+   */
+  canEdit() {
+    // Read the role 'write-user' from the web.xml configuration mapping
+    return this.authenticationService.hasRole('write-user');
+  }
+
+  /**
+   * Switch the tree edit mode
+   * 
+   * @param editMode true if edit mode, false if leaving edit mode
+   * @param save when leaving edit mode, true then save data, false then cancel data
+   */
+  changeTreeEdit(editMode: boolean, save: boolean) {
+    let type = null;
+    if (this.isUUT()) {
+      type = "UNITUNDERTEST";
+    } else if (this.isTE()) {
+      type = "TESTEQUIPMENT";
+    } else if (this.isTS()) {
+      type = "TESTSEQUENCE";
+    }
+
+    this.editMode = editMode;
+
+    if (this.editMode) {
+      // clone tree
+      this.tmpTreeNodes = JSON.parse(JSON.stringify(this.contexts[type]));
+    }
+
+    if (!editMode && save) {
+      // either cancel or save
+      this.putContext(this.selectedNode, type);
+      this.tmpTreeNodes = null;
+    } else if (!editMode && !save) {
+      this.undoEditChanges(type, true);
+    }
+  }
+
+  /**
+   * Method to revert table changes back to the original state
+   * 
+   * @param type
+   */
+  undoEditChanges(type: string, editMode: boolean) {
+    if (this.contexts != undefined && editMode && this.tmpTreeNodes != undefined) {
+      if (type == undefined) {
+        if (this.isUUT()) {
+          type = "UNITUNDERTEST";
+        } else if (this.isTE()) {
+          type = "TESTEQUIPMENT";
+        } else if (this.isTS()) {
+          type = "TESTSEQUENCE";
+        }
+      }
+      // contexts is the origin, so we revert this back as the tree attributes are just references
+      this.contexts[type] = this.tmpTreeNodes;
+      // revert back
+      this.bsTreeNodes.next(this.mapContexts(this.contexts));
+      this.tmpTreeNodes = null;
+    }
+  }
+
+  /**
+   * Get the updated nodes from the current context
+   *
+   * @param context
+   * @param type the context type
+   * @param ordered true if ordered data, false if measured data
+   */
+  getNodesWithUpdatedContent(context: Context, type: string, ordered: boolean) {
+    let list = [];
+    for (let i in context) {
+      let parentNodeId = context[i].relations != null && context[i].relations.length > 0 ? context[i].relations[0].parentId : null;
+      if (parentNodeId == null) {
+        let attrs = [];
+        for (let j in context[i].attributes) {
+          let attr = new ContextAttribute();
+          let addAttr = true;
+          attr.dataType = context[i].attributes[j].dataType;
+          attr.name = context[i].attributes[j].name;
+          attr.unit = context[i].attributes[j].unit;
+          attr.value = "";
+          if (ordered && isArray(context[i].attributes[j].value) && context[i].attributes[j].value.length > 0) {
+            attr.value = context[i].attributes[j].value[0];
+          }
+          else if (!ordered && isArray(context[i].attributes[j].value) && context[i].attributes[j].value.length > 1) {
+            attr.value = context[i].attributes[j].value[1];
+          }
+          // lookup new value from treenodes
+
+          if (attr.dataType === 'BOOLEAN' && attr.value != null && attr.value.toString().length > 0) {
+            attr.value = attr.value.toString() === 'true' ? '1' : '0';
+          }
+
+          // BUG: don't add if non-string as this throws an parsing error in the middleware
+          if (attr.dataType !== 'STRING' && (attr.value == null || attr.value.toString().length == 0))
+            addAttr = false;
+
+          if (addAttr && attr.dataType === 'DATE') {
+            attr.value = this.convertFixedDateStr(attr.value, false);
+          }
+
+          if (addAttr) {
+            let confirmedChange = false;
+            let found = false;
+            // lookup from the original and check if the value was modified
+            for (let i1 in this.tmpTreeNodes) {
+              if (found) break;
+              if (this.tmpTreeNodes[i1].name == context[i].name) {
+                for (let j1 in this.tmpTreeNodes[i1].attributes) {
+                  if (this.tmpTreeNodes[i1].attributes[j1].name == attr.name && isArray(this.tmpTreeNodes[i1].attributes[j1].value)) {
+                    found = true;
+                    let orgValue = ordered ? this.tmpTreeNodes[i1].attributes[j1].value[0] :
+                      this.tmpTreeNodes[i1].attributes[j1].value.length > 1 ? this.tmpTreeNodes[i1].attributes[j1].value[1] : undefined;
+                    if (orgValue != undefined) {
+                      if (attr.dataType === 'BOOLEAN') {
+                        // server value = true or false, UI value = 1 or 0
+                        confirmedChange = attr.value == '0' && orgValue == 'true'
+                          || attr.value == '1' && orgValue == 'false'
+                          || attr.value != '' && orgValue == '';
+                      } else {
+                        // plain comparison
+                        confirmedChange = attr.value != orgValue;
+                      }
+                      break;
+                    }
+                  }
+                }
+              }
+            }
+
+            if (confirmedChange)
+              attrs.push(attr);
+          }
+        }
+        // un-merged list
+        //context[i].attributes = attrs;
+        if (attrs.length > 0) {
+          let component = JSON.parse(JSON.stringify(context[i]));
+          component.attributes = attrs;
+          list.push(component);
+        }
+      }
+    }
+    return list;
+  }
+
+  /** *********************
+   * Edit functions end
+   ********************** */
+
 }
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.component.html
index d8a3f0f..51c70c9 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.component.html
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.component.html
@@ -19,7 +19,7 @@
   }
 </style>
 
-<nav class="navbar navbar-expand bg-light detail-nav ui-corner-all">
+<nav class="navbar navbar-expand bg-light detail-nav ui-corner-all" *ngIf="isVisible()">
   <div class="container-fluid">
     <div class="collapse navbar-collapse" id="bs-mdm-detail-navbar">
       <ul class="nav navbar-nav mdm-link-list">
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.component.ts
index ad99198..c3f41fd 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.component.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.component.ts
@@ -13,12 +13,16 @@
  ********************************************************************************/
 
 
-import {Component} from '@angular/core';
+import { Component } from '@angular/core';
+import { Router, ActivatedRoute } from '@angular/router';
 
 import {MDMDetailViewComponent} from './mdm-detail-view.component';
 import {MDMDescriptiveDataComponent} from './mdm-detail-descriptive-data.component';
 import {SensorComponent} from './sensor.component';
 
+import { Node } from '../navigator/node';
+import { NavigatorService } from '../navigator/navigator.service';
+
 @Component({
   selector: 'mdm-detail',
   templateUrl: 'mdm-detail.component.html',
@@ -26,4 +30,33 @@
 })
 export class MDMDetailComponent {
 
+  visible = false;
+
+  constructor(private route: ActivatedRoute,
+    private router: Router,
+    private navigatorService: NavigatorService) {
+
+    this.refreshVisibility(this.navigatorService.getSelectedNode());
+
+    this.navigatorService.selectedNodeChanged
+      .subscribe(node => this.refreshVisibility(node));
+  }
+
+  refreshVisibility(node: Node) {
+    this.visible = false;
+    if (node != undefined && node.type != undefined && (node.type.toLowerCase() === 'measurement' || node.type.toLowerCase() === 'teststep' || node.type.toLowerCase() === 'test')) {
+       this.visible = true;
+    }
+
+    // check if we are in the general node and if the context tabs are visible
+    if (!this.visible && this.router.url !== '/navigator/details/general') {
+      // redirect to correct router path
+      this.router.navigate(['/navigator/details/general']);
+    }
+  }
+
+  isVisible() {
+    return this.visible;
+  }
+
 }
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.module.ts
index fa13b59..446d4bd 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.module.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/details/mdm-detail.module.ts
@@ -19,6 +19,8 @@
 
 import { MDMCoreModule } from '../core/mdm-core.module';
 import { PanelModule } from 'primeng/panel';
+import { TreeTableModule } from 'primeng/treetable';
+import { TooltipModule } from 'primeng/tooltip';
 
 import { MDMDetailComponent } from './mdm-detail.component';
 import { MDMDetailViewComponent } from './mdm-detail-view.component';
@@ -27,12 +29,18 @@
 import { SensorComponent } from './sensor.component';
 import { ContextService } from './context.service';
 import { DetailPanelComponent } from './detail-panel/detail-panel.component';
+import { AttributeEditorComponent } from './attribute-editor/attribute-editor.component';
+import { DatePipe } from '@angular/common';
+import { AuthenticationModule } from '../authentication/authentication.module';
 
 @NgModule({
   imports: [
     MDMDetailRoutingModule,
     MDMCoreModule,
-    PanelModule
+    PanelModule,
+    TreeTableModule,
+    TooltipModule,
+    AuthenticationModule
   ],
   declarations: [
     MDMDetailComponent,
@@ -40,13 +48,15 @@
     MDMDescriptiveDataComponent,
     SensorComponent,
     DetailPanelComponent,
+    AttributeEditorComponent
   ],
   exports: [
     MDMDetailComponent
   ],
   providers: [
     DetailViewService,
-    ContextService
+    ContextService,
+    DatePipe
   ]
 })
 export class MDMDetailModule {
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.html
index e5ea941..c743f87 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.html
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.html
@@ -11,43 +11,41 @@
  * SPDX-License-Identifier: EPL-2.0
  *
  ********************************************************************************-->
-
-
-<vertical-split-pane primary-component-minsize="{{minWidthLeft()}}" secondary-component-minsize="{{minWidthRight()}}" primary-component-initialratio="{{initRatio()}}">
-  <div class="split-pane-content-primary">
-    <nav class="navigator">
-      <div class="navbar navbar-default container-fluid" style="padding: 0px 5px;">
-        <div class="navbar-header">
-          <a class="navbar-brand" (click)="activate('Navigation')" style="cursor:pointer;">{{ 'navigator-view.mdm-navigator-view.navigator' | translate }}</a>
-        </div>
-        <div>
-          <ul class="nav navbar-nav navbar-right">
-            <li [ngClass]="isDropActive('Dropdown')" title="{{ 'navigator-view.mdm-navigator-view.select-node-provider' | translate }}" dropdown>
-              <a (click)="activate('Dropdown')" class="dropdown-toggle" dropdownToggle aria-haspopup="true" aria-expanded="false" style="cursor:pointer;">
+  <div class="mainnavigation">
+    <div id="leftsidenav" class="split">
+      <nav class="navigator">
+        <div class="navbar navbar-default container-fluid" style="padding: 0px 5px;">
+          <div class="navbar-header">
+            <a class="navbar-brand" (click)="activate('Navigation')" style="cursor:pointer;">{{ 'navigator-view.mdm-navigator-view.navigator' | translate }}</a>
+          </div>
+          <div>
+            <ul class="nav navbar-nav navbar-right">
+              <li [ngClass]="isDropActive('Dropdown')" title="{{ 'navigator-view.mdm-navigator-view.select-node-provider' | translate }}" dropdown>
+                <a (click)="activate('Dropdown')" class="dropdown-toggle" dropdownToggle aria-haspopup="true" aria-expanded="false" style="cursor:pointer;">
                   {{activeNodeprovider.name}}
-                <em class="caret" ></em>
-              </a>
-              <ul class="dropdown-menu" *dropdownMenu>
-                <li *ngFor="let np of getNodeproviders()">
-                  <a class="dropdown-item" (click)="activateNodeProvider(np)" style="cursor:pointer;">
+                  <em class="caret"></em>
+                </a>
+                <ul class="dropdown-menu" *dropdownMenu>
+                  <li *ngFor="let np of getNodeproviders()">
+                    <a class="dropdown-item" (click)="activateNodeProvider(np)" style="cursor:pointer;">
                       {{np.name}}
-                  </a>
-                </li>
-              </ul>
-            </li>
-          </ul>
+                    </a>
+                  </li>
+                </ul>
+              </li>
+            </ul>
+          </div>
         </div>
-      </div>
-      <mdm-navigator></mdm-navigator>
-    </nav>
-  </div>
-  <div class="split-pane-content-secondary">
-    <div class="navigator-content" (scroll)=onScroll($event)>
-      <router-outlet></router-outlet>
-      <mdm-basket (onSelect)="updateSelectedNode($event)" [activeNode]=activeNode (onActive)="updateActiveNode($event)"></mdm-basket>
-      <div *ngIf="scrollBtnVisible" style="position: fixed; bottom: 30px; right: 35px;">
-        <button class="btn btn-default" (click)="onScrollTop()" style="z-index: 10000;"><span class="fa fa-arrow-up" style="z-index: 10000;" title="{{ 'navigator-view.mdm-navigator-view.tooltip-scroll-up' | translate }}"></span></button>
+        <mdm-navigator></mdm-navigator>
+      </nav>
+    </div>
+    <div id="rightsidenav" class="split">
+      <div class="navigator-content" (scroll)=onScroll($event)>
+        <router-outlet></router-outlet>
+        <mdm-basket (onSelect)="updateSelectedNode($event)" [activeNode]=activeNode (onActive)="updateActiveNode($event)"></mdm-basket>
+        <div *ngIf="scrollBtnVisible" style="position: fixed; bottom: 30px; right: 35px;">
+          <button class="btn btn-default" (click)="onScrollTop()" style="z-index: 10000;"><span class="fa fa-arrow-up" style="z-index: 10000;" title="{{ 'navigator-view.mdm-navigator-view.tooltip-scroll-up' | translate }}"></span></button>
+        </div>
       </div>
     </div>
   </div>
-</vertical-split-pane>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts
index 94e3479..017b192 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts
@@ -13,18 +13,18 @@
  ********************************************************************************/
 
 
-import {Component, ViewEncapsulation, OnInit, OnDestroy} from '@angular/core';
+import { Component, ViewEncapsulation, OnInit, OnDestroy, AfterViewInit } from '@angular/core';
 
 import { BsDropdownModule, AccordionConfig, BsDropdownConfig } from 'ngx-bootstrap';
 
 import {NodeService} from '../navigator/node.service';
 import {Node} from '../navigator/node';
 import {NodeproviderService} from '../navigator/nodeprovider.service';
-import { SplitPaneModule } from 'ng2-split-pane/lib/ng2-split-pane';
 
 import {MDMNotificationService} from '../core/mdm-notification.service';
 
 import { TranslateService } from '@ngx-translate/core';
+import Split from 'split.js';
 
 @Component({
   selector: 'mdm-navigator-view',
@@ -33,7 +33,7 @@
   providers: [BsDropdownConfig, AccordionConfig],
   encapsulation: ViewEncapsulation.None
 })
-export class MDMNavigatorViewComponent implements OnInit, OnDestroy {
+export class MDMNavigatorViewComponent implements OnInit, OnDestroy, AfterViewInit {
 
   selectedNode = new Node;
   activeNode: Node;
@@ -46,6 +46,8 @@
   div: any;
   scrollBtnVisible = false;
 
+  split: Split;
+
   constructor(private nodeProviderService: NodeproviderService,
               private notificationService: MDMNotificationService,
               private translateService: TranslateService) {}
@@ -84,7 +86,22 @@
           np => this.activeNodeprovider = np,
           error => this.notificationService.notifyError(
             this.translateService.instant('navigator-view.mdm-navigator-view.err-cannot-update-node-provider'), error)
-        );
+    );
+
+  }
+
+  ngAfterViewInit(): void {
+    this.split = Split(['#leftsidenav', '#rightsidenav'], {
+      sizes: [this.initRatio(), this.initRatioRight()],
+      minSize: this.minWidthLeft(),
+      gutterSize: 10,
+      gutterStyle: function (dimension, gutterSize) {
+        return {
+          'width': gutterSize + 'px',
+          'height': (document.getElementById('leftsidenav').clientHeight - 5) + 'px'
+        }
+      },
+    });
   }
 
   ngOnDestroy() {
@@ -115,7 +132,11 @@
      return 0.20 * window.innerWidth;
    }
 
-   initRatio() {
-     return Math.max(250 / window.innerWidth, 0.20);
-   }
+  initRatio() {
+    return Math.floor(Math.max(250 / window.innerWidth, 0.20) * 100);
+  }
+  initRatioRight() {
+    return 100 - this.initRatio();
+  }
+
 }
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.module.ts
index 30d70e6..c11ea44 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.module.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/navigator-view/mdm-navigator-view.module.ts
@@ -24,7 +24,6 @@
 import { MDMNavigatorModule } from '../navigator/mdm-navigator.module';
 import { MDMModulesModule } from '../modules/mdm-modules.module';
 import { MDMBasketModule } from '../basket/mdm-basket.module';
-import { SplitPaneModule } from 'ng2-split-pane/lib/ng2-split-pane';
 
 @NgModule({
   imports: [
@@ -32,8 +31,7 @@
     MDMNavigatorViewRoutingModule,
     MDMNavigatorModule,
     MDMModulesModule,
-    MDMBasketModule,
-    SplitPaneModule
+    MDMBasketModule
   ],
   declarations: [
     MDMNavigatorViewComponent
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/navigator/node.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/navigator/node.ts
index 74b58e4..7b11779 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/navigator/node.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/navigator/node.ts
@@ -20,6 +20,10 @@
   value: string;
   unit: string;
   dataType: string;
+  sortIndex: number;
+  readOnly: boolean;
+  mandatory: boolean;
+  description: string;
 }
 
 export class Relation {
@@ -28,6 +32,7 @@
   entityType: string;
   contextType: string;
   ids: string[];
+  parentId: string;
 }
 
 export class Node {
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/de.json b/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/de.json
index 906aeec..334a0a6 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/de.json
+++ b/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/de.json
@@ -80,11 +80,16 @@
 			"err-cannot-load-data": "Daten können nicht geladen werden.",
 			"err-cannot-load-scope": "Bereich kann nicht geladen werden.",
 			"status-loading": "Lädt...",
+      "status-saving": "Speichert...",
 			"status-no-descriptive-data-available": "Keine beschriebenden Daten verfügbar.",
 			"status-no-nodes-available": "Keine Knoten verfügbar.",
 			"tblhdr-measured": "Gemessen",
 			"tblhdr-name": "Name",
-			"tblhdr-ordered": "Beauftragt"
+      "tblhdr-ordered": "Beauftragt",
+      "btn-edit": "Bearbeiten",
+      "btn-cancel": "Abbrechen",
+      "btn-save": "Speichern",
+      "input-dateformat": "dd.mm.yy"
 		},
 		"mdm-detail-view": {
 			"cannot-update-node": "Knoten kann nicht aktualisiert werden."
@@ -318,4 +323,4 @@
 			"tooltip-select-view": "Ansicht auswählen"
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/en.json b/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/en.json
index 2ef7534..17e2c32 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/en.json
+++ b/org.eclipse.mdm.application/src/main/webapp/src/assets/i18n/en.json
@@ -75,17 +75,22 @@
 			"err-cannot-load-preference-for-attributes-to-ignore": "Cannot load preference for attributes to ignore.",
 			"err-faulty-preference-for-attributes-to-ignore": "Faulty preference for attributes to ignore."
 		},
-		"mdm-detail-descriptive-data": {
-			"err-cannot-load-context": "Cannot load context.",
-			"err-cannot-load-data": "Cannot load data.",
-			"err-cannot-load-scope": "Cannot load scope.",
-			"status-loading": "Loading...",
-			"status-no-descriptive-data-available": "No descriptive data available.",
-			"status-no-nodes-available": "No nodes available.",
-			"tblhdr-measured": "Measured",
-			"tblhdr-name": "Name",
-			"tblhdr-ordered": "Ordered"
-		},
+    "mdm-detail-descriptive-data": {
+      "err-cannot-load-context": "Cannot load context.",
+      "err-cannot-load-data": "Cannot load data.",
+      "err-cannot-load-scope": "Cannot load scope.",
+      "status-loading": "Loading...",
+      "status-saving": "Saving...",
+      "status-no-descriptive-data-available": "No descriptive data available.",
+      "status-no-nodes-available": "No nodes available.",
+      "tblhdr-measured": "Measured",
+      "tblhdr-name": "Name",
+      "tblhdr-ordered": "Ordered",
+      "btn-edit": "Edit",
+      "btn-cancel": "Cancel",
+      "btn-save": "Save",
+      "input-dateformat": "mm/dd/yy"
+    },
 		"mdm-detail-view": {
 			"cannot-update-node": "Cannot update node."
 		},
@@ -318,4 +323,4 @@
 			"tooltip-select-view": "Select view"
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/styles.css b/org.eclipse.mdm.application/src/main/webapp/src/styles.css
index ed2406c..795b6fa 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/styles.css
+++ b/org.eclipse.mdm.application/src/main/webapp/src/styles.css
@@ -165,4 +165,66 @@
 .mdm-link-list > li > a {
   cursor: pointer;
   color: #888;
-}
\ No newline at end of file
+}
+
+/**
+  Define style sheets for the MDM detail component viewer
+  All units in EM as they are scalable
+*/
+.mdm-details-attributes .ui-treetable-table thead > tr > th {
+  position: relative;
+}
+.mdm-details-attributes .ui-treetable-table thead > tr > th .inlinebuttons {
+  position: absolute;
+  right: 0.25em;
+  top: 0.25em;
+}
+.mdm-details-attributes .ui-treetable-table thead > tr > th .inlinebuttons button {
+  margin-left: 0.1em;
+}
+.mdm-details-attributes .ui-treetable-table tbody > tr > td {
+  position: relative;
+}
+.mdm-details-attributes .ui-treetable-table tbody > tr > td .inlinecontent {
+  position: absolute;
+  right: 0.25em;
+  top: 0.25em;
+}
+.mdm-details-attributes .ui-treetable-table tbody td {
+  line-height: 1;
+  padding: 0.15em 0.15em !important;
+}
+.mdm-details-attributes .ui-treetable-table tbody .mdm-component-row {
+  background-color: #e3e3e3;
+}
+
+.mainnavigation {
+  display: block;
+  position: relative;
+}
+.mainnavigation #leftsidenav .navbar {
+  margin-bottom: 0.5em !important;
+}
+.mainnavigation .split {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: 100%;
+}
+.mainnavigation .gutter {
+  background-color: #eee;
+  background-repeat: no-repeat;
+  background-position: 50%;
+  margin-left: 2px;
+  width: 8px !important;
+}
+.mainnavigation .gutter.gutter-horizontal {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
+}
+.mainnavigation .split,
+.mainnavigation .gutter.gutter-horizontal {
+  display: inline-block;
+  vertical-align: top;
+}
diff --git a/org.eclipse.mdm.application/src/main/webconfig/web.xml b/org.eclipse.mdm.application/src/main/webconfig/web.xml
index 22f3d19..bba2963 100644
--- a/org.eclipse.mdm.application/src/main/webconfig/web.xml
+++ b/org.eclipse.mdm.application/src/main/webconfig/web.xml
@@ -21,6 +21,7 @@
 		</web-resource-collection>
 		<auth-constraint>
 			<role-name>MDM</role-name>
+			<role-name>write-user</role-name>
 		</auth-constraint>
 	</security-constraint>
 
@@ -52,6 +53,10 @@
 		<role-name>MDM</role-name>
 	</security-role>
 
+	<security-role>
+		<role-name>write-user</role-name>
+	</security-role>
+
 	<login-config>
 		<auth-method>FORM</auth-method>
 		<realm-name>MDMRealm</realm-name>
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestResource.java
index 9c8cbb1..5ad9a12 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestResource.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestResource.java
@@ -21,6 +21,7 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 
+import javax.annotation.security.RolesAllowed;
 import javax.ejb.EJB;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -41,6 +42,7 @@
 import org.eclipse.mdm.api.base.model.Environment;
 import org.eclipse.mdm.api.base.model.FileLink;
 import org.eclipse.mdm.api.base.model.Test;
+import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.dflt.model.Classification;
 import org.eclipse.mdm.api.dflt.model.Domain;
 import org.eclipse.mdm.api.dflt.model.Pool;
@@ -48,9 +50,11 @@
 import org.eclipse.mdm.api.dflt.model.TemplateTest;
 import org.eclipse.mdm.api.dflt.model.ValueList;
 import org.eclipse.mdm.businessobjects.control.FileLinkActivity;
+import org.eclipse.mdm.businessobjects.entity.ContextResponse;
 import org.eclipse.mdm.businessobjects.entity.I18NResponse;
 import org.eclipse.mdm.businessobjects.entity.MDMEntityResponse;
 import org.eclipse.mdm.businessobjects.entity.SearchAttributeResponse;
+import org.eclipse.mdm.businessobjects.service.ContextService;
 import org.eclipse.mdm.businessobjects.service.EntityFileLink;
 import org.eclipse.mdm.businessobjects.service.EntityService;
 import org.eclipse.mdm.businessobjects.utils.RequestBody;
@@ -91,6 +95,9 @@
 	private EntityService entityService;
 
 	@EJB
+	private ContextService contextService;
+
+	@EJB
 	private FileLinkActivity fileLinkActivity;
 
 	/**
@@ -261,6 +268,7 @@
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("/{" + REQUESTPARAM_ID + "}")
+	@RolesAllowed({ "write-user" })
 	public Response update(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
 			@Parameter(description = "ID of the Test", required = true) @PathParam(REQUESTPARAM_ID) String id,
@@ -322,4 +330,45 @@
 						efl.getFileLink().getMimeType().toString()).build())
 				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
 	}
+
+	/**
+	 * delegates the request to the {@link TestStepService}
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         id of the {@link TestStep}
+	 * @return the result of the delegated request as {@link Response}
+	 */
+	@GET
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}/contexts")
+	public Response findContext(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@PathParam(REQUESTPARAM_ID) String id) {
+		return contextService.getTestContext(V(sourceName), V(id), false).map(ServiceUtils::contextMapToJava)
+				.map(ContextResponse::new).map(contextResponse -> ServiceUtils.toResponse(contextResponse, Status.OK))
+				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Updates the context of {@link TestStep} with all parameters set in the given
+	 * JSON body of the request.
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         the identifier of the {@link TestStep} to update.
+	 * @param body       the body of the request containing the attributes to update
+	 * @return the context map of the updated {@link TestStep}
+	 */
+	@PUT
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}/contexts")
+	@RolesAllowed({ "write-user" })
+	public Response updateContext(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@PathParam(REQUESTPARAM_ID) String id, String body) {
+
+		return entityService.find(V(sourceName), TestStep.class, V(id))
+				.map(testStep -> contextService.updateContext(body, testStep)).map(ContextResponse::new)
+				.map(contextResponse -> ServiceUtils.toResponse(contextResponse, Status.OK))
+				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
 }
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepResource.java
index 247f946..f0c221e 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepResource.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepResource.java
@@ -18,6 +18,7 @@
 import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_SOURCENAME;
 import static org.eclipse.mdm.businessobjects.service.EntityService.V;
 
+import javax.annotation.security.RolesAllowed;
 import javax.ejb.EJB;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -169,7 +170,7 @@
 	 * JSON body of the request.
 	 * 
 	 * @param sourceName name of the source (MDM {@link Environment} name)
-	 * @param id         the identifier of the {@link TestStepValue} to update.
+	 * @param id         the identifier of the {@link TestStep} to update.
 	 * @param body       the body of the request containing the attributes to update
 	 * @return the context map of the updated {@link TestStep}
 	 */
@@ -177,6 +178,7 @@
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("/{" + REQUESTPARAM_ID + "}/contexts")
+	@RolesAllowed({ "write-user" })
 	public Response updateContext(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
 			@PathParam(REQUESTPARAM_ID) String id, String body) {
 
@@ -210,7 +212,7 @@
 	 * with all parameters set in the given JSON body of the request.
 	 * 
 	 * @param sourceName name of the source (MDM {@link Environment} name)
-	 * @param id         the identifier of the {@link TestStepValue} to update.
+	 * @param id         the identifier of the {@link TestStep} to update.
 	 * @param body       the body of the request containing the attributes to update
 	 * @return the context map of {@link ContextType} UNITUNDERTEST of the updated
 	 *         {@link TestStep}
@@ -219,6 +221,7 @@
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("/{" + REQUESTPARAM_ID + "}/contexts/unitundertest")
+	@RolesAllowed({ "write-user" })
 	public Response updateContextUUT(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
 			@PathParam(REQUESTPARAM_ID) String id, String body) {
 
@@ -252,7 +255,7 @@
 	 * with all parameters set in the given JSON body of the request.
 	 * 
 	 * @param sourceName name of the source (MDM {@link Environment} name)
-	 * @param id         the identifier of the {@link TestStepValue} to update.
+	 * @param id         the identifier of the {@link TestStep} to update.
 	 * @param body       the body of the request containing the attributes to update
 	 * @return the context map of {@link ContextType} TESTSEQUENCE of the updated
 	 *         {@link TestStep}
@@ -261,6 +264,7 @@
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("/{" + REQUESTPARAM_ID + "}/contexts/testsequence")
+	@RolesAllowed({ "write-user" })
 	public Response updateContextTSQ(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
 			@PathParam(REQUESTPARAM_ID) String id, String body) {
 
@@ -294,7 +298,7 @@
 	 * with all parameters set in the given JSON body of the request.
 	 * 
 	 * @param sourceName name of the source (MDM {@link Environment} name)
-	 * @param id         the identifier of the {@link TestStepValue} to update.
+	 * @param id         the identifier of the {@link TestStep} to update.
 	 * @param body       the body of the request containing the attributes to update
 	 * @return the context map of {@link ContextType} TESTEQUIPMENT of the updated
 	 *         {@link TestStep}
@@ -303,6 +307,7 @@
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("/{" + REQUESTPARAM_ID + "}/contexts/testequipment")
+	@RolesAllowed({ "write-user" })
 	public Response updateContextTEQ(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
 			@PathParam(REQUESTPARAM_ID) String id, String body) {
 
@@ -382,7 +387,7 @@
 	 * of the request.
 	 * 
 	 * @param sourceName name of the source (MDM {@link Environment} name)
-	 * @param id         the identifier of the {@link TestStepValue} to update.
+	 * @param id         the identifier of the {@link TestStep} to update.
 	 * @param body       the body of the request containing the attributes to update
 	 * @return the updated {@link TestStep}
 	 */
@@ -393,6 +398,7 @@
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
 	@Path("/{" + REQUESTPARAM_ID + "}")
+	@RolesAllowed({ "write-user" })
 	public Response update(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
 			@Parameter(description = "ID of the TestStep", required = true) @PathParam(REQUESTPARAM_ID) String id,
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UserResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UserResource.java
new file mode 100644
index 0000000..26737fb
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UserResource.java
@@ -0,0 +1,74 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.businessobjects.boundary;
+
+import org.eclipse.mdm.businessobjects.control.MDMEntityAccessException;
+import org.eclipse.mdm.businessobjects.entity.User;
+
+import javax.annotation.security.PermitAll;
+import javax.security.auth.Subject;
+import javax.security.jacc.PolicyContext;
+import javax.security.jacc.PolicyContextException;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.*;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+
+@Path("/user")
+@PermitAll
+public class UserResource {
+
+	@Context
+	public SecurityContext securityContext;
+
+	@GET
+	@Path("/current")
+	@Produces(MediaType.APPLICATION_JSON)
+	public Response currentUser() {
+		User user = new User();
+		user.setUsername(securityContext.getUserPrincipal().getName());
+		user.setRoles(getRolesInGlassfish());
+		return Response.ok(user).build();
+	}
+
+	/**
+	 * Getting the roles of a user is heavily dependent on the used application
+	 * server. This method will only work with Glassfish. (It was tested with
+	 * Glassfish 4.1.2). The following blog entry gives a good overview of the
+	 * issues concerning JAAS.
+	 *
+	 * @see https://arjan-tijms.omnifaces.org/2014/02/jaas-in-java-ee-is-not-universal.html
+	 * @return list of rules of the logged in user.
+	 */
+	private static List<String> getRolesInGlassfish() {
+		List<String> list = new ArrayList<>();
+
+		try {
+			Subject subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
+			for (Principal principal : subject.getPrincipals()) {
+				if ("org.glassfish.security.common.Group".equals(principal.getClass().getName())) {
+					Principal role = (Principal) principal;
+					list.add(role.getName());
+				}
+			}
+		} catch (PolicyContextException e) {
+			throw new MDMEntityAccessException("Could not load roles for user!", e);
+		}
+		return list;
+	}
+}
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/ContextCollection.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/ContextCollection.java
index 23c8eee..469559d 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/ContextCollection.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/ContextCollection.java
@@ -15,6 +15,7 @@
 package org.eclipse.mdm.businessobjects.entity;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -32,9 +33,9 @@
 public class ContextCollection {
 
 	// consistent naming to ContextActivity.CONTEXT_GROUP_MEASURED
-	public Map<ContextType, List<MDMEntity>> measured = new HashMap<>();
+	public Map<ContextType, List<MDMContextEntity>> measured = new HashMap<>();
 	// consistent naming to ContextActivity.CONTEXT_GROUP_ORDERED
-	public Map<ContextType, List<MDMEntity>> ordered = new HashMap<>();
+	public Map<ContextType, List<MDMContextEntity>> ordered = new HashMap<>();
 
 	/**
 	 * set the measured context data map
@@ -51,9 +52,21 @@
 			this.measured.put(contextType, new ArrayList<>());
 
 			for (ContextComponent contextComponent : contextRoot.getContextComponents()) {
-				MDMEntity entity = new MDMEntity(contextComponent);
+				MDMContextEntity entity = new MDMContextEntity(contextComponent);
 				this.measured.get(contextType).add(entity);
 			}
+
+			// sort by SortIndex
+			Collections.sort(this.measured.get(contextType), (o1, o2) -> {
+				if (o1.getSortIndex() == null) {
+					return -1;
+				}
+				if (o2.getSortIndex() == null) {
+					return 1;
+				}
+				return o1.getSortIndex().compareTo(o2.getSortIndex());
+			});
+
 		}
 	}
 
@@ -72,9 +85,20 @@
 			this.ordered.put(contextType, new ArrayList<>());
 
 			for (ContextComponent contextComponent : contextRoot.getContextComponents()) {
-				MDMEntity entity = new MDMEntity(contextComponent);
+				MDMContextEntity entity = new MDMContextEntity(contextComponent);
 				this.ordered.get(contextType).add(entity);
 			}
+
+			// sort by SortIndex
+			Collections.sort(this.ordered.get(contextType), (o1, o2) -> {
+				if (o1.getSortIndex() == null) {
+					return -1;
+				}
+				if (o2.getSortIndex() == null) {
+					return 1;
+				}
+				return o1.getSortIndex().compareTo(o2.getSortIndex());
+			});
 		}
 	}
 
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextAttribute.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextAttribute.java
new file mode 100644
index 0000000..51bb8c3
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextAttribute.java
@@ -0,0 +1,69 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.businessobjects.entity;
+
+/**
+ * Attribute (Entity for attribute information)
+ *
+ * @author Juergen Kleck, Peak Solution GmbH
+ *
+ */
+public class MDMContextAttribute extends MDMAttribute {
+
+	/** The sort index of the template */
+	private final Integer sortIndex;
+	/** boolean flag if this attribute is readonly or writeable in edit mode */
+	private final Boolean readOnly;
+	/** boolean flag if this attribute is mandatory in edit mode */
+	private final Boolean mandatory;
+	/** description text of this attribute */
+	private final String description;
+
+	/**
+	 * Constructor
+	 *
+	 * @param name      name of the attribute value
+	 * @param value     value of the attribute value
+	 * @param unit      unit of the attribute value
+	 * @param dataType  data type of the attribute value
+	 * @param sortIndex optional sort index of the attribute value
+	 * @param readOnly  optional flag if it is readonly in edit mode
+	 * @param mandatory optional flag if it is mandatory in edit mode
+	 */
+	public MDMContextAttribute(String name, Object value, String unit, String dataType, Integer sortIndex,
+			Boolean readOnly, Boolean mandatory, String description) {
+		super(name, value, unit, dataType);
+		this.sortIndex = sortIndex;
+		this.readOnly = readOnly;
+		this.mandatory = mandatory;
+		this.description = description;
+	}
+
+	public Integer getSortIndex() {
+		return sortIndex;
+	}
+
+	public Boolean getReadOnly() {
+		return readOnly;
+	}
+
+	public Boolean getMandatory() {
+		return mandatory;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+}
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextEntity.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextEntity.java
new file mode 100644
index 0000000..63b90c2
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextEntity.java
@@ -0,0 +1,316 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.businessobjects.entity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.mdm.api.base.adapter.Core;
+import org.eclipse.mdm.api.base.model.BaseEntity;
+import org.eclipse.mdm.api.base.model.ContextComponent;
+import org.eclipse.mdm.api.base.model.Deletable;
+import org.eclipse.mdm.api.base.model.Entity;
+import org.eclipse.mdm.api.base.model.Value;
+import org.eclipse.mdm.api.dflt.model.TemplateAttribute;
+import org.eclipse.mdm.api.dflt.model.TemplateComponent;
+import org.eclipse.mdm.businessobjects.utils.Serializer;
+import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
+
+import io.vavr.collection.HashMap;
+
+/**
+ * MDMEntity (Entity for a business object (contains a list of
+ * {@link MDMContextAttribute}s)
+ *
+ * @author Juergen Kleck, Peak Solution GmbH
+ */
+public class MDMContextEntity extends MDMEntity {
+
+	/**
+	 * sort index of the MDM business object
+	 */
+	private final Integer sortIndex;
+	/**
+	 * list of attribute to transfer
+	 */
+	private List<MDMContextAttribute> attributes;
+	/**
+	 * list of relations to transfer
+	 */
+	private List<MDMContextRelation> relations;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param entity the business object
+	 */
+	public MDMContextEntity(Entity entity) {
+		super(entity);
+		// special handling for context component
+		if (entity instanceof ContextComponent) {
+			TemplateComponent tplCmp = TemplateComponent.of((ContextComponent) entity).get();
+			this.sortIndex = tplCmp.getSortIndex();
+			this.attributes = convertAttributeValues(entity.getValues(), tplCmp.getTemplateAttributes());
+			this.relations = convertRelations(entity, tplCmp);
+		} else {
+			this.sortIndex = 0;
+			this.attributes = convertAttributeValues(entity.getValues(), null);
+			this.relations = convertRelations(entity, null);
+		}
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @param name       Name of the Entity
+	 * @param id         ID of the Entity
+	 * @param type       Type of the Entity
+	 * @param sourceType Source type of the Entity
+	 * @param sourceName Source name of the Entity
+	 * @param attributes Attributes of the Entity
+	 */
+	public MDMContextEntity(String name, String id, String type, String sourceType, String sourceName,
+			List<MDMContextAttribute> attributes) {
+		super(name, id, type, sourceType, sourceName, Collections.emptyList());
+		this.sortIndex = 0;
+		if (attributes != null) {
+			this.attributes = new ArrayList<>(attributes);
+		} else {
+			this.attributes = new ArrayList<>();
+		}
+		this.relations = new ArrayList<>();
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @param name       Name of the Entity
+	 * @param id         ID of the Entity
+	 * @param type       Type of the Entity
+	 * @param sourceType Source type of the Entity
+	 * @param sourceName Source name of the Entity
+	 * @param attributes Attributes of the Entity
+	 * @param relations  Relations of the Entity
+	 */
+	public MDMContextEntity(String name, String id, String type, String sourceType, String sourceName,
+			List<MDMContextAttribute> attributes, List<MDMRelation> relations) {
+		super(name, id, type, sourceType, sourceName, Collections.emptyList(), relations);
+		this.sortIndex = 0;
+		this.attributes = new ArrayList<>(attributes);
+		this.relations = new ArrayList<>();
+	}
+
+	public Integer getSortIndex() {
+		return sortIndex;
+	}
+
+	public List<MDMAttribute> getAttributes() {
+		return Collections.unmodifiableList(this.attributes);
+	}
+
+	public List<MDMRelation> getRelations() {
+		return Collections.unmodifiableList(this.relations);
+	}
+
+	/**
+	 * converts the MDM business object values to string values
+	 *
+	 * @param values        values of a MDM business object
+	 * @param tplAttributes optional list of template attributes
+	 * @return list with converted attribute values
+	 */
+	private List<MDMContextAttribute> convertAttributeValues(Map<String, Value> values,
+			List<TemplateAttribute> tplAttributes) {
+		List<MDMContextAttribute> listAttrs = new ArrayList<>();
+		Set<Map.Entry<String, Value>> set = values.entrySet();
+
+		for (Map.Entry<String, Value> entry : set) {
+
+			if (entry.getKey().equals(BaseEntity.ATTR_ID)) {
+				continue;
+			}
+
+			TemplateAttribute tplAttr = null;
+
+			if (tplAttributes != null && !tplAttributes.isEmpty()) {
+				tplAttr = tplAttributes.stream().filter(tpl -> tpl.getName().equals(entry.getKey())).findFirst()
+						.orElse(null);
+			}
+
+			if (!entry.getValue().isValid()) {
+				String dt = entry.getValue().getValueType().toString();
+				Integer sortIndex = null;
+				Boolean mandatory = null;
+				Boolean readOnly = null;
+				String description = null;
+				if (tplAttr != null) {
+					sortIndex = tplAttr.getCatalogAttribute().getSortIndex();
+					description = tplAttr.getCatalogAttribute().getValue("Description").extract();
+					mandatory = (Boolean) tplAttr.getValue("Obligatory").extract();
+					readOnly = (Boolean) tplAttr.getValue("ValueReadonly").extract();
+				}
+				listAttrs.add(new MDMContextAttribute(entry.getKey(), "", "", dt, sortIndex, readOnly, mandatory,
+						description));
+				continue;
+			}
+
+			if (entry.getValue().getValueType().isSequence()) {
+				listAttrs.add(sequenceType2Attribute(entry.getKey(), entry.getValue(), tplAttr));
+			} else {
+				listAttrs.add(singleType2Attribute(entry.getKey(), entry.getValue(), tplAttr));
+			}
+		}
+
+		// clear unneeded elements
+		listAttrs.removeIf(attr -> attr.getName().equals("Name") || attr.getName().equals("MimeType") || attr.getName()
+				.equals("SortIndex"));
+
+		// pre-sort attributes by sort index
+		Collections.sort(listAttrs, (o1, o2) -> {
+			if (o1.getSortIndex() == null) {
+				return -1;
+			}
+			if (o2.getSortIndex() == null) {
+				return 1;
+			}
+			return o1.getSortIndex().compareTo(o2.getSortIndex());
+		});
+
+		return listAttrs;
+	}
+
+	/**
+	 * converts a single type MDM business object value to a attribute
+	 *
+	 * @param name        name of the attribute value
+	 * @param singleValue single MDM business object value
+	 * @return the converted attribute value
+	 */
+	private MDMContextAttribute singleType2Attribute(String name, Value singleValue, TemplateAttribute tplAttr) {
+		Object value = Serializer.serializeValue(singleValue);
+		String unit = singleValue.getUnit();
+		String dt = singleValue.getValueType().toString();
+		Integer sortIndex = null;
+		Boolean mandatory = null;
+		Boolean readOnly = null;
+		String description = null;
+		if (tplAttr != null) {
+			sortIndex = tplAttr.getCatalogAttribute().getSortIndex();
+			description = tplAttr.getCatalogAttribute().getValue("Description").extract();
+			mandatory = (Boolean) tplAttr.getValue("Obligatory").extract();
+			readOnly = (Boolean) tplAttr.getValue("ValueReadonly").extract();
+		}
+		return new MDMContextAttribute(name, value, unit, dt, sortIndex, readOnly, mandatory, description);
+	}
+
+	/**
+	 * converts a sequence type MDM business object value to a attribute
+	 *
+	 * @param name          name of the attribute value
+	 * @param sequenceValue sequence MDM business object value
+	 * @return the converted attribute value
+	 */
+	private MDMContextAttribute sequenceType2Attribute(String name, Value sequenceValue, TemplateAttribute tplAttr) {
+
+		if (sequenceValue.getValueType().isStringSequence()) {
+			return stringSeq2Attribute(name, sequenceValue, tplAttr);
+		}
+
+		String dt = sequenceValue.getValueType().toString();
+		String defValue = "sequence type '" + dt + "' not implemented yet";
+		Integer sortIndex = null;
+		Boolean mandatory = null;
+		Boolean readOnly = null;
+		String description = null;
+		if (tplAttr != null) {
+			sortIndex = tplAttr.getCatalogAttribute().getSortIndex();
+			description = tplAttr.getCatalogAttribute().getValue("Description").extract();
+			mandatory = (Boolean) tplAttr.getValue("Obligatory").extract();
+			readOnly = (Boolean) tplAttr.getValue("ValueReadonly").extract();
+		}
+		return new MDMContextAttribute(name, defValue, "", dt, sortIndex, readOnly, mandatory, description);
+	}
+
+	/**
+	 * converts a string sequence MDM business object value to a attribute The
+	 * result is a separated string (separator: ';')
+	 *
+	 * @param name  name of the attribute value
+	 * @param value string sequence MDM business object value
+	 * @return the converted attribute value
+	 */
+	private MDMContextAttribute stringSeq2Attribute(String name, Value value, TemplateAttribute tplAttr) {
+		String[] stringSeq = value.extract();
+		StringBuffer sb = new StringBuffer();
+
+		for (String stringSeqValue : stringSeq) {
+			sb.append(";").append(stringSeqValue);
+		}
+
+		String stringValue = sb.toString().replaceFirst(";", "");
+		String unit = value.getUnit();
+		String dt = value.getValueType().toString();
+		Integer sortIndex = null;
+		Boolean mandatory = null;
+		Boolean readOnly = null;
+		String description = null;
+		if (tplAttr != null) {
+			sortIndex = tplAttr.getCatalogAttribute().getSortIndex();
+			description = tplAttr.getCatalogAttribute().getValue("Description").extract();
+			mandatory = (Boolean) tplAttr.getValue("Obligatory").extract();
+			readOnly = (Boolean) tplAttr.getValue("ValueReadonly").extract();
+		}
+		return new MDMContextAttribute(name, stringValue, unit, dt, sortIndex, readOnly, mandatory, description);
+	}
+
+	/**
+	 * Converts all relations to MDMContextRelations. The potential ContextType of
+	 * the children is assumed to be the parent's one.
+	 *
+	 * @param entity to get {@link MDMContextRelation}s for
+	 * @return a list of {@link MDMContextRelation}s
+	 */
+	private List<MDMContextRelation> convertRelations(Entity entity, TemplateComponent tplCmp) {
+		// write out children
+		Core core = ServiceUtils.getCore(entity);
+		// get children hash (type is key)
+		return HashMap.ofAll(core.getChildrenStore().getCurrent())
+				// create child relations (the ContextType is assumed to be same as the parent's
+				// check if a value exists before accessing it hardcoded
+				.filter(entry -> entry._2 != null && entry._2.size() > 0)
+				// one
+				.map(entry -> new MDMContextRelation(null, MDMRelation.RelationType.CHILDREN, entry._1.getSimpleName(),
+						ServiceUtils.getContextType(entry._2.get(0)),
+						// call get id on all related entities
+						io.vavr.collection.List.ofAll(entry._2).map(Deletable::getID).asJava(),
+						tplCmp.getParentTemplateComponent().isPresent() ?
+								tplCmp.getParentTemplateComponent().get().getID() :
+								null))
+				// append related entities from the mutable store
+				.appendAll(io.vavr.collection.List.ofAll(core.getMutableStore().getCurrent()).
+						// convert list to map: use simple class name as this is the MDM entity type
+								groupBy(e -> e.getClass().getSimpleName())
+						// create relation for every entry
+						.map(entry -> new MDMContextRelation(null, MDMRelation.RelationType.MUTABLE, entry._1,
+								ServiceUtils.getContextType(entry._2.get(0)), entry._2.map(Entity::getID).asJava(),
+								tplCmp.getParentTemplateComponent().isPresent() ?
+										tplCmp.getParentTemplateComponent().get().getID() :
+										null))).asJava();
+	}
+
+}
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextRelation.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextRelation.java
new file mode 100644
index 0000000..7302bda
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextRelation.java
@@ -0,0 +1,57 @@
+/********************************************************************************
+ * Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.businessobjects.entity;
+
+import java.util.List;
+
+import org.eclipse.mdm.api.base.model.ContextType;
+
+/**
+ * Relation (Entity for relation information)
+ *
+ * @author Juergen Kleck, Peak Solution GmbH
+ *
+ */
+public class MDMContextRelation extends MDMRelation {
+
+	private String parentId;
+
+	/**
+	 * Default constructor used for entity deserialization
+	 */
+	public MDMContextRelation() {
+
+	}
+
+	/**
+	 * Constructor
+	 *
+	 * @param name        name of the relation
+	 * @param type        type of the relation
+	 * @param entityType  type of the related entity
+	 * @param contextType ContextType of the entity if the entityType has one
+	 * @param ids         ids of the related entities
+	 * @param parentId    the parent template id
+	 */
+	public MDMContextRelation(String name, RelationType type, String entityType, ContextType contextType,
+			List<String> ids, String parentId) {
+		super(name, type, entityType, contextType, ids);
+		this.parentId = parentId;
+	}
+
+	public String getParentId() {
+		return parentId;
+	}
+}
\ No newline at end of file
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMEntity.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMEntity.java
index 2de08d3..ff8cfa9 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMEntity.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMEntity.java
@@ -35,34 +35,46 @@
 /**
  * MDMEntity (Entity for a business object (contains a list of
  * {@link MDMAttribute}s)
- * 
- * @author Sebastian Dirsch, Gigatronik Ingolstadt GmbH
  *
+ * @author Sebastian Dirsch, Gigatronik Ingolstadt GmbH
  */
-@Schema(description = "Representation of a MDM entity")
-public class MDMEntity {
+@Schema(description = "Representation of a MDM entity") public class MDMEntity {
 
-	/** name of the MDM business object */
+	/**
+	 * name of the MDM business object
+	 */
 	private final String name;
-	/** id of the MDM business object */
+	/**
+	 * id of the MDM business object
+	 */
 	private final String id;
-	/** type as String of the MDM business object (e.g. TestStep) */
+	/**
+	 * type as String of the MDM business object (e.g. TestStep)
+	 */
 	private final String type;
-	/** source type name of the business object at the data source */
+	/**
+	 * source type name of the business object at the data source
+	 */
 	private final String sourceType;
-	/** source name (e.g. MDM Environment name) */
+	/**
+	 * source name (e.g. MDM Environment name)
+	 */
 	private final String sourceName;
-	/** list of attribute to transfer */
+	/**
+	 * list of attribute to transfer
+	 */
 	private List<MDMAttribute> attributes;
 
-	/** list of relations to transfer */
+	/**
+	 * list of relations to transfer
+	 */
 	private List<MDMRelation> relations;
 
 	private String status;
 
 	/**
 	 * Constructor.
-	 * 
+	 *
 	 * @param entity the business object
 	 */
 	public MDMEntity(Entity entity) {
@@ -77,7 +89,7 @@
 
 	/**
 	 * Constructor.
-	 * 
+	 *
 	 * @param name       Name of the Entity
 	 * @param id         ID of the Entity
 	 * @param type       Type of the Entity
@@ -92,13 +104,17 @@
 		this.type = type;
 		this.sourceType = sourceType;
 		this.sourceName = sourceName;
-		this.attributes = new ArrayList<>(attributes);
+		if (attributes != null) {
+			this.attributes = new ArrayList<>(attributes);
+		} else {
+			this.attributes = new ArrayList<>();
+		}
 		this.relations = new ArrayList<>();
 	}
 
 	/**
 	 * Constructor.
-	 * 
+	 *
 	 * @param name       Name of the Entity
 	 * @param id         ID of the Entity
 	 * @param type       Type of the Entity
@@ -148,7 +164,7 @@
 
 	/**
 	 * converts the MDM business object values to string values
-	 * 
+	 *
 	 * @param values values of a MDM business object
 	 * @return list with converted attribute values
 	 */
@@ -180,7 +196,7 @@
 
 	/**
 	 * converts a single type MDM business object value to a attribute
-	 * 
+	 *
 	 * @param name        name of the attribute value
 	 * @param singleValue single MDM business object value
 	 * @return the converted attribute value
@@ -194,7 +210,7 @@
 
 	/**
 	 * converts a sequence type MDM business object value to a attribute
-	 * 
+	 *
 	 * @param name          name of the attribute value
 	 * @param sequenceValue sequence MDM business object value
 	 * @return the converted attribute value
@@ -217,7 +233,7 @@
 	/**
 	 * converts a string sequence MDM business object value to a attribute The
 	 * result is a separated string (separator: ';')
-	 * 
+	 *
 	 * @param name  name of the attribute value
 	 * @param value string sequence MDM business object value
 	 * @return the converted attribute value
@@ -239,7 +255,7 @@
 	/**
 	 * Converts all relations to MDMRelations. The potential ContextType of the
 	 * children is assumed to be the parent's one.
-	 * 
+	 *
 	 * @param entity to get
 	 *               {@link org.eclipse.mdm.businessobjects.entity.MDMRelation}s for
 	 * @return a list of {@link org.eclipse.mdm.businessobjects.entity.MDMRelation}s
@@ -257,8 +273,8 @@
 						io.vavr.collection.List.ofAll(entry._2).map(Deletable::getID).asJava()))
 				// append related entities from the mutable store
 				.appendAll(io.vavr.collection.List.ofAll(core.getMutableStore().getCurrent()).
-				// convert list to map: use simple class name as this is the MDM entity type
-						groupBy(e -> e.getClass().getSimpleName())
+						// convert list to map: use simple class name as this is the MDM entity type
+								groupBy(e -> e.getClass().getSimpleName())
 						// create relation for every entry
 						.map(entry -> new MDMRelation(null, MDMRelation.RelationType.MUTABLE, entry._1,
 								ServiceUtils.getContextType(entry._2.get(0)), entry._2.map(Entity::getID).asJava())))
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/User.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/User.java
new file mode 100644
index 0000000..90fcb81
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/User.java
@@ -0,0 +1,35 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.businessobjects.entity;
+
+import java.util.List;
+
+public class User {
+	private String username;
+	private List<String> roles;
+	
+	public String getUsername() {
+		return username;
+	}
+	public void setUsername(String username) {
+		this.username = username;
+	}
+	public List<String> getRoles() {
+		return roles;
+	}
+	public void setRoles(List<String> roles) {
+		this.roles = roles;
+	}
+}
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
index e6e5aa2..2db1b02 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
@@ -17,6 +17,7 @@
 import static org.eclipse.mdm.businessobjects.control.ContextActivity.CONTEXT_GROUP_MEASURED;
 import static org.eclipse.mdm.businessobjects.control.ContextActivity.CONTEXT_GROUP_ORDERED;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.NoSuchElementException;
@@ -34,15 +35,18 @@
 import org.eclipse.mdm.api.base.model.Entity;
 import org.eclipse.mdm.api.base.model.Environment;
 import org.eclipse.mdm.api.base.model.Measurement;
+import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.dflt.EntityManager;
 import org.eclipse.mdm.api.dflt.model.EntityFactory;
+import org.eclipse.mdm.api.dflt.model.TemplateComponent;
 import org.eclipse.mdm.api.dflt.model.TemplateRoot;
 import org.eclipse.mdm.api.dflt.model.TemplateTestStep;
 import org.eclipse.mdm.businessobjects.control.ContextActivity;
 import org.eclipse.mdm.businessobjects.control.MDMEntityAccessException;
 import org.eclipse.mdm.businessobjects.entity.MDMAttribute;
-import org.eclipse.mdm.businessobjects.entity.MDMEntity;
+import org.eclipse.mdm.businessobjects.entity.MDMContextAttribute;
+import org.eclipse.mdm.businessobjects.entity.MDMContextEntity;
 import org.eclipse.mdm.businessobjects.utils.RequestBody;
 import org.eclipse.mdm.businessobjects.utils.Serializer;
 import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
@@ -83,6 +87,31 @@
 	 * Possible {@link ContextType}s are {@link ContextType}.UNITUNDERTEST,
 	 * {@link ContextType}.TESTSEQUENCE and {@link ContextType}.TESTEQUIPMENT.
 	 * 
+	 * @param sourceName      name of the source (MDM {@link Environment} name)
+	 * @param testId          the id of {@link TestStep} context is looked up for
+	 * @param includeVariable true then teststep variable data is included, false
+	 *                        only test constant data is included
+	 * @param contextTypes    list of {@link ContextType}s
+	 * @return the ordered and measured context data as context object for the
+	 *         identified {@link TestStep}
+	 */
+	public Try<Map<String, Map<ContextType, ContextRoot>>> getTestContext(Value<String> sourceName,
+			Value<String> testId, boolean includeVariable, ContextType... contextTypes) {
+		Try<Test> test = entityService.find(sourceName, Test.class, testId);
+		return getTestContext(sourceName, test, includeVariable, contextTypes);
+	}
+
+	/**
+	 * Vavr conform version of contextActivity getTestStepContext function.
+	 *
+	 * returns the ordered and measurement context for a {@link TestStep}. If no
+	 * {@link ContextType}s are defined for this method call, the method returns all
+	 * context informations of the available {@link ContextType}s. Otherwise you can
+	 * specify a list of {@link ContextType}s.
+	 *
+	 * Possible {@link ContextType}s are {@link ContextType}.UNITUNDERTEST,
+	 * {@link ContextType}.TESTSEQUENCE and {@link ContextType}.TESTEQUIPMENT.
+	 *
 	 * @param sourceName   name of the source (MDM {@link Environment} name)
 	 * @param testStepId   the id of {@link TestStep} context is looked up for
 	 * @param contextTypes list of {@link ContextType}s
@@ -130,6 +159,105 @@
 	}
 
 	/**
+	 * Vavr conform version of contextActivity getTestContext function.
+	 *
+	 * returns the ordered and measurement context for a {@link Test}. If no
+	 * {@link ContextType}s are defined for this method call, the method returns all
+	 * context informations of the available {@link ContextType}s. Otherwise you can
+	 * specify a list of {@link ContextType}s.
+	 *
+	 * Possible {@link ContextType}s are {@link ContextType}.UNITUNDERTEST,
+	 * {@link ContextType}.TESTSEQUENCE and {@link ContextType}.TESTEQUIPMENT.
+	 *
+	 * @param sourceName      name of the source (MDM {@link Environment} name)
+	 * @param test            {@link Try} of the {@link Test}
+	 * @param includeVariable true then teststep variable data is included, false
+	 *                        only test constant data is included
+	 * @param contextTypes    list of {@link ContextType}s
+	 * @return the ordered and measured context data as context object for the
+	 *         identified {@link TestStep}
+	 */
+	private Try<Map<String, Map<ContextType, ContextRoot>>> getTestContext(Value<String> sourceName, Try<Test> test,
+			boolean includeVariable, ContextType... contextTypes) {
+
+		// init an empty hashmap
+		HashMap<ContextType, ContextRoot> mapOrdered = HashMap.empty();
+		HashMap<ContextType, ContextRoot> mapMeasured = HashMap.empty();
+
+		if (test.isSuccess()) {
+
+			java.util.List<TestStep> list = getEntityManager(sourceName).get().loadChildren(test.get(), TestStep.class);
+
+			// merge all attributes from all test steps together
+			for (TestStep testStep : list) {
+				Try<Map<ContextType, ContextRoot>> contextOrdered = getEntityManager(sourceName)
+						.map(e -> HashMap.ofAll(e.loadContexts(testStep, contextTypes)));
+
+				if (contextOrdered.isSuccess()) {
+					mapOrdered = mapOrdered.merge(contextOrdered.get());
+				}
+
+				Try<Map<ContextType, ContextRoot>> contextMeasured = getEntityManager(sourceName).map(e -> HashMap
+						.ofAll(e.loadContexts(findMeasurements(sourceName, testStep).get().get(), contextTypes)));
+
+				if (contextMeasured.isSuccess()) {
+					mapMeasured = mapMeasured.merge(contextMeasured.get());
+				}
+			}
+
+			// filter data out to reduce traffic in the json rest service
+			if (!includeVariable) {
+				mapOrdered.values().forEach(ctxRoot -> {
+					java.util.List<ContextComponent> removals = new ArrayList<>();
+					ctxRoot.getContextComponents().forEach(ctxCmp -> {
+						if (TemplateComponent.of(ctxCmp).isPresent()) {
+							org.eclipse.mdm.api.base.model.Value attr = TemplateComponent.of(ctxCmp).get()
+									.getValue("TestStepSeriesVariable");
+							if (attr.isValid() && ((Boolean) attr.extract()).booleanValue()) {
+								removals.add(ctxCmp);
+							}
+						}
+					});
+					for (ContextComponent ctxCmp : removals) {
+						ctxRoot.removeContextComponent(ctxCmp.getName());
+					}
+				});
+				mapMeasured.values().forEach(ctxRoot -> {
+					java.util.List<ContextComponent> removals = new ArrayList<>();
+					ctxRoot.getContextComponents().forEach(ctxCmp -> {
+						if (TemplateComponent.of(ctxCmp).isPresent()) {
+							org.eclipse.mdm.api.base.model.Value attr = TemplateComponent.of(ctxCmp).get()
+									.getValue("TestStepSeriesVariable");
+							if (attr.isValid() && ((Boolean) attr.extract()).booleanValue()) {
+								removals.add(ctxCmp);
+							}
+						}
+					});
+					for (ContextComponent ctxCmp : removals) {
+						ctxRoot.removeContextComponent(ctxCmp.getName());
+					}
+				});
+			}
+		}
+
+		// set final for follow-up lambda
+		final HashMap<ContextType, ContextRoot> tmpMapOrdered = mapOrdered;
+		final HashMap<ContextType, ContextRoot> tmpMapMeasured = mapMeasured;
+
+		// convert to try class object
+		Try<Map<ContextType, ContextRoot>> contextOrdered = Try.of(() -> tmpMapOrdered);
+		Try<Map<ContextType, ContextRoot>> contextMeasured = Try.of(() -> tmpMapMeasured);
+
+		return Try
+				.of(() -> Lazy
+						.of(() -> HashMap.of(CONTEXT_GROUP_ORDERED,
+								contextOrdered.recover(NoSuchElementException.class, t -> HashMap.empty()).get(),
+								CONTEXT_GROUP_MEASURED,
+								contextMeasured.recover(NoSuchElementException.class, t -> HashMap.empty()).get()))
+						.get());
+	}
+
+	/**
 	 * Vavr conform version of contextActivity getMeasurementContext function.
 	 * 
 	 * returns the ordered and measurement context for a {@link Measurement}. If no
@@ -204,11 +332,22 @@
 	}
 
 	/**
-	 * 
+	 *
 	 * @param sourceName
 	 * @param testStep
 	 * @return
 	 */
+	private Try<List<Measurement>> findMeasurements(Value<String> sourceName, TestStep testStep) {
+		return getEntityManager(sourceName)
+				.map(e -> Lazy.of(() -> List.ofAll(e.loadChildren(testStep, TestStep.CHILD_TYPE_MEASUREMENT))).get());
+	}
+
+	/**
+	 * 
+	 * @param sourceName
+	 * @param measurement
+	 * @return
+	 */
 	private Try<TestStep> findTestStep(Value<String> sourceName, Try<Measurement> measurement) {
 		return getEntityManager(sourceName)
 				.map(e -> Lazy.of(() -> e.loadParent(measurement.get(), Measurement.PARENT_TYPE_TESTSTEP).get()).get());
@@ -405,8 +544,8 @@
 		}
 	}
 
-	private MDMEntity transformToMDMEntity(Map<String, Object> component) {
-		return new MDMEntity(
+	private MDMContextEntity transformToMDMEntity(Map<String, Object> component) {
+		return new MDMContextEntity(
 				component.get("name").map(Object::toString)
 						.getOrElseThrow(() -> new MDMEntityAccessException("Missing attribute 'name' in MDMEntity")),
 				component.get("id").map(Object::toString).getOrElse(""),
@@ -416,7 +555,7 @@
 				transformList(component.get("attributes").getOrElseThrow(
 						() -> new MDMEntityAccessException("Missing attribute 'attributes' in MDMEntity")))
 								.map(this::transformMap)
-								.map(m -> new MDMAttribute(
+								.map(m -> new MDMContextAttribute(
 										m.get("name").map(Object::toString)
 												.getOrElseThrow(() -> new MDMEntityAccessException(
 														"Missing attribute 'name' in MDMAttribute")),
@@ -424,7 +563,7 @@
 												.getOrElseThrow(() -> new MDMEntityAccessException(
 														"Missing attribute 'value' in MDMAttribute")),
 										m.get("unit").map(Object::toString).getOrElse(""),
-										m.get("datatype").map(Object::toString).getOrElse("")))
+										m.get("datatype").map(Object::toString).getOrElse(""), null, null, null, null))
 								.toJavaList());
 	}
 }
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ISODateDeseralizer.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ISODateDeseralizer.java
index 122c743..7705277 100755
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ISODateDeseralizer.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ISODateDeseralizer.java
@@ -17,6 +17,7 @@
 import java.io.IOException;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.regex.Pattern;
 
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.core.JsonTokenId;
@@ -39,6 +40,9 @@
 
 	transient DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
 
+	// use pattern for the dateformat as we only compile this
+	private static final Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}\\:\\d{2}\\:\\d{2}Z");
+
 	/**
 	 * Deserialize JSON and try to parse every String as an ISO8601 date
 	 */
@@ -47,8 +51,9 @@
 		// try to parse every string as a date
 		// TODO anehmer on 2018-04-30: this approach could lead to a performance leak as
 		// every incoming string is tried to be converted into a date though the
-		// appraoch is very generic
-		if (jp.getCurrentTokenId() == JsonTokenId.ID_STRING) {
+		// approach is very generic
+		// Optimized with pre-compiled pattern to avoid random exception throwing
+		if (jp.getCurrentTokenId() == JsonTokenId.ID_STRING && jp.getTextLength() > 0 && pattern.matcher(jp.getText()).matches()) {
 			try {
 				return LocalDateTime.parse(jp.getText(), dateFormatter);
 			} catch (Exception e) {
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/RequestBody.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/RequestBody.java
index a39451a..0c45ad5 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/RequestBody.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/RequestBody.java
@@ -44,7 +44,8 @@
 	static {
 		mapper = new ObjectMapper();
 		SimpleModule simpleModule = new SimpleModule();
-		simpleModule.addDeserializer(Object.class, new ISODateDeseralizer());
+		// disabled deserializer as ContextService.updateContextDescribableContext invokes a parsing of ISO date anyways
+		//simpleModule.addDeserializer(Object.class, new ISODateDeseralizer());
 		mapper.registerModule(simpleModule);
 	}