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