552401 - Model mapping in atfxadapter with ExtSystem

added etities related to ExtSystem

Signed-off-by: Juergen Kleck <j.kleck@peak-solution.de>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.css b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.css
new file mode 100644
index 0000000..ea615fe
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.css
@@ -0,0 +1,20 @@
+@charset "ISO-8859-1";
+
+/********************************************************************************
+ * 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
+ *
+ ********************************************************************************/
+
+.navbar-vertical {}
+.navbar-vertical .navbar-nav {}
+.navbar-vertical .navbar-nav .navbar-brand {display:block;}
+.navbar-vertical .navbar-nav .nav-item {display:block;}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.html
index 8fbbf81..b240a99 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.html
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.html
@@ -12,19 +12,29 @@
  *
  ********************************************************************************-->
 
-<nav class="navbar navbar-default navbar-expand  bg-light">
-  <div class="container-fluid">
-    <div class="collapse navbar-collapse" id="bs-admin-navbar">
-      <a class="navbar-brand" style="font-weight: 500;">{{'administration.admin-modules.scope' | translate }}</a>
-      <ul class="nav navbar-nav mdm-link-list">
-        <li *ngFor="let m of links" [routerLinkActive]="['active']" class="nav-item">
-          <a routerLink="{{m.path}}" class="nav-link" style="cursor:pointer;">
-            {{m.name | translate}}
-          </a>
-        </li>
-      </ul>
-    </div>
-
+<vertical-split-pane primary-component-minsize="{{minWidthLeft()}}" secondary-component-minsize="{{minWidthRight()}}" primary-component-initialratio="{{initRatio()}}">
+  <div class="split-pane-content-primary">
+    <nav class="navbar navbar-default navbar-vertical bg-light">
+      <div class="container-fluid">
+        <div class="" id="bs-admin-navbar">
+          <a class="navbar-brand" style="font-weight: 500;">{{'administration.admin-modules.modules' | translate }}</a>
+          <ul class="nav navbar-nav mdm-link-list">
+            <li *ngFor="let m of links" [routerLinkActive]="['active']" class="nav-item">
+              <a routerLink="{{m.path}}" class="nav-link" style="cursor:pointer;">
+                {{m.name | translate}}
+              </a>
+            </li>
+          </ul>
+        </div>
+      </div>
+    </nav>
   </div>
-</nav>
-<router-outlet></router-outlet>
+  <div class="split-pane-content-secondary">
+    <div class="navigator-content" (scroll)=onScroll($event)>
+      <router-outlet></router-outlet>
+      <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>
+</vertical-split-pane>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.ts
index 1727754..f784dfe 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-modules.component.ts
@@ -17,18 +17,48 @@
 import {Router} from '@angular/router';
 
 import { TRANSLATE } from '../core/mdm-core.module';
+import { SplitPaneModule } from 'ng2-split-pane/lib/ng2-split-pane';
 
 @Component({
   selector: 'admin-modules',
   templateUrl: 'admin-modules.component.html',
+  styleUrls: ['./admin-modules.component.css'],
   providers: []
 })
 export class AdminModulesComponent {
 
+  div: any;
+  scrollBtnVisible = false;
+
   links = [
-    { name: TRANSLATE('administration.admin-modules.system'), path: 'system'},
-    { name: TRANSLATE('administration.admin-modules.source'), path: 'source'},
-    { name: TRANSLATE('administration.admin-modules.user'), path: 'user'}
+    { name: TRANSLATE('administration.admin-modules.preferences'), path: 'preferences'},
+    { name: TRANSLATE('administration.admin-modules.extsystems'), path: 'extsystems'}
   ];
-  constructor(private router: Router) {}
+  constructor(private router: Router) { }
+
+
+  minWidthLeft() {
+    return 180;
+  }
+
+  minWidthRight() {
+    return 0.20 * window.innerWidth;
+  }
+
+  initRatio() {
+    return Math.max(200 / window.innerWidth, 0.20);
+  }
+
+  onScrollTop() {
+    this.div.scrollTop = 0;
+  }
+
+  onScroll(event: any) {
+    if (event.target.scrollTop > 0) {
+      this.scrollBtnVisible = true;
+    } else {
+      this.scrollBtnVisible = false;
+    }
+    this.div = event.target;
+  }
 }
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-routing.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-routing.module.ts
index 719b94c..dd2921b 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-routing.module.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin-routing.module.ts
@@ -16,15 +16,17 @@
 import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
 
-import { PreferenceComponent } from './preference.component';
 import { AdminModulesComponent } from './admin-modules.component';
+import { ExtSystemComponent } from './extsystem.component';
+import { PreferenceRoutingModule } from './preference-routing.module';
 
 const moduleRoutes: Routes = [
   {
     path: '', component: AdminModulesComponent,
     children: [
-      { path: ':scope', component: PreferenceComponent },
-      { path: '', redirectTo: 'system', pathMatch: 'full' }
+      { path: 'preferences', loadChildren: './preference.module#PreferenceModule' },
+      { path: 'extsystems', component: ExtSystemComponent },
+      { path: '', redirectTo: 'preferences', pathMatch: 'full' }
     ]
   }
 ];
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin.module.ts
index 0e3839a..68eaff0 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin.module.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/admin.module.ts
@@ -17,32 +17,48 @@
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { ComponentLoaderFactory } from 'ngx-bootstrap/component-loader';
 
-import { PreferenceService } from '../core/preference.service';
 import { MDMCoreModule } from '../core/mdm-core.module';
+import { ExtSystemComponent } from './extsystem.component';
+import { ExtSystemViewerComponent } from './extsystem-viewer.component';
+import { ExtSystemEditorComponent } from './extsystem-editor.component';
 
 import { AdminModulesComponent } from './admin-modules.component';
 import { AdminRoutingModule } from './admin-routing.module';
-import { PreferenceComponent } from './preference.component';
-import { EditPreferenceComponent } from './edit-preference.component';
+import { ExtSystemService } from './extsystem.service';
+import { SplitPaneModule } from 'ng2-split-pane/lib/ng2-split-pane';
+import { PreferenceModule } from './preference.module';
+import { TableModule } from 'primeng/table';
+import { ContextMenuModule } from 'primeng/contextmenu';
+import { DialogModule } from 'primeng/dialog';
+import { CatalogService } from './catalog.service';
+import { AutoCompleteModule } from 'primeng/autocomplete';
 
 @NgModule( {
     imports: [
         AdminRoutingModule,
         MDMCoreModule,
         FormsModule,
-        ReactiveFormsModule
+        ReactiveFormsModule,
+        SplitPaneModule,
+        PreferenceModule,
+        TableModule,
+        ContextMenuModule,
+        DialogModule,
+        AutoCompleteModule
     ],
     declarations: [
-        PreferenceComponent,
-        EditPreferenceComponent,
         AdminModulesComponent,
+        ExtSystemComponent,
+        ExtSystemViewerComponent,
+        ExtSystemEditorComponent
     ],
     exports: [
         AdminModulesComponent,
     ],
     providers: [
         ComponentLoaderFactory,
-        PreferenceService
+        ExtSystemService,
+        CatalogService
     ],
 })
 export class AdminModule { }
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/catalog.service.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/catalog.service.ts
new file mode 100644
index 0000000..6f3991e
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/catalog.service.ts
@@ -0,0 +1,78 @@
+/********************************************************************************
+ * 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 { Http, Response, Headers, RequestOptions } from '@angular/http';
+
+import { PropertyService } from '../core/property.service';
+
+import { plainToClass } from 'class-transformer';
+import { HttpErrorHandler } from '../core/http-error-handler';
+import { throwError as observableThrowError,  Observable } from 'rxjs';
+import { map, catchError } from 'rxjs/operators';
+import { Node } from '../navigator/node';
+
+@Injectable()
+export class CatalogService {
+
+  private prefEndpoint: string;
+
+  constructor(private http: Http,
+              private httpErrorHandler: HttpErrorHandler,
+              private _prop: PropertyService) {
+    this.prefEndpoint = _prop.getUrl('mdm/environments/');
+  }
+
+  getTplRootsForType(scope: string, type: string): Observable<Node[]> {
+    return this.http.get(this.prefEndpoint + scope + '/tplroots/' + type).pipe(
+      map(response => plainToClass(Node, response.json().data)),
+      catchError(this.handleError));
+  }
+
+  getTplCompForRoot(scope: string, type: string, rootId: string): Observable<Node[]> {
+    return this.http.get(this.prefEndpoint + scope + '/tplroots/' + type + '/' + rootId + '/tplcomps/rootList').pipe(
+      map(response => plainToClass(Node, response.json().data)),
+      catchError(this.handleError));
+  }
+
+  getTplAttrsForComp(scope: string, type: string, rootId: string, tplCompId: string): Observable<Node[]> {
+    return this.http.get(this.prefEndpoint + scope + '/tplroots/' + type + '/' + rootId + '/tplcomps/' + tplCompId + '/tplattrs').pipe(
+      map(response => plainToClass(Node, response.json().data)),
+      catchError(this.handleError));
+  }
+
+  getCatCompsForType(scope: string, type: string): Observable<Node[]> {
+    return this.http.get(this.prefEndpoint + scope + '/catcomps/' + type).pipe(
+      map(response => plainToClass(Node, response.json().data)),
+      catchError(this.handleError));
+  }
+
+  getCatAttrsForComp(scope: string, type: string, catCompId: string): Observable<Node[]> {
+    return this.http.get(this.prefEndpoint + scope + '/catcomps/' + type + '/' + catCompId + '/catattrs').pipe(
+      map(response => plainToClass(Node, response.json().data)),
+      catchError(this.handleError));
+  }
+
+  private handleError(e: Error | any) {
+    if (e instanceof Response) {
+      let response = <Response> e;
+      if (response.status !== 200) {
+        return observableThrowError('Could not request catalog element! '
+          + 'Please check if application server is running and database is configured correctly.');
+      }
+    }
+    return this.httpErrorHandler.handleError(e);
+  }
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-editor.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-editor.component.html
new file mode 100644
index 0000000..a82d955
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-editor.component.html
@@ -0,0 +1,162 @@
+<!-- ********************************************************************************
+ * 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
+ *
+ ******************************************************************************** -->
+
+  <div class="mdm-extsystem-editor-container">
+
+    <p-table class="ext-system-table" [value]="tableExtSystems" *ngIf="selectedEnvironment && extSystems" styleClass="table-hover">
+      <ng-template pTemplate="header">
+        <tr>
+          <th>{{ 'administration.extsystem.name' | translate }}</th>
+          <th>{{ 'administration.extsystem.description' | translate }}</th>
+        </tr>
+      </ng-template>
+      <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
+        <tr>
+          <td>
+            <input required type="text" [(ngModel)]="rowData.name" />
+          </td>
+          <td>
+            <input type="text" *ngIf="getAttributeFromNode(rowData,'Description')" [(ngModel)]="getAttributeFromNode(rowData,'Description').value" />
+          </td>
+        </tr>
+      </ng-template>
+    </p-table>
+    <div class="commands">
+      <button type="button" class="btn btn-default pull-right" (click)="saveExtSystem()">
+        <span class="fa fa-plus"></span> {{'administration.extsystem.btn-save' | translate }}
+      </button>
+    </div>
+
+    <div *ngIf="selectedExtSystem">
+      <div class="custom-split-pane">
+        <div class="custom-split-pane-content custom-split-pane-left">
+          <div>{{ 'administration.extsystem.ext-system-attributes' | translate }}</div>
+          <p-table [value]="getExternalSystemAttributes()" selectionMode="single" [(selection)]="selectedExtSystemAttr"
+                   [(contextMenuSelection)]="selectedExtSystemAttr" [contextMenu]="cmExtSystemAttr">
+            <ng-template pTemplate="header">
+              <tr>
+                <th>{{ 'administration.extsystem.name' | translate }}</th>
+                <th>{{ 'administration.extsystem.description' | translate }}</th>
+              </tr>
+            </ng-template>
+            <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
+              <tr [pSelectableRow]="rowData" [pSelectableRowIndex]="rowIndex" [pContextMenuRow]="rowData">
+                <td>{{rowData.name}}</td>
+                <td>{{getAttributeValueFromNode(rowData,'Description')}}</td>
+              </tr>
+            </ng-template>
+          </p-table>
+          <div *ngIf="loadingExtSystemAttr">{{ 'administration.extsystem.loading-attributes' | translate }}</div>
+
+          <div class="commands">
+            <button type="button" class="btn btn-default pull-right" (click)="addExtSystemAttr()">
+              <span class="fa fa-plus"></span> {{'administration.extsystem.btn-add' | translate }}
+            </button>
+          </div>
+
+        </div>
+        <div class="custom-split-pane-content custom-split-pane-right">
+          <div>{{ 'administration.extsystem.mdm-attributes' | translate }}</div>
+          <p-table *ngIf="selectedExtSystemAttr" [value]="getMDMAttributes()" selectionMode="single" [(selection)]="selectedExtSystemMDMAttr"
+                   [(contextMenuSelection)]="selectedExtSystemMDMAttr" [contextMenu]="cmExtSystemMDMAttr">
+            <ng-template pTemplate="header">
+              <tr>
+                <th>{{ 'administration.extsystem.component-type' | translate }}</th>
+                <th>{{ 'administration.extsystem.component-name' | translate }}</th>
+                <th>{{ 'administration.extsystem.attribute-name' | translate }}</th>
+              </tr>
+            </ng-template>
+            <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
+              <tr [pSelectableRow]="rowData" [pSelectableRowIndex]="rowIndex" [pContextMenuRow]="rowData">
+                <td>{{getAttributeValueFromNode(rowData,'CompType')}}</td>
+                <td>{{getAttributeValueFromNode(rowData,'CompName')}}</td>
+                <td>{{getAttributeValueFromNode(rowData,'AttrName')}}</td>
+              </tr>
+            </ng-template>
+          </p-table>
+
+          <div class="commands">
+            <button *ngIf="selectedExtSystemAttr" type="button" class="btn btn-default pull-right" (click)="addExtSystemMDMAttr()">
+              <span class="fa fa-plus"></span> {{'administration.extsystem.btn-add' | translate }}
+            </button>
+          </div>
+
+        </div>
+      </div>
+
+      <div class="float-elements">
+        <p-contextMenu #cmExtSystemAttr [model]="menuItemsExtSystemAttr"></p-contextMenu>
+
+        <p-dialog header="{{ 'administration.extsystem.dialog-ext-system-attr-title' | translate}}" [(visible)]="dialogExtSystemAttr">
+          <table *ngIf="tmpExtSystemAttr">
+            <tr>
+              <td>{{ 'administration.extsystem.name' | translate}}</td>
+              <td><input required type="text" [(ngModel)]="tmpExtSystemAttr.name" /></td>
+            </tr>
+            <tr *ngIf="tmpExtSystemAttr.id">
+              <td>{{ 'administration.extsystem.description' | translate}}</td>
+              <td><input type="text" *ngIf="getAttributeFromNode(tmpExtSystemAttr,'Description')" [(ngModel)]="getAttributeFromNode(tmpExtSystemAttr,'Description').value" /></td>
+            </tr>
+          </table>
+          <div class="dialogcommands">
+            <button type="button" class="btn btn-default pull-right" (click)="saveDialogExtSystemAttr()">
+              <span class="fa fa-plus"></span> {{'administration.extsystem.btn-save' | translate }}
+            </button>
+            <button type="button" class="btn btn-default pull-right" (click)="cancelDialogExtSystemAttr()">
+              <span class="fa fa-times"></span> {{'administration.extsystem.btn-cancel' | translate }}
+            </button>
+          </div>
+        </p-dialog>
+
+
+        <p-contextMenu #cmExtSystemMDMAttr [model]="menuItemsExtSystemMDMAttr"></p-contextMenu>
+
+        <p-dialog header="{{ 'administration.extsystem.dialog-ext-system-mdm-attr-title' | translate}}" [(visible)]="dialogExtSystemMDMAttr"
+                  [contentStyle]="{'overflow':'visible'}" class="dialog-extsystem-mdmattr">
+          <table *ngIf="tmpExtSystemMDMAttr">
+            <tr>
+              <td>{{ 'administration.extsystem.component-type' | translate}}</td>
+              <td>
+                <p-dropdown *ngIf="getAttributeFromNode(tmpExtSystemMDMAttr,'CompType')" [options]="mdmCompTypes" [(ngModel)]="getAttributeFromNode(tmpExtSystemMDMAttr,'CompType').value"  (onChange)="handleCompTypeSelect($event)" required="true"></p-dropdown>
+              </td>
+            </tr>
+            <tr>
+              <td>{{ 'administration.extsystem.component-name' | translate}}</td>
+              <td>
+                <div *ngIf="dialogLoadingExtSystemComps" class="floattext"><div class="text">{{ 'administration.extsystem.loading-short' | translate }}</div></div>
+                <p-autoComplete *ngIf="getAttributeFromNode(tmpExtSystemMDMAttr,'CompName')" [(ngModel)]="getAttributeFromNode(tmpExtSystemMDMAttr,'CompName').value" [suggestions]="tmpCatalogComps" (completeMethod)="searchCatalogComps($event)" (onSelect)="handleCompSelect($event)" [dropdown]="true"></p-autoComplete>
+              </td>
+            </tr>
+            <tr>
+              <td>{{ 'administration.extsystem.attribute-name' | translate}}</td>
+              <td>
+                <div *ngIf="dialogLoadingExtSystemAttrs" class="floattext"><div class="text">{{ 'administration.extsystem.loading-short' | translate }}</div></div>
+                <p-autoComplete *ngIf="getAttributeFromNode(tmpExtSystemMDMAttr,'AttrName')" [(ngModel)]="getAttributeFromNode(tmpExtSystemMDMAttr,'AttrName').value" [suggestions]="tmpAttributeComps" (completeMethod)="searchAttributeComps($event)" [dropdown]="true"></p-autoComplete>
+              </td>
+            </tr>
+          </table>
+          <div class="dialogcommands">
+            <button type="button" class="btn btn-default pull-right" (click)="saveDialogExtSystemMDMAttr()">
+              <span class="fa fa-plus"></span> {{'administration.extsystem.btn-save' | translate }}
+            </button>
+            <button type="button" class="btn btn-default pull-right" (click)="cancelDialogExtSystemMDMAttr()">
+              <span class="fa fa-times"></span> {{'administration.extsystem.btn-cancel' | translate }}
+            </button>
+          </div>
+        </p-dialog>
+      </div>
+
+    </div>
+
+  </div>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-editor.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-editor.component.ts
new file mode 100644
index 0000000..02e0ab8
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-editor.component.ts
@@ -0,0 +1,612 @@
+/********************************************************************************
+ * 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, OnInit, ViewChild, Input, OnDestroy, Output, EventEmitter } from '@angular/core';
+import { FormGroup, FormControl, FormBuilder, FormArray, Validators } from '@angular/forms';
+import { ActivatedRoute } from '@angular/router';
+import { TranslateService } from '@ngx-translate/core';
+import { DropdownModule } from 'primeng/dropdown';
+import { TreeTableModule } from 'primeng/treetable';
+import { TreeNode, MenuItem, SelectItem } from 'primeng/api';
+import { ContextMenuModule } from 'primeng/contextmenu';
+import { TreeTable, TreeTableToggler, DataTable } from 'primeng/primeng';
+import { DialogModule } from 'primeng/dialog';
+import { Observable, BehaviorSubject } from 'rxjs';
+import 'rxjs/add/operator/toPromise';
+
+import { MDMNotificationService } from '../core/mdm-notification.service';
+import { ExtSystemService } from './extsystem.service';
+import { Node, Attribute, Relation } from '../navigator/node';
+import { plainToClass } from 'class-transformer';
+import { CatalogService } from './catalog.service';
+
+@Component( {
+    selector: 'mdm-extsystem-editor',
+  templateUrl: './extsystem-editor.component.html',
+  styleUrls: ['./extsystem.component.css']
+})
+export class ExtSystemEditorComponent implements OnInit, OnDestroy {
+
+  // passed down from parent
+  @Input() extSystems: Node[];
+  @Input() selectedEnvironment: Node;
+  @Input() selectedES: string;
+
+  @Output() editMode = new EventEmitter<boolean>();
+
+  // table selection
+  selectedExtSystem: Node;
+  selectedExtSystemAttr: Node;
+  selectedExtSystemMDMAttr: Node;
+  tableExtSystems: Node[] = new Array();
+
+  // dropdown for edit dialog
+  mdmCompTypes: SelectItem[];
+  mdmCompNames: Node[];
+  mdmAttrNames: Node[];
+
+  // external system attributes
+  extSystemAttrs: Node[];
+  bsExtSystemAttrs: BehaviorSubject<Node[]> = new BehaviorSubject<Node[]>(undefined);
+
+  // contextmenu mappings
+  menuItemsExtSystemAttr: MenuItem[];
+  menuItemsExtSystemMDMAttr: MenuItem[];
+
+  // dialog and loading states
+  dialogExtSystemAttr: boolean = false;
+  dialogExtSystemMDMAttr: boolean = false;
+  loadingExtSystemAttr: boolean = false;
+
+  dialogLoadingExtSystemComps: boolean = false;
+  dialogLoadingExtSystemAttrs: boolean = false;
+
+  // for edit dialogs
+  tmpExtSystemAttr: Node;
+  tmpExtSystemMDMAttr: Node;
+  tmpNode: Node;
+  tmpCatalogComps: string[];
+  tmpAttributeComps: string[];
+
+  loadedTemplateRoots: any[][] = [];
+  loadedCatalogComps: any[][] = [];
+  loadedAttributeComps: Node[] = new Array();
+
+  constructor(private extSystemService: ExtSystemService,
+              private notificationService: MDMNotificationService,
+              private translateService: TranslateService,
+              private catalogService: CatalogService) {
+
+    this.bsExtSystemAttrs.subscribe(value => {
+      this.extSystemAttrs = value;
+    });
+
+    this.mdmCompTypes = [
+      { label: this.translateService.instant('administration.extsystem.dropdown-please-select'), value: '' },
+      { label: this.translateService.instant('administration.extsystem.unit-under-test'), value: 'UnitUnderTest' },
+      { label: this.translateService.instant('administration.extsystem.test-equipment'), value: 'TestEquipment' },
+      { label: this.translateService.instant('administration.extsystem.test-sequence'), value: 'TestSequence' },
+      { label: this.translateService.instant('administration.extsystem.sensor'), value: 'Sensor' }
+    ];
+  }
+
+  ngOnInit() {
+    for (let i in this.extSystems) {
+      if (this.extSystems[i].type === 'ExtSystem' && this.extSystems[i].id === this.selectedES) {
+        this.selectedExtSystem = this.extSystems[i];
+        this.tableExtSystems.push(this.selectedExtSystem);
+        break;
+      }
+    }
+
+    this.loadingExtSystemAttr = true;
+    this.extSystemService.getExtSystemAttributesForScope(this.selectedEnvironment.sourceName, this.selectedExtSystem.id).subscribe(attrs => {
+      this.bsExtSystemAttrs.next(attrs);
+      this.loadingExtSystemAttr = false;
+    });
+
+    this.menuItemsExtSystemAttr = [
+      {
+        label: this.translateService.instant('administration.extsystem.btn-edit'), icon: 'fa fa-pencil-square-o', command: (event) => this.editExtSystemAttr(this.selectedExtSystemAttr)
+      },
+      { label: this.translateService.instant('administration.extsystem.btn-del'), icon: 'fa fa-times', command: (event) => this.removeExtSystemAttr(this.selectedExtSystemAttr) }
+    ];
+
+    this.menuItemsExtSystemMDMAttr = [
+      {
+        label: this.translateService.instant('administration.extsystem.btn-edit'), icon: 'fa fa-pencil-square-o', command: (event) => this.editExtSystemMDMAttr(this.selectedExtSystemMDMAttr)
+      },
+      { label: this.translateService.instant('administration.extsystem.btn-del'), icon: 'fa fa-times', command: (event) => this.removeExtSystemMDMAttr(this.selectedExtSystemMDMAttr) }
+    ];
+  }
+
+  ngOnDestroy() {
+  }
+
+  getExternalSystemAttributes() {
+    let data = new Array();
+    for (let i in this.extSystemAttrs) {
+      if (this.extSystemAttrs[i].type === 'ExtSystemAttribute') {
+        data.push(this.extSystemAttrs[i]);
+      }
+    }
+    return data;
+  }
+  getMDMAttributes() {
+    let ids = new Array();
+    for (let i in this.selectedExtSystemAttr.relations) {
+      if (this.selectedExtSystemAttr.relations[i].entityType === 'MDMAttribute') {
+        for (let j in this.selectedExtSystemAttr.relations[i].ids) {
+          ids.push(this.selectedExtSystemAttr.relations[i].ids[j]);
+        }
+      }
+    }
+    let data = new Array();
+    for (let i in this.extSystemAttrs) {
+      if (this.extSystemAttrs[i].type === 'MDMAttribute' && ids.find(el => el === this.extSystemAttrs[i].id)) {
+        data.push(this.extSystemAttrs[i]);
+      }
+    }
+    return data;
+  }
+
+  getAttributeValueFromNode(node: Node, attribute: string) {
+    if (node.attributes !== undefined) {
+      for (let i in node.attributes) {
+        if (node.attributes[i].name === attribute) {
+          return node.attributes[i].value;
+        }
+      }
+    }
+    return '';
+  }
+
+  getAttributeFromNode(node: Node, attribute: string) {
+    if (node.attributes !== undefined) {
+      for (let i in node.attributes) {
+        if (node.attributes[i].name === attribute) {
+          return node.attributes[i];
+        }
+      }
+    }
+    return undefined;
+  }
+
+  getNextTemporaryId() {
+    let id: number = -1;
+    for (let i in this.extSystems) {
+      if (parseInt(this.extSystems[i].id) < id) {
+        id = parseInt(this.extSystems[i].id);
+      }
+    }
+    return --id;
+  }
+
+  createAttribute(name: string, value?: string) {
+    let attr = new Attribute();
+    attr.dataType = 'STRING';
+    attr.unit = '';
+    attr.value = value !== undefined ? value : '';
+    attr.name = name;
+    return attr;
+  }
+
+  getIndicesForIds(ids: string[]) {
+    let indices = new Array();
+    for (let id in ids) {
+      for (let i in this.extSystems) {
+        if (this.extSystems[i].id === ids[id]) {
+          indices.push(this.extSystems.indexOf(this.extSystems[i]));
+        }
+      }
+    }
+    return indices;
+  }
+  
+  addExtSystemAttr() {
+    this.tmpExtSystemAttr = new Node();
+    this.tmpExtSystemAttr.type = 'ExtSystemAttribute';
+    this.tmpExtSystemAttr.sourceType = 'ExtSystemAttr';
+    this.tmpExtSystemAttr.sourceName = this.selectedEnvironment.sourceName;
+    this.tmpExtSystemAttr.attributes = new Array();
+    this.tmpExtSystemAttr.attributes.push(this.createAttribute('Description'));
+    this.tmpExtSystemAttr.attributes.push(this.createAttribute('Name'));
+    this.tmpExtSystemAttr.attributes.push(this.createAttribute('ConverterClassname'));
+    this.tmpExtSystemAttr.attributes.push(this.createAttribute('ConverterParameter'));
+    this.tmpExtSystemAttr.attributes.push(this.createAttribute('MimeType', 'application/x-asam.aoany.extsystemattr'));
+    this.dialogExtSystemAttr = true;
+  }
+
+  editExtSystemAttr(extSystemAttr?: Node) {
+    if (extSystemAttr != undefined) {
+      this.tmpNode = JSON.parse(JSON.stringify(extSystemAttr));
+      this.tmpExtSystemAttr = extSystemAttr;
+      this.dialogExtSystemAttr = true;
+    }
+  }
+
+  removeExtSystemAttr(extSystemAttr?: Node) {
+    if (extSystemAttr != undefined) {
+
+      if (extSystemAttr.id !== undefined && parseInt(extSystemAttr.id) > 0 && this.extSystemAttrs.indexOf(extSystemAttr) !== -1) {
+        this.extSystemService.deleteExtSystemAttr(this.selectedEnvironment.sourceName, extSystemAttr.id).subscribe();
+        let idxES: number = this.extSystemAttrs.indexOf(extSystemAttr);
+        if (idxES !== -1) {
+          this.extSystemAttrs.splice(idxES, 1);
+          if (extSystemAttr.relations !== undefined && extSystemAttr.relations.length > 0) {
+            // remove all children
+            let indices = new Array<number>();
+            for (let h in extSystemAttr.relations) {
+              // the mdm attributes
+              indices = indices.concat(this.getIndicesForIds(extSystemAttr.relations[h].ids));
+            }
+            indices.sort((a, b) => b - a);
+            for (let i in indices) {
+              this.extSystemAttrs.splice(indices[i], 1);
+            }
+          }
+        }   
+      }
+    }
+    this.selectedExtSystemAttr = undefined;
+  }
+
+  saveExtSystem() {
+    this.getAttributeFromNode(this.selectedExtSystem, 'Name').value = this.selectedExtSystem.name;
+    this.extSystemService.saveExtSystem(this.selectedEnvironment.sourceName, this.selectedExtSystem)
+      .subscribe(
+        response => { /* discard */ },
+        error => this.notificationService.notifyError(
+          this.translateService.instant('administration.extsystem.err-cannot-update-ext-system'), error)
+      );
+  }
+
+  patchResponseAttr(nodes: Node[]) {
+    for (let i in nodes) {
+      if (nodes[i].name === this.tmpExtSystemAttr.name) {
+        if (this.tmpExtSystemAttr.id === undefined) {
+          this.extSystemAttrs.push(this.tmpExtSystemAttr);
+        }
+        this.tmpExtSystemAttr.id = nodes[i].id;
+      }
+    }
+    this.tmpExtSystemAttr = undefined;
+    this.tmpNode = undefined;
+  }
+
+  saveDialogExtSystemAttr() {
+    if (this.tmpExtSystemAttr.id === undefined) {
+      // add relation
+      if (this.selectedExtSystem.relations === undefined || this.selectedExtSystem.relations.length == 0) {
+        this.selectedExtSystem.relations = new Array();
+        let relation = new Relation();
+        relation.entityType = 'ExtSystemAttribute';
+        relation.type = 'CHILDREN';
+        relation.ids = new Array();
+        this.selectedExtSystem.relations.push(relation);
+      }
+      if (this.selectedExtSystem.relations[0].ids === undefined) {
+        this.selectedExtSystem.relations[0].ids = new Array();
+      }
+      this.tmpExtSystemAttr.id = undefined;
+    }
+    // update the name attribute
+    this.getAttributeFromNode(this.tmpExtSystemAttr, 'Name').value = this.tmpExtSystemAttr.name;
+    this.extSystemService.saveExtSystemAttr(this.selectedEnvironment.sourceName, this.tmpExtSystemAttr, this.selectedExtSystem)
+      .subscribe(
+        response => this.patchResponseAttr(plainToClass(Node, response.json().data)),
+        error => {
+          this.notificationService.notifyError(
+            this.translateService.instant('administration.extsystem.err-cannot-save-ext-system-attr'), error);
+          // restore values
+          this.getAttributeFromNode(this.tmpExtSystemAttr, 'Name').value = this.getAttributeValueFromNode(this.tmpNode, 'Name');
+          this.getAttributeFromNode(this.tmpExtSystemAttr, 'Description').value = this.getAttributeValueFromNode(this.tmpNode, 'Description');
+          this.tmpExtSystemAttr.name = this.getAttributeValueFromNode(this.tmpExtSystemAttr, 'Name');
+          this.tmpNode = undefined;
+       }
+      );
+
+    this.dialogExtSystemAttr = false;
+  }
+
+  cancelDialogExtSystemAttr() {
+    this.dialogExtSystemAttr = false;
+    this.getAttributeFromNode(this.tmpExtSystemAttr, 'Name').value = this.getAttributeValueFromNode(this.tmpNode, 'Name');
+    this.getAttributeFromNode(this.tmpExtSystemAttr, 'Description').value = this.getAttributeValueFromNode(this.tmpNode, 'Description');
+    this.tmpExtSystemAttr.name = this.getAttributeValueFromNode(this.tmpExtSystemAttr, 'Name');
+    this.tmpExtSystemAttr = undefined;
+  }
+
+  addExtSystemMDMAttr() {
+    this.tmpExtSystemMDMAttr = new Node();
+    this.tmpExtSystemMDMAttr.type = 'MDMAttribute';
+    this.tmpExtSystemMDMAttr.sourceType = 'MDMAttr';
+    this.tmpExtSystemMDMAttr.sourceName = this.selectedEnvironment.sourceName;
+    this.tmpExtSystemMDMAttr.attributes = new Array();
+    this.tmpExtSystemMDMAttr.attributes.push(this.createAttribute('AttrName'));
+    this.tmpExtSystemMDMAttr.attributes.push(this.createAttribute('CompName'));
+    this.tmpExtSystemMDMAttr.attributes.push(this.createAttribute('CompType'));
+    this.tmpExtSystemMDMAttr.attributes.push(this.createAttribute('MimeType', 'application/x-asam.aoany.mdmattr'));
+    // the name is the hierarchy from the parent elements appended with the name, set in save method
+    this.tmpExtSystemMDMAttr.attributes.push(this.createAttribute('Name'));
+    this.dialogExtSystemMDMAttr = true;
+  }
+
+  editExtSystemMDMAttr(extSystemMDMAttr?: Node) {
+    if (extSystemMDMAttr != undefined) {
+      this.tmpNode = JSON.parse(JSON.stringify(extSystemMDMAttr));
+      this.tmpExtSystemMDMAttr = extSystemMDMAttr;
+      // trigger data reload
+      this.tmpAttributeComps = [];
+      this.loadedAttributeComps = new Array();
+      this.dialogExtSystemMDMAttr = true;
+      this.getAttributeComponentStr();
+    }
+  }
+
+  removeExtSystemMDMAttr(extSystemMDMAttr?: Node) {
+    if (extSystemMDMAttr != undefined) {
+      if (this.extSystemAttrs.indexOf(extSystemMDMAttr) !== -1) {
+        if (extSystemMDMAttr.id !== undefined && parseInt(extSystemMDMAttr.id) > 0) {
+          this.extSystemService.deleteExtSystemMDMAttr(this.selectedEnvironment.sourceName, extSystemMDMAttr.id).subscribe();
+          this.extSystemAttrs.splice(this.extSystemAttrs.indexOf(extSystemMDMAttr), 1);
+        }
+      }   
+    }
+    this.selectedExtSystemMDMAttr = undefined;
+  }
+
+  patchResponseMDMAttr(nodes: Node[]) {
+    for (let i in nodes) {
+      if (nodes[i].name === this.tmpExtSystemMDMAttr.name) {
+        if (this.tmpExtSystemMDMAttr.id === undefined) {
+          this.extSystemAttrs.push(this.tmpExtSystemMDMAttr);
+        }
+        this.tmpExtSystemMDMAttr.id = nodes[i].id;
+        for (let j in this.selectedExtSystemAttr.relations) {
+          if (this.selectedExtSystemAttr.relations[j].entityType === 'MDMAttribute') {
+            if (this.selectedExtSystemAttr.relations[j].ids === undefined) {
+              this.selectedExtSystemAttr.relations[j].ids = new Array();
+            };
+            this.selectedExtSystemAttr.relations[j].ids.push(nodes[i].id);
+          }
+        }
+      }
+    }
+    this.tmpExtSystemMDMAttr = undefined;
+    this.tmpNode = undefined;
+ }
+
+  saveDialogExtSystemMDMAttr() {
+    if (this.tmpExtSystemMDMAttr.id === undefined) {
+      // add relation
+      if (this.selectedExtSystemAttr.relations === undefined || this.selectedExtSystemAttr.relations.length == 0) {
+        this.selectedExtSystemAttr.relations = new Array();
+        let relation = new Relation();
+        relation.entityType = 'MDMAttribute';
+        relation.type = 'CHILDREN';
+        relation.ids = new Array();
+        this.selectedExtSystemAttr.relations.push(relation);
+      }
+      if (this.selectedExtSystemAttr.relations[0].ids === undefined) {
+        this.selectedExtSystemAttr.relations[0].ids = new Array();
+      }
+      this.tmpExtSystemMDMAttr.id = undefined;
+    }
+    // update the name attribute with the hierarchy
+    this.tmpExtSystemMDMAttr.name = this.getAttributeValueFromNode(this.tmpExtSystemMDMAttr, 'CompType') + '.' + this.getAttributeValueFromNode(this.tmpExtSystemMDMAttr, 'CompName') + '.' + this.getAttributeValueFromNode(this.tmpExtSystemMDMAttr, 'AttrName');
+    this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'Name').value = this.tmpExtSystemMDMAttr.name;
+    this.extSystemService.saveExtSystemMDMAttr(this.selectedEnvironment.sourceName, this.tmpExtSystemMDMAttr, this.selectedExtSystemAttr)
+      .subscribe(
+        response => this.patchResponseMDMAttr(plainToClass(Node, response.json().data)),
+        error => {
+          this.notificationService.notifyError(
+            this.translateService.instant('administration.extsystem.err-cannot-save-ext-mdm-attr'), error);
+          // restore values
+          this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'Name').value = this.getAttributeValueFromNode(this.tmpNode, 'Name');
+          this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'CompType').value = this.getAttributeValueFromNode(this.tmpNode, 'CompType');
+          this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'CompName').value = this.getAttributeValueFromNode(this.tmpNode, 'CompName');
+          this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'AttrName').value = this.getAttributeValueFromNode(this.tmpNode, 'AttrName');
+          this.tmpExtSystemMDMAttr.name = this.getAttributeValueFromNode(this.tmpExtSystemMDMAttr, 'Name');
+          this.tmpNode = undefined;
+        }
+      );
+
+    this.dialogExtSystemMDMAttr = false;
+  }
+
+  cancelDialogExtSystemMDMAttr() {
+    this.dialogExtSystemMDMAttr = false;
+    this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'Name').value = this.getAttributeValueFromNode(this.tmpNode, 'Name');
+    this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'CompType').value = this.getAttributeValueFromNode(this.tmpNode, 'CompType');
+    this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'CompName').value = this.getAttributeValueFromNode(this.tmpNode, 'CompName');
+    this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'AttrName').value = this.getAttributeValueFromNode(this.tmpNode, 'AttrName');
+    this.tmpExtSystemMDMAttr = undefined;
+  }
+
+  async loadCatalogComps(type: string) {
+    if (type !== undefined && type != null && type.length > 0) {
+      this.tmpCatalogComps = [];
+      this.tmpAttributeComps = [];
+      this.loadedCatalogComps[type] = [];
+      this.loadedAttributeComps = new Array();
+      
+      if (this.loadedTemplateRoots[type] === undefined) {
+        await this.catalogService.getTplRootsForType(this.selectedEnvironment.sourceName, type)
+          .subscribe(
+            response => this.loadedTemplateRoots[type] = response,
+            error => this.notificationService.notifyError(
+              this.translateService.instant('administration.extsystem.err-cannot-load-comp-types'), error)
+          );
+      }
+      
+      if (this.loadedTemplateRoots[type] !== undefined) {
+        let tmpIds = '';
+        for (let i = 0; i < this.loadedTemplateRoots[type].length; i++) {
+          tmpIds = tmpIds + this.loadedTemplateRoots[type][i].id;
+          if (i + 1 < this.loadedTemplateRoots[type].length) tmpIds = tmpIds + ',';
+        }
+        this.dialogLoadingExtSystemComps = true;
+        await this.catalogService.getTplCompForRoot(this.selectedEnvironment.sourceName, type, tmpIds)
+          .subscribe(
+            response => {
+              for(let t in response) this.loadedCatalogComps[type].push(response[t])
+              this.dialogLoadingExtSystemComps = false;
+            },
+            error => {
+              this.notificationService.notifyError(
+                this.translateService.instant('administration.extsystem.err-cannot-load-comp-types'), error);
+              this.dialogLoadingExtSystemComps = false;
+            }
+          );
+        return true;
+      }
+    }
+    return false;
+  }
+
+  async loadAttributeComps(type: string, comp: string) {
+    if (type !== undefined && type != null && type.length > 0 && comp !== undefined && comp != null && comp.length > 0) {
+      this.loadedAttributeComps = new Array();
+      this.tmpAttributeComps = [];
+      let compId: string = '';
+      let rootId: string = '0';
+      for (let i in this.loadedCatalogComps[type]) {
+        if (this.loadedCatalogComps[type][i].name === comp) {
+          compId = this.loadedCatalogComps[type][i].id;
+          break;
+        }
+      }
+      for (let i in this.loadedTemplateRoots[type]) {
+        if (this.loadedTemplateRoots[type][i].relations !== undefined) {
+          for (let j in this.loadedTemplateRoots[type][i].relations) {
+            if (this.loadedTemplateRoots[type][i].relations[j].entityType === 'TemplateComponent' && this.loadedTemplateRoots[type][i].relations[j].ids !== undefined) {
+              for (let k in this.loadedTemplateRoots[type][i].relations[j].ids) {
+                if (this.loadedTemplateRoots[type][i].relations[j].ids[k] === compId) {
+                  rootId = this.loadedTemplateRoots[type][i].id;
+                  break;
+                }
+              }
+              if (rootId !== '0') break;
+            }
+          }
+          if (rootId !== '0') break;
+        }
+      }
+      this.dialogLoadingExtSystemAttrs = true;
+      if (rootId !== '0' && compId !== '') {
+        await this.catalogService.getTplAttrsForComp(this.selectedEnvironment.sourceName, type, rootId, compId)
+          .subscribe(
+            response => {
+              this.loadedAttributeComps = response;
+              this.dialogLoadingExtSystemAttrs = false;
+            },
+            error => {
+              this.notificationService.notifyError(
+                this.translateService.instant('administration.extsystem.err-cannot-load-comp-types'), error);
+              this.dialogLoadingExtSystemAttrs = false;
+            }
+          );
+        return true;
+      }
+      this.dialogLoadingExtSystemAttrs = false;
+    } else {
+      return false;
+    }
+  }
+
+  getCatalogComponentStr() {
+    let type: string = this.getAttributeValueFromNode(this.tmpExtSystemMDMAttr, 'CompType');
+    let data: string[] = new Array();
+    // sensor does not have catalog elements
+    if (type !== undefined && type !== 'Sensor') {
+      let tmp = this.getCatalogComponents(type);
+      for (let i in tmp) {
+        data.push(tmp[i].name);
+      }
+    }
+    return data;
+  }
+
+  getAttributeComponentStr() {
+    let type: string = this.getAttributeValueFromNode(this.tmpExtSystemMDMAttr, 'CompType');
+    let comp: string = this.getAttributeValueFromNode(this.tmpExtSystemMDMAttr, 'CompName');
+    let data: string[] = new Array();
+    if (type !== undefined && comp !== undefined) {
+      let tmp = this.getAttributeComponents(type, comp);
+      for (let i in tmp) {
+        data.push(tmp[i].name);
+      }
+    }
+    return data;
+  }
+
+  getCatalogComponents(type: string) {
+    let data: Node[] = this.loadedCatalogComps[type];
+    if (data === undefined || data == null || data.length == 0) {
+      if (this.loadCatalogComps(type)) {
+        data = this.loadedCatalogComps[type];
+      }
+    }
+    return data;
+  }
+
+  getAttributeComponents(type: string, comp: string) {
+    let data: Node[] = this.loadedAttributeComps;
+    if (data === undefined || data == null || data.length == 0) {
+      if (this.loadAttributeComps(type, comp)) {
+        data = this.loadedAttributeComps;
+      }
+    }
+    return data;
+  }
+
+  searchCatalogComps(event) {
+    this.tmpCatalogComps = [];
+    for (let i = 0; i < this.getCatalogComponentStr().length; i++) {
+      let item = this.getCatalogComponentStr()[i];
+      if (item !== undefined && item.toLowerCase().indexOf(event.query.toLowerCase()) == 0) {
+        this.tmpCatalogComps.push(item);
+      }
+    }
+  }
+
+  searchAttributeComps(event) {
+    this.tmpAttributeComps = [];
+    for (let i = 0; i < this.getAttributeComponentStr().length; i++) {
+      let item = this.getAttributeComponentStr()[i];
+      if (item !== undefined && item.toLowerCase().indexOf(event.query.toLowerCase()) == 0) {
+        this.tmpAttributeComps.push(item);
+      }
+    }
+  }
+
+  handleCompTypeSelect(event) {
+    this.tmpAttributeComps = [];
+    this.tmpCatalogComps = [];
+    this.loadedAttributeComps = new Array();
+    this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'CompName').value = '';
+    this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'AttrName').value = '';
+    this.getCatalogComponentStr();
+  }
+
+  handleCompSelect(event) {
+    this.tmpAttributeComps = [];
+    this.loadedAttributeComps = new Array();
+    this.getAttributeFromNode(this.tmpExtSystemMDMAttr, 'AttrName').value = '';
+    this.getAttributeComponentStr();
+  }
+
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-viewer.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-viewer.component.html
new file mode 100644
index 0000000..543a87e
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-viewer.component.html
@@ -0,0 +1,127 @@
+<!-- ********************************************************************************
+ * 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
+ *
+ ******************************************************************************** -->
+
+  <div class="mdm-extsystem-viewer-container">
+
+    <p-table class="ext-system-table" [value]="getExternalSystems()" *ngIf="selectedEnvironment && extSystems" styleClass="table-hover"
+             (onRowSelect)="onExtSystemRowSelect($event)" (onRowUnselect)="onExtSystemRowUnselect($event)"
+             selectionMode="single" [(selection)]="selectedExtSystem">
+      <ng-template pTemplate="header">
+        <tr>
+          <th>{{ 'administration.extsystem.name' | translate }}</th>
+          <th>{{ 'administration.extsystem.description' | translate }}</th>
+          <th class="button-col"></th>
+          <th class="button-col"></th>
+        </tr>
+      </ng-template>
+      <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
+        <tr [pSelectableRow]="rowData" [pSelectableRowIndex]="rowIndex" [pContextMenuRow]="rowData">
+          <td>
+            <span>{{rowData.name}}</span>
+          </td>
+          <td>
+            <span>{{getAttributeValueFromNode(rowData,'Description')}}</span>
+          </td>
+          <td>
+            <button type="button" class="btn btn-default pull-right" (click)="editExtSystem(rowData)" title="{{'administration.extsystem.btn-edit' | translate }}">
+              <span class="fa fa-pencil-square-o"></span>
+            </button>
+          </td>
+          <td>
+            <button type="button" class="btn btn-default pull-right" (click)="removeExtSystem(rowData)" title="{{'administration.extsystem.btn-del' | translate }}">
+              <span class="fa fa-times"></span>
+            </button>
+          </td>
+        </tr>
+      </ng-template>
+    </p-table>
+
+    <p-dialog header="{{ 'administration.extsystem.dialog-ext-system-delete-title' | translate }}" [(visible)]="dialogExtSystemDelete">
+      <div class="text">
+        <span>{{ 'administration.extsystem.dialog-ext-system-delete-text' | translate }}</span>
+      </div>
+      <div class="dialogcommands">
+        <button type="button" class="btn btn-default pull-right" (click)="confirmRemoveExtSystem()">
+          <span class="fa fa-plus"></span> {{'administration.extsystem.btn-del' | translate }}
+        </button>
+        <button type="button" class="btn btn-default pull-right" (click)="cancelRemoveExtSystem()">
+          <span class="fa fa-times"></span> {{'administration.extsystem.btn-cancel' | translate }}
+        </button>
+      </div>
+    </p-dialog>
+
+  <p-dialog header="{{ 'administration.extsystem.dialog-ext-system-title' | translate}}" [(visible)]="dialogExtSystemCreate">
+    <table *ngIf="tmpExtSystemCreate">
+      <tr>
+        <td>{{ 'administration.extsystem.name' | translate}}</td>
+        <td><input required type="text" [(ngModel)]="tmpExtSystemCreate.name" /></td>
+      </tr>
+     </table>
+    <div class="dialogcommands">
+      <button type="button" class="btn btn-default pull-right" (click)="saveDialogExtSystem()">
+        <span class="fa fa-plus"></span> {{'administration.extsystem.btn-save' | translate }}
+      </button>
+      <button type="button" class="btn btn-default pull-right" (click)="cancelDialogExtSystem()">
+        <span class="fa fa-times"></span> {{'administration.extsystem.btn-cancel' | translate }}
+      </button>
+    </div>
+  </p-dialog>
+
+    <div *ngIf="selectedEnvironment" class="commands">
+      <button type="button" class="btn btn-default pull-right" (click)="addExtSystem()">
+        <span class="fa fa-plus"></span> {{'administration.extsystem.btn-add' | translate }}
+      </button>
+    </div>
+
+    <div class="custom-split-pane" *ngIf="selectedExtSystem">
+      <div class="custom-split-pane-content custom-split-pane-left">
+        <div>{{ 'administration.extsystem.ext-system-attributes' | translate }}</div>
+        <p-table [value]="getExternalSystemAttributes()" selectionMode="single" [(selection)]="selectedExtSystemAttr">
+          <ng-template pTemplate="header">
+            <tr>
+              <th>{{ 'administration.extsystem.name' | translate }}</th>
+              <th>{{ 'administration.extsystem.description' | translate }}</th>
+            </tr>
+          </ng-template>
+          <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
+            <tr [pSelectableRow]="rowData" [pSelectableRowIndex]="rowIndex" [pContextMenuRow]="rowData">
+              <td>{{rowData.name}}</td>
+              <td>{{getAttributeValueFromNode(rowData,'Description')}}</td>
+            </tr>
+          </ng-template>
+        </p-table>
+        <div *ngIf="loadingExtSystemAttr">{{ 'administration.extsystem.loading-attributes' | translate }}</div>
+      </div>
+      <div class="custom-split-pane-content custom-split-pane-right">
+        <div>{{ 'administration.extsystem.mdm-attributes' | translate }}</div>
+        <p-table *ngIf="selectedExtSystemAttr" [value]="getMDMAttributes()">
+          <ng-template pTemplate="header">
+            <tr>
+              <th>{{ 'administration.extsystem.component-type' | translate }}</th>
+              <th>{{ 'administration.extsystem.component-name' | translate }}</th>
+              <th>{{ 'administration.extsystem.attribute-name' | translate }}</th>
+            </tr>
+          </ng-template>
+          <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
+            <tr>
+              <td>{{getAttributeValueFromNode(rowData,'CompType')}}</td>
+              <td>{{getAttributeValueFromNode(rowData,'CompName')}}</td>
+              <td>{{getAttributeValueFromNode(rowData,'AttrName')}}</td>
+            </tr>
+          </ng-template>
+        </p-table>
+      </div>
+    </div>
+
+  </div>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-viewer.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-viewer.component.ts
new file mode 100644
index 0000000..a8daaf8
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem-viewer.component.ts
@@ -0,0 +1,270 @@
+/********************************************************************************
+ * 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, OnInit, ViewChild, Input, OnDestroy, Output, EventEmitter } from '@angular/core';
+import { FormGroup, FormControl, FormBuilder, FormArray, Validators } from '@angular/forms';
+import { ActivatedRoute } from '@angular/router';
+import { TranslateService } from '@ngx-translate/core';
+import { DropdownModule } from 'primeng/dropdown';
+import { TreeTableModule } from 'primeng/treetable';
+import { TreeNode, MenuItem } from 'primeng/api';
+import { ContextMenuModule } from 'primeng/contextmenu';
+import { TreeTable, TreeTableToggler, DataTable } from 'primeng/primeng';
+import { DialogModule } from 'primeng/dialog';
+import { Observable, BehaviorSubject } from 'rxjs';
+
+import { MDMNotificationService } from '../core/mdm-notification.service';
+import { ExtSystemService } from './extsystem.service';
+import { Node, Attribute } from '../navigator/node';
+import { plainToClass } from 'class-transformer';
+
+@Component( {
+    selector: 'mdm-extsystem-viewer',
+  templateUrl: './extsystem-viewer.component.html',
+  styleUrls: ['./extsystem.component.css']
+})
+export class ExtSystemViewerComponent implements OnInit, OnDestroy {
+
+  // passed down from parent
+  @Input() extSystems: Node[];
+  @Input() selectedEnvironment: Node;
+
+  @Output() editMode = new EventEmitter<boolean>();
+  @Output() selectedES = new EventEmitter<string>();
+
+  // dialog and loading states
+  dialogExtSystemCreate: boolean = false;
+  dialogExtSystemDelete: boolean = false;
+  loadingExtSystemAttr: boolean = false;
+
+  // temporary data for dialogs
+  tmpExtSystemCreate: Node;
+  tmpExtSystemDelete: Node;
+
+  // external system attributes
+  extSystemAttrs: Node[];
+  bsExtSystemAttrs: BehaviorSubject<Node[]> = new BehaviorSubject<Node[]>(undefined);
+
+  // table selection
+  selectedExtSystem: Node;
+  selectedExtSystemAttr: Node;
+
+  constructor(private extSystemService: ExtSystemService,
+              private notificationService: MDMNotificationService,
+              private translateService: TranslateService) {
+
+    this.bsExtSystemAttrs.subscribe(value => {
+      this.extSystemAttrs = value;
+    });
+  }
+
+  ngOnInit() {
+    this.bsExtSystemAttrs.next(undefined);
+  }
+
+
+  ngOnDestroy() {
+  }
+  
+  getExternalSystems() {
+    let data = new Array();
+    for (let i in this.extSystems) {
+      if (this.extSystems[i].type === 'ExtSystem') {
+        data.push(this.extSystems[i]);
+      }
+    }
+    return data;
+  }
+  getExternalSystemAttributes() {
+    let data = new Array();
+    for (let i in this.extSystemAttrs) {
+      if (this.extSystemAttrs[i].type === 'ExtSystemAttribute') {
+        data.push(this.extSystemAttrs[i]);
+      }
+    }
+    return data;
+  }
+  getMDMAttributes() {
+    let ids = new Array();
+    for (let i in this.selectedExtSystemAttr.relations) {
+      if (this.selectedExtSystemAttr.relations[i].entityType === 'MDMAttribute') {
+        for (let j in this.selectedExtSystemAttr.relations[i].ids) {
+          ids.push(this.selectedExtSystemAttr.relations[i].ids[j]);
+        }
+      }
+    }
+    let data = new Array();
+    for (let i in this.extSystemAttrs) {
+      if (this.extSystemAttrs[i].type === 'MDMAttribute' && ids.find(el => el === this.extSystemAttrs[i].id)) {
+        data.push(this.extSystemAttrs[i]);
+      }
+    }
+    return data;
+  }
+
+  getAttributeValueFromNode(node: Node, attribute: string) {
+    for (let i in node.attributes) {
+      if (node.attributes[i].name === attribute) {
+        return node.attributes[i].value;
+      }
+    }
+    return '';
+  }
+
+  getAttributeFromNode(node: Node, attribute: string) {
+    if (node.attributes !== undefined) {
+      for (let i in node.attributes) {
+        if (node.attributes[i].name === attribute) {
+          return node.attributes[i];
+        }
+      }
+    }
+    return undefined;
+  }
+
+  onExtSystemRowSelect(event: any) {
+    this.selectedExtSystemAttr = undefined;
+    this.bsExtSystemAttrs.next(undefined);
+    this.loadingExtSystemAttr = true;
+    this.selectedExtSystem = event.data;
+    this.extSystemService.getExtSystemAttributesForScope(this.selectedEnvironment.sourceName, this.selectedExtSystem.id).subscribe(attrs => {
+      this.bsExtSystemAttrs.next(attrs);
+      this.loadingExtSystemAttr = false;
+    });
+  }
+
+  onExtSystemRowUnselect(event: any) {
+    this.selectedExtSystem = undefined;
+    this.bsExtSystemAttrs.next(undefined);
+  }
+
+
+  createAttribute(name: string, value?: string) {
+    let attr = new Attribute();
+    attr.dataType = 'STRING';
+    attr.unit = '';
+    attr.value = value !== undefined ? value : '';
+    attr.name = name;
+    return attr;
+  }
+
+  getIndicesForIds(ids: string[]) {
+    let indices = new Array();
+    for (let id in ids) {
+      for (let i in this.extSystems) {
+        if (this.extSystems[i].id === ids[id]) {
+          indices.push(this.extSystems.indexOf(this.extSystems[i]));
+        }
+      }
+    }
+    return indices;
+  }
+
+  addExtSystem() {
+    this.tmpExtSystemCreate = new Node();
+    this.tmpExtSystemCreate.type = 'ExtSystem';
+    this.tmpExtSystemCreate.sourceType = 'ExtSystem';
+    this.tmpExtSystemCreate.sourceName = this.selectedEnvironment.sourceName;
+    this.tmpExtSystemCreate.attributes = new Array();
+    this.tmpExtSystemCreate.attributes.push(this.createAttribute('Description'));
+    this.tmpExtSystemCreate.attributes.push(this.createAttribute('Name'));
+    this.tmpExtSystemCreate.attributes.push(this.createAttribute('MimeType', 'application/x-asam.aoany.extsystem'));
+    this.dialogExtSystemCreate = true;
+  }
+
+  editExtSystem(extSystem?: Node) {
+    if (extSystem != undefined) {
+      this.tmpExtSystemCreate = extSystem;
+      //this.dialogExtSystem = true;
+      this.selectedES.next(extSystem.id);
+      this.editMode.next(true);
+    }
+  }
+
+  removeExtSystem(extSystem?: Node) {
+    this.tmpExtSystemDelete = extSystem;
+    this.dialogExtSystemDelete = true;
+  }
+
+  cancelRemoveExtSystem() {
+    this.tmpExtSystemDelete = undefined;
+    this.dialogExtSystemDelete = false;
+  }
+
+  confirmRemoveExtSystem() {
+    if (this.tmpExtSystemDelete != undefined) {
+      let idxES: number = this.extSystems.indexOf(this.tmpExtSystemDelete);
+      if (idxES !== -1) {
+        this.extSystems.splice(idxES, 1);
+        if (this.tmpExtSystemDelete.relations !== undefined && this.tmpExtSystemDelete.relations.length > 0) {
+          // remove all children
+          let indices = new Array<number>();
+          for (let h in this.tmpExtSystemDelete.relations) {
+            // the ext system attributes
+            let indicesESA = this.getIndicesForIds(this.tmpExtSystemDelete.relations[h].ids);
+            for (let i in indicesESA) {
+              for (let j in this.extSystems[indicesESA[i]].relations) {
+                // the mdm attributes
+                indices = indices.concat(this.getIndicesForIds(this.extSystems[indicesESA[i]].relations[j].ids));
+              }
+            }
+            indices = indices.concat(indicesESA);
+          }
+          indices.sort((a, b) => b - a);
+          for (let i in indices) {
+            this.extSystems.splice(indices[i], 1);
+          }
+        }
+      }
+      if (this.tmpExtSystemDelete.id !== undefined && parseInt(this.tmpExtSystemDelete.id) > 0) {
+        this.extSystemService.deleteExtSystem(this.selectedEnvironment.sourceName, this.tmpExtSystemDelete.id).subscribe();
+      }
+    }
+    this.selectedExtSystem = undefined;
+    this.tmpExtSystemDelete = undefined;
+    this.dialogExtSystemDelete = false;
+  }
+
+  // for new external systems only
+  saveDialogExtSystem() {
+    // update the name attribute
+    this.getAttributeFromNode(this.tmpExtSystemCreate, 'Name').value = this.tmpExtSystemCreate.name;
+    this.extSystemService.saveExtSystem(this.selectedEnvironment.sourceName, this.tmpExtSystemCreate)
+      .subscribe(
+        response => this.patchResponse(plainToClass(Node, response.json().data)),
+        error => this.notificationService.notifyError(
+          this.translateService.instant('administration.extsystem.err-cannot-save-ext-system'), error)
+      );
+    this.dialogExtSystemCreate = false;
+  }
+
+  cancelDialogExtSystem() {
+    this.dialogExtSystemCreate = false;
+    this.tmpExtSystemCreate = undefined;
+  }
+
+  patchResponse(nodes: Node[]) {
+    for (let i in nodes) {
+      if (nodes[i].name === this.tmpExtSystemCreate.name) {
+        if (this.tmpExtSystemCreate.id === undefined) {
+          this.extSystems.push(this.tmpExtSystemCreate);
+        }
+        this.tmpExtSystemCreate.id = nodes[i].id;
+      }
+    }
+    this.tmpExtSystemCreate = undefined;
+  }
+
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.css b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.css
new file mode 100644
index 0000000..7044280
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.css
@@ -0,0 +1,67 @@
+@charset "ISO-8859-1";
+
+/********************************************************************************
+ * 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
+ *
+ ********************************************************************************/
+
+.mdm-extsystem-viewer-container,
+.mdm-extsystem-editor-container{display:block;}
+
+  .mdm-extsystem-editor-container .commands,
+  .mdm-extsystem-viewer-container .commands,
+  .extsystem-comp .commands,
+  .extsystem-comp .commands {
+    min-height: 30px;
+    display: block;
+    margin-bottom: 10px;
+    margin-top:6px;
+    width: 100%;
+  }
+    .mdm-extsystem-editor-container .commands button,
+    .mdm-extsystem-viewer-container .commands button,
+    .extsystem-comp .commands button,
+    .extsystem-comp .commands button {
+      margin-left: 10px;
+    }
+
+.ext-system-table {
+    margin-top: 10px;
+}
+  .ext-system-table .button-col{
+    width:60px;
+  }
+  .ext-system-table td input{width:100%;}
+
+  .custom-split-pane {
+    display: block;
+    margin-top: 20px;
+  }
+.custom-split-pane-content{display: inline-block;}
+.custom-split-pane-left{width:48%;vertical-align:top;}
+.custom-split-pane-right {
+  margin-left: 1%;
+  width: 48%;
+  vertical-align: top;
+}
+
+.dialogcommands {padding-top:10px;min-height:45px;}
+.dialogcommands button {margin-left:10px;}
+
+.dialog-extsystem-mdmattr{height:200px;}
+  .dialog-extsystem-mdmattr .floattext {
+    position: absolute;
+    right: 70px;
+    margin-top:7px;
+    z-index:1001;
+  }
+  .dialog-extsystem-mdmattr .floattext .text{font-size:9pt;}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.html
new file mode 100644
index 0000000..a03a9ba
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.html
@@ -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
+ *
+ ******************************************************************************** -->
+
+<div class="extsystem-comp">
+  <div *ngIf="!environments">{{ 'administration.extsystem.loading' | translate }}</div>
+
+  <div class="system-selector">
+    <div *ngIf="!selectedEnvironment" style="display:inline;">{{ 'administration.extsystem.select-system' | translate }}:</div>
+    <div *ngIf="selectedEnvironment" style="display:inline;">{{ 'administration.extsystem.selected-system' | translate }}:</div>
+    <p-dropdown [options]="environments" [(ngModel)]="selectedEnvironment" optionLabel="sourceName" (onChange)="onChangeExtSystem($event)" placeholder="{{ 'administration.extsystem.dropdown-please-select' | translate }}"></p-dropdown>
+  </div>
+
+  <div *ngIf="selectedEnvironment && extSystems">
+
+    <div class="commands">
+      <button *ngIf="editorMode" type="button" class="btn btn-default pull-right" (click)="onPageBack()">
+        <span class="fa fa-reply"></span> {{'administration.extsystem.btn-back' | translate }}
+      </button>
+    </div>
+
+    <mdm-extsystem-editor *ngIf="editorMode" [selectedEnvironment]="selectedEnvironment" [extSystems]="extSystems" (editMode)="onEditModeChange($event)" [selectedES]="selectedExtSystem"></mdm-extsystem-editor>
+    <mdm-extsystem-viewer *ngIf="!editorMode" [selectedEnvironment]="selectedEnvironment" [extSystems]="extSystems" (editMode)="onEditModeChange($event)" (selectedES)="onChangeSelectedExtSystem($event)"></mdm-extsystem-viewer>
+  </div>
+</div>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.ts
new file mode 100644
index 0000000..e6e0757
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.component.ts
@@ -0,0 +1,103 @@
+/********************************************************************************
+ * 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, OnInit, ViewChild, Input, OnDestroy } from '@angular/core';
+import { FormGroup, FormControl, FormBuilder, FormArray, Validators } from '@angular/forms';
+import { TranslateService } from '@ngx-translate/core';
+import { DropdownModule } from 'primeng/dropdown';
+import { TreeTableModule } from 'primeng/treetable';
+import { TreeNode, MenuItem } from 'primeng/api';
+import { ContextMenuModule } from 'primeng/contextmenu';
+import { TreeTable, TreeTableToggler, DataTable } from 'primeng/primeng';
+import { DialogModule } from 'primeng/dialog';
+import { Observable, BehaviorSubject } from 'rxjs';
+
+import { MDMNotificationService } from '../core/mdm-notification.service';
+import { ExtSystemService } from './extsystem.service';
+import { Node } from '../navigator/node';
+import { NodeService } from '../navigator/node.service';
+
+@Component( {
+    selector: 'mdm-extsystem',
+    templateUrl: './extsystem.component.html',
+    styleUrls: ['./extsystem.component.css']
+})
+export class ExtSystemComponent implements OnInit, OnDestroy {
+
+  // available external systems
+  extSystems: Node[];
+  bsExtSystems: BehaviorSubject<Node[]> = new BehaviorSubject<Node[]>(undefined);
+  selectedEnvironment: Node;
+
+  editorMode: boolean = false;
+  selectedExtSystem: string = undefined;
+
+  // dropdown selection
+  environments: Node[];
+
+  //  unknown -> remove
+  scope: string;
+  
+  constructor(private extSystemService: ExtSystemService,
+               private notificationService: MDMNotificationService,
+    private translateService: TranslateService,
+    private nodeService: NodeService) {
+
+    this.bsExtSystems.subscribe(value => {
+      this.extSystems = value;
+    });
+  }
+
+  ngOnInit() {
+    this.nodeService.getRootNodes().subscribe(
+      envs => this.environments = envs,
+      error => this.translateService.instant('basket.mdm-basket.err-cannot-load-sources')
+        .subscribe(msg => this.notificationService.notifyError(msg, error)
+    ));
+
+    this.bsExtSystems.next(undefined);
+  }
+
+  ngOnDestroy() {
+  }
+
+  onEditModeChange(editMode: boolean) {
+    this.editorMode = editMode;
+  }
+
+  onChangeSelectedExtSystem(extSystem: string) {
+    this.selectedExtSystem = extSystem;
+  }
+
+  onChangeExtSystem(event: any) {
+    this.extSystems = undefined;
+    if (this.selectedEnvironment !== undefined)
+      this.scope = this.selectedEnvironment.sourceName;
+    this.extSystemService.getExtSystemForScope(this.scope)
+      .subscribe(es => {
+        this.bsExtSystems.next(es);
+      });
+  }
+
+
+  onPageSwitchToEdit() {
+    this.onEditModeChange(true);
+  }
+ 
+  onPageBack() {
+    this.onEditModeChange(false);
+  }
+
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.service.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.service.ts
new file mode 100644
index 0000000..0bd2ff4
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/extsystem.service.ts
@@ -0,0 +1,166 @@
+/********************************************************************************
+ * 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 { Http, Response, Headers, RequestOptions } from '@angular/http';
+
+import { PropertyService } from '../core/property.service';
+
+import { plainToClass } from 'class-transformer';
+import { HttpErrorHandler } from '../core/http-error-handler';
+import { throwError as observableThrowError,  Observable } from 'rxjs';
+import { map, catchError } from 'rxjs/operators';
+import { Node } from '../navigator/node';
+
+@Injectable()
+export class ExtSystemService {
+
+  private prefEndpoint: string;
+
+  constructor(private http: Http,
+              private httpErrorHandler: HttpErrorHandler,
+              private _prop: PropertyService) {
+    this.prefEndpoint = _prop.getUrl('mdm/administration/');
+  }
+
+  getExtSystemForScope(scope: string, key?: string): Observable<Node[]> {
+      if (key == null) {
+          key = '';
+      }
+
+    return this.http.get(this.prefEndpoint + scope + '/externalsystems').pipe(
+      map(response => plainToClass(Node, response.json().data)),
+          catchError(this.handleError));
+  }
+
+  getExtSystemAttributesForScope(scope: string, id: string, key?: string): Observable<Node[]> {
+      if (key == null) {
+          key = '';
+      }
+
+    return this.http.get(this.prefEndpoint + scope + '/externalsystems/attributes/' + id).pipe(
+      map(response => plainToClass(Node, response.json().data)),
+          catchError(this.handleError));
+  }
+
+  getExtSystem(key?: string) {
+      if (key == null) {
+          key = '';
+      }
+      return this.http.get(this.prefEndpoint + '?key=' + key).pipe(
+        map(response => plainToClass(Node, response.json().data)),
+          catchError(this.handleError));
+  }
+
+  getAttributeValueFromNode(node: Node, attribute: string) {
+    if (node.attributes !== undefined) {
+      for (let i in node.attributes) {
+        if (node.attributes[i].name === attribute) {
+          return node.attributes[i].value;
+        }
+      }
+    }
+    return '';
+  }
+
+  saveExtSystemAttr(scope: string, extSystemAttr: Node, extSystem: Node) {
+    let headers = new Headers({ 'Content-Type': 'application/json' });
+    let options = new RequestOptions({ headers: headers });
+
+    let structure = {};
+    structure['Name'] = extSystemAttr.name;
+
+    if (parseInt(extSystemAttr.id) > 0) {
+      structure['Description'] = this.getAttributeValueFromNode(extSystemAttr, 'Description');
+      // update
+      return this.http.put(this.prefEndpoint + scope + '/externalsystems/attribute/' + extSystemAttr.id, JSON.stringify(structure), options).pipe(
+        catchError(this.handleError));
+    } else {
+      structure['ExtSystemAttribute'] = extSystemAttr;
+      // only provide the ID as the backend will evaluate the whole json string
+      structure['ExtSystem'] = extSystem.id;
+      // create
+      return this.http.post(this.prefEndpoint + scope + '/externalsystems/attribute', JSON.stringify(structure), options).pipe(
+        catchError(this.handleError));
+    }
+  }
+
+  saveExtSystemMDMAttr(scope: string, extSystemMDMAttr: Node, extSystemAttr: Node) {
+    let headers = new Headers({ 'Content-Type': 'application/json' });
+    let options = new RequestOptions({ headers: headers });
+
+    let structure = {};
+    structure['Name'] = extSystemMDMAttr.name;
+
+    if (parseInt(extSystemMDMAttr.id) > 0) {
+      structure['CompType'] = this.getAttributeValueFromNode(extSystemMDMAttr, 'CompType');
+      structure['CompName'] = this.getAttributeValueFromNode(extSystemMDMAttr, 'CompName');
+      structure['AttrName'] = this.getAttributeValueFromNode(extSystemMDMAttr, 'AttrName');
+      // update
+      return this.http.put(this.prefEndpoint + scope + '/externalsystems/mdmattribute/' + extSystemMDMAttr.id, JSON.stringify(structure), options).pipe(
+        catchError(this.handleError));
+    } else {
+      structure['MDMAttribute'] = extSystemMDMAttr;
+      structure['ExtSystemAttribute'] = extSystemAttr.id;
+      // create
+      return this.http.post(this.prefEndpoint + scope + '/externalsystems/mdmattribute', JSON.stringify(structure), options).pipe(
+        catchError(this.handleError));
+    }
+  }
+
+  saveExtSystem(scope: string, extSystem: Node) {
+    let headers = new Headers({ 'Content-Type': 'application/json' });
+    let options = new RequestOptions({ headers: headers });
+
+    let structure = {};
+    structure['Name'] = extSystem.name;
+
+    if (parseInt(extSystem.id) > 0) {
+      // update
+      structure['Description'] = this.getAttributeValueFromNode(extSystem, 'Description');
+      return this.http.put(this.prefEndpoint + scope + '/externalsystems/' + extSystem.id, JSON.stringify(structure), options).pipe(
+        catchError(this.handleError));
+    } else {
+      structure['ExtSystem'] = extSystem;
+      // create
+      return this.http.post(this.prefEndpoint + scope + '/externalsystems', JSON.stringify(structure), options).pipe(
+        catchError(this.handleError));
+    }
+  }
+
+  deleteExtSystem(scope: string, id: string) {
+    return this.http.delete(this.prefEndpoint + scope + '/externalsystems/' + id).pipe(
+      catchError(this.handleError));
+  }
+  deleteExtSystemAttr(scope: string, id: string) {
+    return this.http.delete(this.prefEndpoint + scope + '/externalsystems/attribute/' + id).pipe(
+      catchError(this.handleError));
+  }
+  deleteExtSystemMDMAttr(scope: string, id: string) {
+    return this.http.delete(this.prefEndpoint + scope + '/externalsystems/mdmattribute/' + id).pipe(
+      catchError(this.handleError));
+  }
+
+  private handleError(e: Error | any) {
+    if (e instanceof Response) {
+      let response = <Response> e;
+      if (response.status !== 200) {
+        return observableThrowError('Could not request extSystems! '
+          + 'Please check if application server is running and extSystem database is configured correctly.');
+      }
+    }
+    return this.httpErrorHandler.handleError(e);
+  }
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-module.component.html b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-module.component.html
new file mode 100644
index 0000000..e818119
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-module.component.html
@@ -0,0 +1,30 @@
+<!--/********************************************************************************
+* 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
+*
+********************************************************************************/-->
+
+<nav class="navbar navbar-default navbar-expand  bg-light">
+  <div class="container-fluid">
+    <div class="collapse navbar-collapse" id="bs-admin-navbar">
+      <a class="navbar-brand" style="font-weight: 500;">{{'administration.admin-modules.scope' | translate }}</a>
+      <ul class="nav navbar-nav mdm-link-list">
+        <li *ngFor="let m of links" [routerLinkActive]="['active']" class="nav-item">
+          <a (click)="onScopeChange(m.path)" routerLink="{{m.path}}" class="nav-link" style="cursor:pointer;">
+            {{m.name | translate}}
+          </a>
+        </li>
+      </ul>
+    </div>
+
+  </div>
+</nav>
+<mdm-preference [scope]="scope" [preferences]="preferences"></mdm-preference>
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-module.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-module.component.ts
new file mode 100644
index 0000000..ab14b0a
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-module.component.ts
@@ -0,0 +1,52 @@
+/********************************************************************************
+ * 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, OnInit} from '@angular/core';
+import {Router} from '@angular/router';
+
+import { TRANSLATE } from '../core/mdm-core.module';
+import { PreferenceService, Preference } from '../core/preference.service';
+
+@Component({
+  selector: 'preference-module',
+  templateUrl: 'preference-module.component.html',
+  providers: []
+})
+export class PreferenceModuleComponent implements OnInit {
+
+  preferences: Preference[];
+  scope: string;
+  sub: any;
+
+  links = [
+    { name: TRANSLATE('administration.admin-modules.system'), path: 'system'},
+    { name: TRANSLATE('administration.admin-modules.source'), path: 'source'},
+    { name: TRANSLATE('administration.admin-modules.user'), path: 'user'}
+  ];
+  constructor(private router: Router,
+    private preferenceService: PreferenceService) { }
+
+  ngOnInit() {
+    this.onScopeChange('system');
+  }
+
+  onScopeChange(path: string) {
+    this.scope = path.toUpperCase();
+    if (this.scope !== undefined) {
+      this.preferenceService.getPreferenceForScope(this.scope)
+        .subscribe(pref => this.preferences = pref);
+    }
+  }
+}
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-routing.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-routing.module.ts
new file mode 100644
index 0000000..b36bfc4
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference-routing.module.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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { PreferenceComponent } from './preference.component';
+import { PreferenceModuleComponent } from './preference-module.component';
+
+const moduleRoutes: Routes = [
+  {
+    path: '', component: PreferenceModuleComponent,
+    children: [
+      { path: ':scope', component: PreferenceComponent },
+      { path: '', redirectTo: 'system', pathMatch: 'full' }
+    ]
+  }
+];
+
+@NgModule({
+  imports: [
+    RouterModule.forChild(moduleRoutes)
+  ],
+  exports: [
+    RouterModule
+  ]
+})
+
+export class PreferenceRoutingModule { }
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference.component.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference.component.ts
index 72f49a6..45f466e 100644
--- a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference.component.ts
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference.component.ts
@@ -20,7 +20,7 @@
 import { PreferenceService, Preference, Scope } from '../core/preference.service';
 import { EditPreferenceComponent } from './edit-preference.component';
 
-import {MDMNotificationService} from '../core/mdm-notification.service';
+import { MDMNotificationService } from '../core/mdm-notification.service';
 
 import { TranslateService } from '@ngx-translate/core';
 
@@ -32,7 +32,7 @@
 export class PreferenceComponent implements OnInit, OnDestroy {
 
   @Input() preferences: Preference[];
-  scope: string;
+  @Input() scope: string;
   subscription: any;
   sub: any;
 
@@ -46,21 +46,10 @@
                private translateService: TranslateService) { }
 
   ngOnInit() {
-    this.sub = this.route.params.subscribe(
-        params => this.onScopeChange(params),
-        error => this.notificationService.notifyError(
-          this.translateService.instant('administration.edit-preference.err-cannot-load-scope'), error)
-      );
+    
   }
 
   ngOnDestroy() {
-      this.sub.unsubscribe();
-  }
-
-  onScopeChange(params: any) {
-      this.scope = params['scope'].toUpperCase();
-      this.preferenceService.getPreferenceForScope(this.scope)
-        .subscribe(pref => this.preferences = pref);
   }
 
   editPreference( preference?: Preference ) {
diff --git a/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference.module.ts b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference.module.ts
new file mode 100644
index 0000000..49becfa
--- /dev/null
+++ b/org.eclipse.mdm.application/src/main/webapp/src/app/administration/preference.module.ts
@@ -0,0 +1,45 @@
+/********************************************************************************
+ * 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 { PreferenceRoutingModule } from './preference-routing.module';
+import { PreferenceModuleComponent } from './preference-module.component';
+import { PreferenceComponent } from './preference.component';
+import { EditPreferenceComponent } from './edit-preference.component';
+import { MDMCoreModule } from '../core/mdm-core.module';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { PreferenceService } from '../core/preference.service';
+import { ComponentLoaderFactory } from 'ngx-bootstrap';
+
+
+@NgModule({
+  imports: [
+    MDMCoreModule,
+    FormsModule,
+    ReactiveFormsModule,
+    PreferenceRoutingModule
+  ],
+  declarations: [
+    EditPreferenceComponent,
+    PreferenceModuleComponent,
+    PreferenceComponent
+  ],
+  exports: [
+  ],
+  providers: [
+    ComponentLoaderFactory,
+    PreferenceService
+  ],
+})
+export class PreferenceModule {}
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 334a0a6..ad15cbf 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
@@ -1,31 +1,69 @@
 {
-	"administration": {
-		"admin-modules": {
-			"scope": "Geltungsbereich",
-			"source": "Quelle",
-			"system": "System",
-			"user": "Benutzer"
-		},
-		"edit-preference": {
-			"btn-cancel": "Abbrechen",
-			"btn-save": "Speichern",
-			"err-cannot-load-data-source": "Datenquelle kann nicht geladen werden.",
-			"err-cannot-load-scope": "Geltungsbereich kann nicht geladen werden.",
-			"lbl-key": "Schlüssel",
-			"lbl-scope": "Geltungsbereich",
-			"lbl-source": "Quelle",
-			"lbl-value": "Wert",
-			"title-preferences-editor": "Einstellungseditor",
-			"tooltip-close": "Schließen"
-		},
-		"preference": {
-			"btn-add-preference": "Einstellung hinzufügen",
-			"btn-tooltip-delete-preference": "Entfernen",
-			"btn-tooltip-edit-preference": "Bearbeiten",
-			"err-cannot-save-preference": "Einstellung kann nicht gespeichert werden.",
-			"err-cannot-update-preference": "Einstellung kann nicht aktualisiert werden."
-		}
-	},
+  "administration": {
+    "admin-modules": {
+      "scope": "Geltungsbereich",
+      "modules": "Module",
+      "source": "Quelle",
+      "system": "System",
+      "user": "Benutzer",
+      "extsystems": "Externe Systeme",
+      "preferences": "Präferenzen"
+    },
+    "edit-preference": {
+      "btn-cancel": "Abbrechen",
+      "btn-save": "Speichern",
+      "err-cannot-load-data-source": "Datenquelle kann nicht geladen werden.",
+      "err-cannot-load-scope": "Geltungsbereich kann nicht geladen werden.",
+      "lbl-key": "Schlüssel",
+      "lbl-scope": "Geltungsbereich",
+      "lbl-source": "Quelle",
+      "lbl-value": "Wert",
+      "title-preferences-editor": "Einstellungseditor",
+      "tooltip-close": "Schließen"
+    },
+    "preference": {
+      "btn-add-preference": "Einstellung hinzufügen",
+      "btn-tooltip-delete-preference": "Entfernen",
+      "btn-tooltip-edit-preference": "Bearbeiten",
+      "err-cannot-save-preference": "Einstellung kann nicht gespeichert werden.",
+      "err-cannot-update-preference": "Einstellung kann nicht aktualisiert werden."
+    },
+    "extsystem": {
+      "loading": "Lade Daten...",
+      "loading-short": "Lade",
+      "dropdown-please-select": "Bitte auswählen",
+      "select-system": "Datenquelle auswählen",
+      "selected-system": "Gewählte Datenquelle",
+      "name": "Name",
+      "description": "Beschreibung",
+      "ext-system-attributes": "Externes System Attribute",
+      "mdm-attributes": "MDM Attribute",
+      "btn-add": "Neu hinzufügen",
+      "btn-edit": "Bearbeiten",
+      "btn-del": "Entfernen",
+      "btn-save": "Speichern",
+      "btn-cancel": "Abbrechen",
+      "btn-back": "Zurück",
+      "component-type": "Komponenten-Typ",
+      "component-name": "Komponenten-Name",
+      "attribute-name": "Attribut-Name",
+      "dialog-ext-system-title": "Externes System",
+      "dialog-ext-system-attr-title": "Externes System Attribute",
+      "dialog-ext-system-mdm-attr-title": "MDM Attribute",
+      "dialog-ext-system-delete-title": "Externes System löschen",
+      "dialog-ext-system-delete-text": "Bitte bestätigen Sie das Löschen des Externen Systems",
+      "err-cannot-update-ext-system": "Externes System kann nicht gespeichert werden",
+      "err-cannot-save-ext-system": "Externes System kann nicht aktualisiert werden",
+      "err-cannot-save-ext-system-attr": "Externes System Attribut kann nicht gespeichert werden",
+      "err-cannot-save-ext-mdm-attr": "MDM Attribut kann nicht gespeichert werden",
+      "err-cannot-load-comp-types": "Komponententypen konnten nicht geladen werden",
+      "loading-attributes": "Lade Attribute...",
+      "sensor": "Sensor",
+      "test-equipment": "Messgerät",
+      "test-sequence": "Testablauf",
+      "unit-under-test": "Prüfling"
+    }
+  },
 	"app": {
 		"about": "Über",
 		"language": "Sprache",
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 17e2c32..ac998b3 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
@@ -1,11 +1,14 @@
 {
 	"administration": {
-		"admin-modules": {
-			"scope": "Scope",
-			"source": "Source",
-			"system": "System",
-			"user": "User"
-		},
+    "admin-modules": {
+      "scope": "Scope",
+      "modules": "Module",
+      "source": "Source",
+      "system": "System",
+      "user": "User",
+      "extsystems": "External Systems",
+      "preferences": "Preferences"
+    },
 		"edit-preference": {
 			"btn-cancel": "Cancel",
 			"btn-save": "Save",
@@ -18,13 +21,48 @@
 			"title-preferences-editor": "Preferences Editor",
 			"tooltip-close": "Close"
 		},
-		"preference": {
-			"btn-add-preference": "Add preference",
-			"btn-tooltip-delete-preference": "Delete",
-			"btn-tooltip-edit-preference": "Edit",
-			"err-cannot-save-preference": "Cannot save preference.",
-			"err-cannot-update-preference": "Cannot update preference."
-		}
+    "preference": {
+      "btn-add-preference": "Add preference",
+      "btn-tooltip-delete-preference": "Delete",
+      "btn-tooltip-edit-preference": "Edit",
+      "err-cannot-save-preference": "Cannot save preference.",
+      "err-cannot-update-preference": "Cannot update preference."
+    },
+    "extsystem": {
+      "loading": "Loading data...",
+      "loading-short": "Loading",
+      "dropdown-please-select": "Please select...",
+      "select-system": "Select environment",
+      "selected-system": "Selected environment",
+      "name": "Name",
+      "description": "Description",
+      "ext-system-attributes": "Ext. system attributes",
+      "mdm-attributes": "MDM attributes",
+      "btn-add": "Add new",
+      "btn-edit": "Edit",
+      "btn-del": "Remove",
+      "btn-save": "Save",
+      "btn-cancel": "Cancel",
+      "btn-back": "Back",
+      "component-type": "Component type",
+      "component-name": "Component name",
+      "attribute-name": "Attribute name",
+      "dialog-ext-system-title": "External system",
+      "dialog-ext-system-attr-title": "External system attribute",
+      "dialog-ext-system-mdm-attr-title": "MDM attribute",
+      "dialog-ext-system-delete-title": "External system deletion",
+      "dialog-ext-system-delete-text": "Please confirm the deletion of the external system",
+      "err-cannot-update-ext-system": "External system cannot be saved",
+      "err-cannot-save-ext-system": "External system cannot be refreshed",
+      "err-cannot-save-ext-system-attr": "External system attribute cannot be saved",
+      "err-cannot-save-ext-mdm-attr": "MDM attribute cannot be saved",
+      "err-cannot-load-comp-types": "Cannot load component types",
+      "loading-attributes": "Loading attributes...",
+      "sensor": "Sensor",
+      "test-equipment": "Test equipment",
+      "test-sequence": "Test sequence",
+      "unit-under-test": "Unit under test"
+    }
 	},
 	"app": {
 		"about": "About",
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ExtSystemResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ExtSystemResource.java
new file mode 100644
index 0000000..a06a7c4
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ExtSystemResource.java
@@ -0,0 +1,359 @@
+/********************************************************************************
+ * 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 io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.vavr.collection.List;
+import io.vavr.control.Try;
+import org.eclipse.mdm.api.base.model.*;
+import org.eclipse.mdm.api.dflt.model.ExtSystem;
+import org.eclipse.mdm.api.dflt.model.ExtSystemAttribute;
+import org.eclipse.mdm.api.dflt.model.MDMAttribute;
+import org.eclipse.mdm.api.dflt.model.ValueList;
+import org.eclipse.mdm.businessobjects.control.FileLinkActivity;
+import org.eclipse.mdm.businessobjects.entity.*;
+import org.eclipse.mdm.businessobjects.service.ContextService;
+import org.eclipse.mdm.businessobjects.service.EntityService;
+import org.eclipse.mdm.businessobjects.utils.RequestBody;
+import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
+
+import javax.ejb.EJB;
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_ID;
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_SOURCENAME;
+import static org.eclipse.mdm.businessobjects.service.EntityService.V;
+
+/**
+ * {@link MDMEntity} REST interface for external systems
+ * 
+ * @author Juergen Kleck, Peak Solution GmbH
+ *
+ */
+@Tag(name = "ExtSystem")
+@Path("/administration/{" + REQUESTPARAM_SOURCENAME + "}/externalsystems")
+public class ExtSystemResource {
+
+	@EJB
+	private ExtSystemService extSystemService;
+
+	@EJB
+	private EntityService entityService;
+
+	@EJB
+	private ContextService contextService;
+
+	@EJB
+	private FileLinkActivity fileLinkActivity;
+
+
+	/**
+	 * delegates the request to the {@link ExtSystemService}
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param filter     filter string to filter the ExtSystem result
+	 * @return the result of the delegated request as {@link Response}
+	 */
+	@GET
+	@Operation(summary = "Find ExtSystems by filter", description = "Get list of ExtSystems", responses = {
+			@ApiResponse(description = "The ExtSystems", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Error") })
+	@Produces(MediaType.APPLICATION_JSON)
+	public Response getExtSystems(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
+
+		return Try.of(() -> extSystemService.getExtSystems(sourceName)).map(List::ofAll).map(e -> ServiceUtils.buildEntityResponse(e, Status.OK, ExtSystem.class))
+				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * delegates the request to the {@link ExtSystemService}
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param filter     filter string to filter the ExtSystem result
+	 * @return the result of the delegated request as {@link Response}
+	 */
+	@GET
+	@Operation(summary = "Find ExtSystems by filter", description = "Get list of ExtSystems", responses = {
+			@ApiResponse(description = "The ExtSystems", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Error") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/attributes/{" + REQUESTPARAM_ID + "}")
+	public Response getExtSystemAttributes(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@Parameter(description = "ID of the ExtSystem", required = true) @PathParam(REQUESTPARAM_ID) String id) {
+
+		java.util.List all = new java.util.ArrayList<>();
+		java.util.List extSystems = extSystemService.getExtSystemAttributes(sourceName, id);
+		// the ext. system attributes
+		all.addAll(extSystems);
+		// the mdm attributes
+		extSystems.stream().forEach(e -> all.addAll(((ExtSystemAttribute) e).getAttributes()));
+
+		return ServiceUtils.buildEntityResponse(List.ofAll(all), Status.OK, ExtSystemAttribute.class);
+	}
+
+	/**
+	 * delegates the request to the {@link ExtSystemService}
+	 *
+	 * @param sourceName    name of the source (MDM {@link Environment} name)
+	 * @param id id of the {@link MDMEntity}
+	 * @return the result of the delegated request as {@link Response}
+	 */
+	@GET
+	@Operation(summary = "Find a ExtSystem by ID", description = "Returns ExtSystem based on ID", responses = {
+			@ApiResponse(description = "The Project", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Invalid ID supplied") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}")
+	public Response findExtSystem(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@Parameter(description = "ID of the ExtSystem", required = true) @PathParam(REQUESTPARAM_ID) String id) {
+		return entityService.find(V(sourceName), ExtSystem.class, V(id))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Returns the created {@link MDMEntity}.
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param body       The {@link MDMEntity} to create.
+	 * @return the created {@link MDMEntity} as {@link Response}.
+	 */
+	@POST
+	@Operation(summary = "Create a new ExtSystem",  responses = {
+			@ApiResponse(description = "The created ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "500", description = "Error") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	public Response createExtSystem(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName, String body) {
+
+		return entityService
+				.create(V(sourceName), ExtSystem.class,
+						entityService.extractRequestBody(body, sourceName, List.of(ExtSystem.class)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.CREATED))
+				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Returns the created {@link MDMEntity}.
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param body       The {@link MDMEntity} to create.
+	 * @return the created {@link MDMEntity} as {@link Response}.
+	 */
+	@POST
+	@Operation(summary = "Create a new ExtSystem",  responses = {
+			@ApiResponse(description = "The created ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "500", description = "Error") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	@Path("/attribute")
+	public Response createExtSystemAttribute(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName, String body) {
+
+		return entityService
+				.create(V(sourceName), ExtSystemAttribute.class,
+						entityService.extractRequestBody(body, sourceName, List.of(ExtSystem.class, ExtSystemAttribute.class)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.CREATED))
+				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Returns the created {@link MDMEntity}.
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param body       The {@link MDMEntity} to create.
+	 * @return the created {@link MDMEntity} as {@link Response}.
+	 */
+	@POST
+	@Operation(summary = "Create a new ExtSystem",  responses = {
+			@ApiResponse(description = "The created ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "500", description = "Error") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	@Path("/mdmattribute")
+	public Response createMDMAttribute(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName, String body) {
+
+		return entityService
+				.create(V(sourceName), MDMAttribute.class,
+						entityService.extractRequestBody(body, sourceName, List.of(ExtSystemAttribute.class, MDMAttribute.class)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.CREATED))
+				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Updates the {@link MDMEntity} 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 MDMEntity} to update.
+	 * @param body       the body of the request containing the attributes to update
+	 * @return the updated {@link MDMEntity}
+	 */
+	@PUT
+	@Operation(summary = "Update an existing ExtSystem", description = "Updates the ExtSystem with all parameters set in the body of the request.", responses = {
+			@ApiResponse(description = "The updated ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Invalid ID supplied") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}")
+	public Response updateExtSystem(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName, 
+			@Parameter(description = "ID of the ExtSystem", required = true) @PathParam(REQUESTPARAM_ID) String id,
+			String body) {
+		RequestBody requestBody = RequestBody.create(body);
+
+		return entityService
+				.update(V(sourceName), entityService.find(V(sourceName), ExtSystem.class, V(id)),
+						requestBody.getValueMapSupplier())
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Updates the {@link MDMEntity} 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 MDMEntity} to update.
+	 * @param body       the body of the request containing the attributes to update
+	 * @return the updated {@link MDMEntity}
+	 */
+	@PUT
+	@Operation(summary = "Update an existing ExtSystem", description = "Updates the ExtSystem with all parameters set in the body of the request.", responses = {
+			@ApiResponse(description = "The updated ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Invalid ID supplied") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	@Path("/attribute/{" + REQUESTPARAM_ID + "}")
+	public Response updateExtSystemAttribute(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@Parameter(description = "ID of the ExtSystem", required = true) @PathParam(REQUESTPARAM_ID) String id,
+			String body) {
+		RequestBody requestBody = RequestBody.create(body);
+
+		return entityService
+				.update(V(sourceName), entityService.find(V(sourceName), ExtSystemAttribute.class, V(id)),
+						requestBody.getValueMapSupplier())
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Updates the {@link MDMEntity} 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 MDMEntity} to update.
+	 * @param body       the body of the request containing the attributes to update
+	 * @return the updated {@link MDMEntity}
+	 */
+	@PUT
+	@Operation(summary = "Update an existing ExtSystem", description = "Updates the ExtSystem with all parameters set in the body of the request.", responses = {
+			@ApiResponse(description = "The updated ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Invalid ID supplied") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	@Path("/mdmattribute/{" + REQUESTPARAM_ID + "}")
+	public Response updateMDMAttribute(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@Parameter(description = "ID of the ExtSystem", required = true) @PathParam(REQUESTPARAM_ID) String id,
+			String body) {
+		RequestBody requestBody = RequestBody.create(body);
+
+		return entityService
+				.update(V(sourceName), entityService.find(V(sourceName), MDMAttribute.class, V(id)),
+						requestBody.getValueMapSupplier())
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Deletes and returns the deleted {@link MDMEntity}.
+	 * 
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         The identifier of the {@link MDMEntity} to delete.
+	 * @return the deleted {@link ValueList }s as {@link Response}
+	 */
+	@DELETE
+	@Operation(summary = "Delete an existing ExtSystem", responses = {
+			@ApiResponse(description = "The deleted ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Invalid ID supplied") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}")
+	public Response deleteExtSystem(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@Parameter(description = "ID of the ExtSystem", required = true) @PathParam(REQUESTPARAM_ID) String id) {
+		return entityService.delete(V(sourceName), entityService.find(V(sourceName), ExtSystem.class, V(id)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Deletes and returns the deleted {@link MDMEntity}.
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         The identifier of the {@link MDMEntity} to delete.
+	 * @return the deleted {@link ValueList }s as {@link Response}
+	 */
+	@DELETE
+	@Operation(summary = "Delete an existing ExtSystem", responses = {
+			@ApiResponse(description = "The deleted ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Invalid ID supplied") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/attribute/{" + REQUESTPARAM_ID + "}")
+	public Response deleteExtSystemAttribute(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@Parameter(description = "ID of the ExtSystem", required = true) @PathParam(REQUESTPARAM_ID) String id) {
+		return entityService.delete(V(sourceName), entityService.find(V(sourceName), ExtSystemAttribute.class, V(id)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Deletes and returns the deleted {@link MDMEntity}.
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         The identifier of the {@link MDMEntity} to delete.
+	 * @return the deleted {@link ValueList }s as {@link Response}
+	 */
+	@DELETE
+	@Operation(summary = "Delete an existing ExtSystem", responses = {
+			@ApiResponse(description = "The deleted ExtSystem", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Invalid ID supplied") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/mdmattribute/{" + REQUESTPARAM_ID + "}")
+	public Response deleteMDMAttribute(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@Parameter(description = "ID of the ExtSystem", required = true) @PathParam(REQUESTPARAM_ID) String id) {
+		return entityService.delete(V(sourceName), entityService.find(V(sourceName), MDMAttribute.class, V(id)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, 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/ExtSystemService.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ExtSystemService.java
new file mode 100644
index 0000000..df6c50d
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ExtSystemService.java
@@ -0,0 +1,105 @@
+/********************************************************************************
+ * 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.api.base.model.*;
+import org.eclipse.mdm.api.base.query.DataAccessException;
+import org.eclipse.mdm.api.dflt.EntityManager;
+import org.eclipse.mdm.api.dflt.model.ExtSystem;
+import org.eclipse.mdm.api.dflt.model.ExtSystemAttribute;
+import org.eclipse.mdm.businessobjects.control.*;
+import org.eclipse.mdm.connector.boundary.ConnectorService;
+
+import javax.ejb.Stateless;
+import javax.inject.Inject;
+import java.util.*;
+
+/**
+ * ExtSystemService Bean implementation with available
+ * operations
+ * 
+ * @author Juergen Kleck, Peak Solution GmbH
+ *
+ */
+@Stateless
+public class ExtSystemService {
+
+	@Inject
+	private ConnectorService connectorService;
+
+	/**
+	 * returns the matching {@link ExtSystem}s using the given filter or all
+	 * {@link ExtSystem}s if no filter is available
+	 * 
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @return the found {@link ExtSystem}s
+	 */
+	public List<ExtSystem> getExtSystems(String sourceName) {
+		List<ExtSystem> list = new ArrayList<>();
+		try {
+			Optional<List<ExtSystem>> values = this.connectorService.getContextByName(sourceName).getEntityManager()
+					.map(em -> em.loadAll(ExtSystem.class));
+
+			if(values.isPresent()) {
+				list = values.get();
+			}
+		} catch (DataAccessException e) {
+			throw new MDMEntityAccessException(e.getMessage(), e);
+		}
+		return list;
+	}
+
+	/**
+	 * returns a {@link ExtSystem} identified by the given id.
+	 * 
+	 * @param sourceName    name of the source (MDM {@link Environment} name)
+	 * @param extSystemId id of the {@link ExtSystem}
+	 * @return the matching {@link ExtSystem}
+	 */
+	public ExtSystem getExtSystem(String sourceName, String extSystemId) {
+		try {
+			EntityManager em = this.connectorService.getContextByName(sourceName).getEntityManager()
+					.orElseThrow(() -> new MDMEntityAccessException("Entity manager not present!"));
+			return em.load(ExtSystem.class, extSystemId);
+		} catch (DataAccessException e) {
+			throw new MDMEntityAccessException(e.getMessage(), e);
+		}
+	}
+
+	/**
+	 * returns the matching {@link ExtSystemAttribute}s using the given filter or all
+	 * {@link ExtSystemAttribute}s if no filter is available
+	 *
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         the id of the external system
+	 * @return the found {@link ExtSystemAttribute}s
+	 */
+	public List<ExtSystemAttribute> getExtSystemAttributes(String sourceName, String id) {
+		List<ExtSystemAttribute> list = new ArrayList<>();
+		try {
+
+			Optional<List<ExtSystemAttribute>> values = this.connectorService.getContextByName(sourceName).getEntityManager()
+					.map(em -> em.loadChildren(em.load(ExtSystem.class, id), ExtSystemAttribute.class));
+
+			if(values.isPresent()) {
+				list = values.get();
+			}
+		} catch (DataAccessException e) {
+			throw new MDMEntityAccessException(e.getMessage(), e);
+		}
+		return list;
+	}
+
+}
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateComponentResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateComponentResource.java
index 9265f83..e0b95e1 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateComponentResource.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateComponentResource.java
@@ -38,6 +38,7 @@
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
+import io.vavr.control.Try;
 import org.eclipse.mdm.api.base.model.ContextType;
 import org.eclipse.mdm.api.base.model.Environment;
 import org.eclipse.mdm.api.dflt.model.CatalogComponent;
@@ -51,6 +52,8 @@
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.vavr.collection.List;
 
+import java.util.Collections;
+
 /**
  * {@link TemplateComponent} resource handling REST requests
  * 
@@ -111,6 +114,45 @@
 	}
 
 	/**
+	 * Returns the (filtered) {@link TemplateComponent}s.
+	 *
+	 * @param sourceName  name of the source (MDM {@link Environment} name)
+	 * @param contextType {@link ContextType} of the {@link TemplateComponent} to
+	 *                    load
+	 * @param filter      filter string to filter the {@link TemplateComponent}
+	 *                    result
+	 * @return the (filtered) {@link TemplateComponent}s as {@link Response}
+	 */
+	@GET
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/rootList")
+	public Response findAllForAllRoots(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@PathParam(REQUESTPARAM_CONTEXTTYPE) String contextTypeParam, @PathParam(REQUESTPARAM_ID) String tplRootIds,
+			@QueryParam("filter") String filter) {
+		java.util.List<TemplateComponent> components = new java.util.ArrayList<>();
+
+		String[] rootIds = tplRootIds.split(",");
+		for(String rootId : rootIds) {
+			entityService
+					.find(V(sourceName), TemplateRoot.class, V(rootId),
+							ServiceUtils.getContextTypeSupplier(contextTypeParam))
+					.map(e -> {
+						for(int j=0; j<e.getTemplateComponents().size(); j++) {
+							final TemplateComponent cmp = e.getTemplateComponents().get(j);
+							if(components.stream().noneMatch(tmp -> tmp.getName().equals(cmp.getName()))) {
+								components.add(cmp);
+							}
+						}
+						return true;
+					});
+		}
+		Collections.sort(components, (TemplateComponent tc1, TemplateComponent tc2) -> tc1.getName().compareTo(tc2.getName()));
+
+		return Try.of(() -> components).map(List::ofAll).map(e -> ServiceUtils.buildEntityResponse(e, Status.OK))
+					.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
 	 * Returns the created {@link TemplateComponentValue}.
 	 * 
 	 * @param body The {@link TemplateComponent} to create.
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 84ec915..1aaf322 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
@@ -261,6 +261,8 @@
 		Core core = ServiceUtils.getCore(entity);
 		// get children hash (type is key)
 		return HashMap.ofAll(core.getChildrenStore().getCurrent())
+				// if we don't check the size we crash, some elements may not have a relation
+				.filter(entry -> entry._2.size() > 0)
 				// create child relations (the ContextType is assumed to be same as the parent's
 				// one
 				.map(entry -> new MDMRelation(null, MDMRelation.RelationType.CHILDREN, entry._1.getSimpleName(),
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ServiceUtils.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ServiceUtils.java
index 9cb9fc2..013ddbc 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ServiceUtils.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ServiceUtils.java
@@ -40,12 +40,7 @@
 import org.eclipse.mdm.api.base.query.FilterItem;
 import org.eclipse.mdm.api.dflt.ApplicationContext;
 import org.eclipse.mdm.api.dflt.EntityManager;
-import org.eclipse.mdm.api.dflt.model.CatalogAttribute;
-import org.eclipse.mdm.api.dflt.model.CatalogComponent;
-import org.eclipse.mdm.api.dflt.model.TemplateAttribute;
-import org.eclipse.mdm.api.dflt.model.TemplateComponent;
-import org.eclipse.mdm.api.dflt.model.TemplateRoot;
-import org.eclipse.mdm.api.dflt.model.ValueList;
+import org.eclipse.mdm.api.dflt.model.*;
 import org.eclipse.mdm.businessobjects.control.FilterParser;
 import org.eclipse.mdm.businessobjects.entity.I18NResponse;
 import org.eclipse.mdm.businessobjects.entity.MDMEntityResponse;
@@ -346,4 +341,5 @@
 			return (Core) getMetod.invoke(e);
 		}).get();
 	}
+
 }