blob: 3eaf779caa1859a275aec652933d500884b49e47 [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2020 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.service;
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;
import java.util.stream.Collectors;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.inject.Inject;
import org.eclipse.mdm.api.base.Transaction;
import org.eclipse.mdm.api.base.model.ContextComponent;
import org.eclipse.mdm.api.base.model.ContextDescribable;
import org.eclipse.mdm.api.base.model.ContextRoot;
import org.eclipse.mdm.api.base.model.ContextType;
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.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;
import org.eclipse.mdm.connector.boundary.ConnectorService;
import com.google.common.base.Strings;
import io.vavr.Lazy;
import io.vavr.Value;
import io.vavr.collection.HashMap;
import io.vavr.collection.List;
import io.vavr.collection.Map;
import io.vavr.control.Try;
/**
* Class providing basic data access methods to contexts.
*
* @author Johannes Stamm, PeakSolution GmbH Nuernberg
*
*/
@Stateless
public class ContextService {
@Inject
private ConnectorService connectorService;
@EJB
private EntityService entityService;
/**
* 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 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
* @return the ordered and measured context data as context object for the
* identified {@link TestStep}
*/
public Try<Map<String, Map<ContextType, ContextRoot>>> getTestStepContext(Value<String> sourceName,
Value<String> testStepId, ContextType... contextTypes) {
return getTestStepContext(sourceName, entityService.find(sourceName, TestStep.class, testStepId), 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 testStep {@link Try} of the {@link TestStep}
* @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>>> getTestStepContext(Value<String> sourceName,
Try<TestStep> testStep, ContextType... contextTypes) {
Try<Map<ContextType, ContextRoot>> contextOrdered = getEntityManager(sourceName)
.map(e -> HashMap.ofAll(e.loadContexts(testStep.get(), contextTypes)));
Try<Map<ContextType, ContextRoot>> contextMeasured = getEntityManager(sourceName).map(
e -> HashMap.ofAll(e.loadContexts(findMeasurements(sourceName, testStep).get().get(), contextTypes)));
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 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
* {@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 measurementId the id of {@link Measurement} context is looked up for
* @param contextTypes list of {@link ContextType}s
* @return the ordered and measured context data as context object for the
* identified {@link Measurement}
*/
public Try<Map<String, Map<ContextType, ContextRoot>>> getMeasurementContext(Value<String> sourceName,
Value<String> measurementId, ContextType... contextTypes) {
return getMeasurementContext(sourceName, entityService.find(sourceName, Measurement.class, measurementId),
contextTypes);
}
/**
* Vavr conform version of contextActivity getTestStepContext function.
*
* returns the ordered and measurement context for a {@link Measurement}. 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 measurement {@link Try} of the {@link Measurement}
* @param contextTypes list of {@link ContextType}s
* @return the ordered and measured context data as context object for the
* identified {@link Measurement}
*/
private Try<Map<String, Map<ContextType, ContextRoot>>> getMeasurementContext(Value<String> sourceName,
Try<Measurement> measurement, ContextType... contextTypes) {
Try<Map<ContextType, ContextRoot>> contextOrdered = getEntityManager(sourceName)
.map(e -> HashMap.ofAll(e.loadContexts(findTestStep(sourceName, measurement).get(), contextTypes)));
Try<Map<ContextType, ContextRoot>> contextMeasured = getEntityManager(sourceName)
.map(e -> HashMap.ofAll(e.loadContexts(measurement.get(), contextTypes)));
return Try.of(() -> Lazy.of(() -> HashMap.of(CONTEXT_GROUP_ORDERED, contextOrdered.get(),
CONTEXT_GROUP_MEASURED, contextMeasured.get())).get());
}
/**
*
* @param sourceName
* @return
*/
private Try<EntityManager> getEntityManager(Value<String> sourceName) {
return Try.of(() -> Lazy.of(() -> connectorService.getContextByName(sourceName.get()).getEntityManager()
.orElseThrow(() -> new MDMEntityAccessException("Entity manager not present!"))).get());
}
/**
*
* @param sourceName
* @param testStep
* @return
*/
private Try<List<Measurement>> findMeasurements(Value<String> sourceName, Try<TestStep> testStep) {
return getEntityManager(sourceName).map(
e -> Lazy.of(() -> List.ofAll(e.loadChildren(testStep.get(), TestStep.CHILD_TYPE_MEASUREMENT))).get());
}
/**
*
* @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());
}
/**
* Updates the context of the given ContextDescribable.
*
* @param body update data
* @param contextDescribable ContextDescribable to update
* @param contextTypes contextTypes which whould be updated. Empty means
* all contextTypes.
* @return
*/
public DescribableContexts updateContext(String body, ContextDescribable contextDescribable,
ContextType... contextTypes) {
// extract request body
RequestBody rqBody = RequestBody.create(body);
Try<Map<String, Object>> contextMap = rqBody.getValueMapSupplier()
.map(dataMap -> dataMap.get("data")
.getOrElseThrow(() -> new MDMEntityAccessException("Missing attribute 'data'")))
.map(this::transformList).map(List::get).map(this::transformMap);
DescribableContexts ec = updateContext(getTestStep(contextDescribable), contextMap,
getContextTypes(contextTypes));
persist(ec);
return ec;
}
private DescribableContexts updateContext(TestStep testStep, Try<Map<String, Object>> contextMap,
ContextType[] contextTypes) {
Map<ContextType, Object> orderedMap = contextMap.get().get(ContextActivity.CONTEXT_GROUP_ORDERED)
.map(this::transformMap).getOrElse(HashMap.empty())
.mapKeys(t -> ServiceUtils.getContextTypeSupplier(t).get())
.filterKeys(t -> Arrays.asList(contextTypes).contains(t));
DescribableContexts ec = new DescribableContexts();
ec.setTestStep(testStep);
ec.setOrdered(updateContextDescribableContext(testStep, orderedMap, contextTypes));
Map<ContextType, Object> measuredMap = contextMap.get().get(ContextActivity.CONTEXT_GROUP_MEASURED)
.map(this::transformMap).getOrElse(HashMap.empty())
.mapKeys(t -> ServiceUtils.getContextTypeSupplier(t).get())
.filterKeys(t -> Arrays.asList(contextTypes).contains(t));
ec.setMeasurements(getEntityManager(testStep.getSourceName()).loadChildren(testStep, Measurement.class));
ec.getAnyMeasurement().map(m -> updateContextDescribableContext(m, measuredMap, contextTypes))
.ifPresent(ec::setMeasured);
return ec;
}
private java.util.Map<ContextType, ContextRoot> updateContextDescribableContext(
ContextDescribable contextDescribable, Map<ContextType, Object> rootMap, ContextType[] contextTypes) {
java.util.Map<ContextType, ContextRoot> existingRootMap = getEntityManager(contextDescribable.getSourceName())
.loadContexts(contextDescribable, contextTypes);
for (ContextType contextType : contextTypes) {
ContextRoot existingRoot = existingRootMap.computeIfAbsent(contextType,
ct -> createContextRoot(contextDescribable, ct));
rootMap.get(contextType).forEach(newContextRoot -> updateContextRoot(existingRoot, newContextRoot));
}
return existingRootMap;
}
private ContextRoot createContextRoot(ContextDescribable contextDescribable, ContextType contextType) {
EntityFactory factory = getEntityFactory(contextDescribable.getSourceName());
TemplateRoot templateRoot = findTemplateRoot(contextDescribable, contextType);
if (contextDescribable instanceof Measurement) {
return factory.createContextRoot((Measurement) contextDescribable, templateRoot);
} else if (contextDescribable instanceof TestStep) {
return factory.createContextRoot((TestStep) contextDescribable, templateRoot);
} else {
throw new MDMEntityAccessException("Only entities TestStep and Measurement are supported!");
}
}
private void updateContextRoot(ContextRoot existingRoot, Object newContextRoot) {
transformList(newContextRoot).map(this::transformMap).map(this::transformToMDMEntity).forEach(mdmEntity -> {
ContextComponent comp = existingRoot.getContextComponent(mdmEntity.getName())
.orElseGet(() -> getEntityFactory(existingRoot.getSourceName())
.createContextComponent(mdmEntity.getName(), existingRoot));
updateContextComponent(comp, mdmEntity.getAttributes());
});
}
private void updateContextComponent(ContextComponent contextComponent,
java.util.List<MDMContextAttribute> nameValues) {
nameValues.forEach(attribute -> Serializer.applyValue(contextComponent.getValue(attribute.getName()),
attribute.getValue()));
}
private TemplateRoot findTemplateRoot(ContextDescribable contextDescribable, ContextType contextType) {
TestStep testStep;
if (contextDescribable instanceof Measurement) {
testStep = getTestStep((Measurement) contextDescribable);
} else if (contextDescribable instanceof TestStep) {
testStep = (TestStep) contextDescribable;
} else {
throw new MDMEntityAccessException("Only entities TestStep and Measurement are supported!");
}
TemplateTestStep tpl = TemplateTestStep.of(testStep).orElseThrow(
() -> new MDMEntityAccessException("Cannot find TemplateTestStep for TestStep: " + testStep));
return tpl.getTemplateRoot(contextType).orElseThrow(() -> new MDMEntityAccessException(
"Cannot find TemplateRoot for ContextType " + contextType + " on Template TestStep " + tpl));
}
public TestStep getTestStep(ContextDescribable contextDescribable) {
if (contextDescribable instanceof TestStep) {
return (TestStep) contextDescribable;
} else {
return getTestStep((Measurement) contextDescribable);
}
}
private TestStep getTestStep(Measurement measurement) {
return getEntityManager(measurement.getSourceName()).loadParent(measurement, TestStep.class).orElseThrow(
() -> new MDMEntityAccessException("Cannot find parent TestStep of Measurement: " + measurement));
}
private EntityManager getEntityManager(String sourceName) {
return connectorService.getContextByName(sourceName).getEntityManager()
.orElseThrow(() -> new MDMEntityAccessException("Entity manager not present!"));
}
private EntityFactory getEntityFactory(String sourceName) {
return connectorService.getContextByName(sourceName).getEntityFactory()
.orElseThrow(() -> new MDMEntityAccessException("Entity factory not present!"));
}
private ContextType[] getContextTypes(ContextType[] types) {
if (types.length == 0) {
return ContextType.values();
} else {
return types;
}
}
public void persist(DescribableContexts ec) {
Transaction t = null;
try {
// start transaction to persist ValueList
t = getEntityManager(ec.getTestStep().getSourceName()).startTransaction();
java.util.Map<Boolean, java.util.List<Entity>> map = ec.getEntities().stream()
.collect(Collectors.groupingBy(this::isPersisted));
t.create(map.getOrDefault(Boolean.FALSE, Collections.emptyList()));
t.update(map.getOrDefault(Boolean.TRUE, Collections.emptyList()));
// commit the transaction
t.commit();
} catch (Exception e) {
if (t != null) {
t.abort();
}
throw new MDMEntityAccessException(e.getMessage(), e);
}
}
private boolean isPersisted(Entity e) {
return !Strings.isNullOrEmpty(e.getID()) && !"0".equals(e.getID());
}
@SuppressWarnings("unchecked")
private Map<String, Object> transformMap(Object obj) {
if (obj instanceof java.util.Map) {
return HashMap.ofAll(java.util.Map.class.cast(obj));
} else {
throw new MDMEntityAccessException(String.format("Expected instance of '%s', but got '%s'",
java.util.Map.class.getName(), obj.getClass().getName()));
}
}
/**
* Casts an {@link Object} holding a {@link java.util.List<Object>} to a
* {@link io.vavr.collection.List<Object>}
*
* @param obj the {@link Object} to cast
* @return the {@link io.vavr.collection.List<Object>}
*/
@SuppressWarnings("unchecked")
public List<Object> transformList(Object obj) {
if (obj instanceof java.util.List) {
return List.ofAll(java.util.List.class.cast(obj));
} else {
throw new MDMEntityAccessException(String.format("Expected instance of '%s', but got '%s'",
java.util.List.class.getName(), obj.getClass().getName()));
}
}
public 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(""),
component.get("type").map(Object::toString).getOrElse(""),
component.get("sourceType").map(Object::toString).getOrElse(""),
component.get("sourceName").map(Object::toString).getOrElse(""),
transformList(component.get("attributes").getOrElseThrow(
() -> new MDMEntityAccessException("Missing attribute 'attributes' in MDMEntity")))
.map(this::transformMap)
.map(m -> new MDMContextAttribute(
m.get("name").map(Object::toString)
.getOrElseThrow(() -> new MDMEntityAccessException(
"Missing attribute 'name' in MDMAttribute")),
m.get("value").map(Object::toString)
.getOrElseThrow(() -> new MDMEntityAccessException(
"Missing attribute 'value' in MDMAttribute")),
m.get("unit").map(Object::toString).getOrElse(""),
m.get("datatype").map(Object::toString).getOrElse(""), null, null, null, null))
.toJavaList());
}
}