Bug 561943 - Nodeprovider: Features Prio 1
implemented getTreePath
refactoring & documentation
removed environment filter
Change-Id: I6a037474cb709d4b160c8258aa4a3203a75110fa
Signed-off-by: Matthias Koller <m.koller@peak-solution.de>
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSQuery.java
index 3c51891..c0cab0d 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSQuery.java
@@ -258,8 +258,9 @@
private void logQuery() {
LOGGER.debug("Executing query with selects: {}, joins: {}.",
anuSeq.stream().map(a -> getName(a.attr.aid) + "." + a.attr.aaName).collect(Collectors.joining(",")),
- joinSeq.stream().map(j -> joinType(j.joiningType) + ": " + getName(j.fromAID) + "-" + j.refName + "->"
- + getName(j.toAID)).collect(Collectors.joining(",")));
+ joinSeq.stream().map(
+ j -> getName(j.fromAID) + "-[" + joinType(j.joiningType) + j.refName + "]->" + getName(j.toAID))
+ .collect(Collectors.joining(", ")));
}
private String getName(T_LONGLONG aid) {
@@ -271,12 +272,8 @@
}
}
- private EntityType lookupEntityType(T_LONGLONG aid) {
- return entityTypesByID.get("" + ODSConverter.fromODSLong(aid));
- }
-
private String joinType(org.asam.ods.JoinType joinType) {
- return (joinType == org.asam.ods.JoinType.JTDEFAULT ? "default" : "outer");
+ return (joinType == org.asam.ods.JoinType.JTDEFAULT ? "" : "outer: ");
}
/**
diff --git a/nucleus/businessobjects/build.gradle b/nucleus/businessobjects/build.gradle
index 35415a3..48c2414 100644
--- a/nucleus/businessobjects/build.gradle
+++ b/nucleus/businessobjects/build.gradle
@@ -36,6 +36,7 @@
testCompile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.23.2'
testImplementation project(":api:odsadapter")
+ testImplementation project(":api:atfxadapter")
testImplementation "com.google.code.gson:gson:2.7"
antlr "org.antlr:antlr4:4.5.3"
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java
index 5640e07..ebc2098 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java
@@ -36,6 +36,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
@@ -61,13 +68,21 @@
}
@GET
+ @Operation(summary = "Load nodeprovider IDs", description = "Get list of available nodeprovider IDs", responses = {
+ @ApiResponse(description = "The IDs of the available nodeproviders", content = {
+ @Content(array = @ArraySchema(schema = @Schema(implementation = String.class))) }),
+ @ApiResponse(responseCode = "400", description = "Error") })
public Response getNodeProviders() {
- return Response.ok(this.nodeProviderService.getNodeProviderNames()).build();
+ return Response.ok(this.nodeProviderService.getNodeProviderIDs()).build();
}
@GET
@Path("{NODEPROVIDER}")
- public Response getRoots(@PathParam("NODEPROVIDER") String nodeProviderId) {
+ @Operation(summary = "Load root nodes", description = "Get list of root nodes of a nodeprovider", responses = {
+ @ApiResponse(description = "The nodes at root level", content = @Content(schema = @Schema(implementation = NodeProviderResponse.class))),
+ @ApiResponse(responseCode = "400", description = "Error") })
+ public Response getRoots(
+ @Parameter(description = "ID of the nodeprovider", required = true) @PathParam("NODEPROVIDER") String nodeProviderId) {
try {
java.util.List<Node> nodes = this.nodeProviderService.getNodeProvider(nodeProviderId).getRoots();
return Response.status(Status.OK).entity(NodeProviderResponse.newBuilder().addAllData(nodes).build())
@@ -81,8 +96,12 @@
@GET
@Path("{NODEPROVIDER}/{PARENT_ID}")
- public Response getChildren(@PathParam("NODEPROVIDER") String nodeProviderId,
- @PathParam("PARENT_ID") String parentId) {
+ @Operation(summary = "Load children", description = "Get children of given node for a nodeprovider", responses = {
+ @ApiResponse(description = "The requested child nodes", content = @Content(schema = @Schema(implementation = NodeProviderResponse.class))),
+ @ApiResponse(responseCode = "400", description = "Error") })
+ public Response getChildren(
+ @Parameter(description = "ID of the nodeprovider", required = true) @PathParam("NODEPROVIDER") String nodeProviderId,
+ @Parameter(description = "serial of the parent node", required = true) @PathParam("PARENT_ID") String parentId) {
try {
java.util.List<Node> nodes = this.nodeProviderService.getNodeProvider(nodeProviderId)
.getChildren(SerializationUtil.deserializeNode(parentId));
@@ -98,7 +117,12 @@
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Path("{NODEPROVIDER}")
- public Response getTreePath(@PathParam("NODEPROVIDER") String nodeProviderId, Node node) {
+ @Operation(summary = "Load tree path", description = "Get a list all parent nodes including the current node", responses = {
+ @ApiResponse(description = "List of all parent nodes including the given node", content = @Content(schema = @Schema(implementation = NodeProviderResponse.class))),
+ @ApiResponse(responseCode = "400", description = "Error") })
+ public Response getTreePath(
+ @Parameter(description = "ID of the nodeprovider", required = true) @PathParam("NODEPROVIDER") String nodeProviderId,
+ @RequestBody(description = "Node that is the child of the returned TreePath.") Node node) {
try {
java.util.List<Node> nodes = this.nodeProviderService.getNodeProvider(nodeProviderId).getTreePath(node);
return Response.status(Status.OK).entity(NodeProviderResponse.newBuilder().addAllData(nodes).build())
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java
index 8fe65c8..ccc91a2 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java
@@ -47,16 +47,24 @@
this.nodeProviders = nodeProviders;
}
- public List<String> getNodeProviderNames() {
+ /**
+ * @return list of nodeprovider IDs
+ */
+ public List<String> getNodeProviderIDs() {
List<String> names = new ArrayList<>(nodeProviders.getNodeProviders().keySet());
Collections.sort(names);
return names;
}
- public NodeProvider getNodeProvider(String name) {
- NodeProvider nodeProvider = nodeProviders.getNodeProviders().get(name);
+ /**
+ * @param nodeproviderId ID of the requested {@link NodeProvider}
+ * @return NodeProvider with requested ID
+ * @throws NodeProviderException if the requested ID does not exist.
+ */
+ public NodeProvider getNodeProvider(String nodeproviderId) {
+ NodeProvider nodeProvider = nodeProviders.getNodeProviders().get(nodeproviderId);
if (nodeProvider == null) {
- throw new NodeProviderException("NodeProvider with name " + name + " does not exist!");
+ throw new NodeProviderException("NodeProvider with name " + nodeproviderId + " does not exist!");
}
return nodeProvider;
}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
index 9b88702..7062bfd 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
@@ -160,7 +160,7 @@
nodes.add(convertNode(current.get()));
current = getParent(current.get());
}
-
+ Collections.reverse(nodes);
return nodes;
}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
index 71050a5..75ad230 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
@@ -5,8 +5,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.stream.Collectors;
import org.eclipse.mdm.api.base.ServiceNotProvidedException;
import org.eclipse.mdm.api.base.adapter.Attribute;
@@ -32,7 +31,6 @@
import org.eclipse.mdm.api.base.search.ContextState;
import org.eclipse.mdm.api.base.search.SearchService;
import org.eclipse.mdm.api.dflt.ApplicationContext;
-import org.eclipse.mdm.api.dflt.EntityManager;
import org.eclipse.mdm.api.dflt.model.Pool;
import org.eclipse.mdm.api.dflt.model.Project;
import org.eclipse.mdm.businessobjects.control.FilterParser;
@@ -46,6 +44,8 @@
import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;
import org.eclipse.mdm.protobuf.Mdm.Node;
+import com.google.common.base.Joiner;
+
/**
* Configurable implementation of a {@link NodeProvider}. A
* {@link NodeProviderRoot} must be provided, which defines the structure of the
@@ -94,9 +94,12 @@
List<Node> nodes = new ArrayList<>();
for (ApplicationContext context : this.connectorService.getContexts()) {
- getChildNodeLevel(context, parent).map(nl -> getNodes(context, parent, nl)).ifPresent(nodes::addAll);
+ root.getChildNodeLevel(context, parent).map(nl -> loadChildNodes(context, parent, nl))
+ .ifPresent(nodes::addAll);
}
+ // Sort the complete list again, as only Nodes from each context are sorted.
+ // TODO 30.11.2020: sort by defined OrderAttributes
nodes.sort(compareByLabel);
return nodes;
@@ -107,66 +110,191 @@
*/
@Override
public List<Node> getTreePath(Node node) {
- // TODO Auto-generated method stub
- return null;
- }
+ ApplicationContext context = this.connectorService.getContextByName(node.getSource());
- private List<Node> getNodes(ApplicationContext context, Node parent, NodeLevel children) {
- String sourceName = getSourceName(context);
-
- Filter filter = getFilter(context, parent);
-
- if (Environment.class.getSimpleName().equals(children.getEntityType().getName())) {
-
- QueryService queryService = context.getQueryService().get();
- Query query = queryService.createQuery().select(children.getIdAttribute(), children.getLabelAttribute())
- .group(children.getIdAttribute(), children.getLabelAttribute());
-
- children.getOrderAttributes().stream()
- .forEach(oa -> query.order(oa.getAttribute(), oa.getOrder() == Order.ASCENDING));
-
- List<Node> nodes = new ArrayList<>();
-
- for (Result r : query.fetch()) {
- nodes.add(convertNode(r, sourceName, children, filter));
- }
-
- return nodes;
- }
-
- if (!sourceName.equalsIgnoreCase(parent.getSource())) {
- return Collections.emptyList();
- }
-
- if (!sourceNameMatches(filter, sourceName)) {
- return Collections.emptyList();
- }
-
- SearchService search = context.getSearchService().get();
+ NodeLevel nodeLevel = root.getNodeLevel(context, node.getType(), node.getIdAttribute());
List<Node> nodes = new ArrayList<>();
- if (children.isVirtual()) {
- // We use the TestStep entity as query root to request the values for the label
- List<Value> values = search.getFilterValues(TestStep.class, children.getLabelAttribute());
+ // reload node to get correct label
- // Filter values are always in measured
- for (Value v : values) {
- nodes.add(convertNode(v, sourceName, children, filter));
- }
- } else {
- Class<? extends Entity> entityClass = getEntityClass(children.getIdAttribute().getEntityType());
+ Query query = createQueryForNodeLevel(context, nodeLevel);
- List<Result> results = search.fetchResults(entityClass,
- Arrays.asList(children.getIdAttribute(), children.getLabelAttribute()), filter, "");
-
- for (Result r : results) {
- nodes.add(convertNode(r, sourceName, children, filter));
- }
+ for (Result r : query.fetch(createFilterFromIdOrLabel(node, nodeLevel))) {
+ nodes.add(convertNode(r, node.getSource(), nodeLevel, Filter.and()));
}
- return nodes;
+
+ if (nodes.size() != 1) {
+ throw new NodeProviderException("Expected exactly one node, but got " + nodes.size() + "!");
+ }
+
+ Node parent = nodes.get(0);
+
+ while ((parent = loadParentNode(context, parent, nodeLevel)) != null) {
+ nodes.add(parent);
+ nodeLevel = root.getNodeLevel(context, parent.getType(), parent.getIdAttribute());
+ }
+
+ return reverseListAndFilters(nodes);
}
+ /**
+ * Loads the child node of the given parent Node and NodeLevel.
+ *
+ * @param context ApplicationContext
+ * @param parent parent node
+ * @param childNodeLevel nodeLevel of the child nodes
+ * @return the children of the given node
+ */
+ private List<Node> loadChildNodes(ApplicationContext context, Node parent, NodeLevel childNodeLevel) {
+ String sourceName = context.getSourceName();
+
+ Filter filter = getFilter(context, parent);
+
+ if (isEnvironment(childNodeLevel.getEntityType())) {
+ return createQueryForNodeLevel(context, childNodeLevel).fetch().stream()
+ .map(r -> convertNode(r, sourceName, childNodeLevel, filter)).collect(Collectors.toList());
+ }
+
+ if (!sourceName.equalsIgnoreCase(parent.getSource()) || !sourceNameMatches(filter, sourceName)) {
+ return Collections.emptyList();
+ }
+
+ if (childNodeLevel.isVirtual()) {
+ // We use the TestStep entity as query root to request the values for the label
+ List<Value> values = getSearchService(context).getFilterValues(TestStep.class,
+ childNodeLevel.getLabelAttribute());
+
+ // Filter values are always in measured
+ List<Node> nodes = new ArrayList<>();
+ for (Value v : values) {
+ nodes.add(convertNode(v, sourceName, childNodeLevel, filter));
+ }
+ return nodes;
+ } else {
+ return fetchNodes(context, childNodeLevel, filter);
+ }
+ }
+
+ /**
+ * Loads the parent node of the given Node and NodeLevel.
+ *
+ * @param context
+ * @param node
+ * @param nodeLevel
+ * @return the parent node of the given node, or null if the given node is a
+ * root node.
+ */
+ protected Node loadParentNode(ApplicationContext context, Node node, NodeLevel nodeLevel) {
+ NodeLevel parentLevel = root.getParentNodeLevel(context, nodeLevel);
+
+ if (parentLevel == null) {
+ return null;
+ }
+
+ Filter childFilter = getFilter(context, node);
+ if (childFilter.isEmtpty()) {
+ throw new NodeProviderException("Filter cannot be empty! Received node '" + node + "' with empty filter.");
+ }
+
+ if (isEnvironment(parentLevel.getEntityType())) {
+ Query query = createQueryForNodeLevel(context, parentLevel);
+
+ for (Result r : query.fetch()) {
+ return convertNode(r, context.getSourceName(), parentLevel, childFilter);
+ }
+ return null;
+ }
+
+ List<Node> nodes = new ArrayList<>();
+ if (parentLevel.isVirtual()) {
+ // We use the TestStep entity as query root to request the values for the label
+ List<Result> results = getSearchService(context).fetchResults(TestStep.class,
+ Arrays.asList(parentLevel.getLabelAttribute()), childFilter, "");
+
+ for (Result r : results) {
+ Value value = r.getValue(parentLevel.getLabelAttribute());
+ nodes.add(convertNode(value, node.getSource(), parentLevel, childFilter));
+ break;
+ }
+ } else {
+ nodes.addAll(fetchNodes(context, parentLevel, childFilter));
+ }
+
+ if (nodes.size() != 1) {
+ throw new NodeProviderException("Expected exactly one node but got " + nodes.size() + "!");
+ }
+ return nodes.get(0);
+ }
+
+ /**
+ * Creates an ID filter for non virtual nodes or a filter based on the label for
+ * virtual nodes.
+ *
+ * @param node Node to build the Filter for
+ * @param nodeLevel NodeLevel of the Node
+ * @return created Filter
+ */
+ private Filter createFilterFromIdOrLabel(Node node, NodeLevel nodeLevel) {
+ if (nodeLevel.isVirtual()) {
+ return Filter.and().add(ComparisonOperator.EQUAL.create(nodeLevel.getContextState(),
+ nodeLevel.getLabelAttribute(), node.getLabel()));
+ } else {
+ return Filter.and().id(nodeLevel.getEntityType(), node.getId());
+ }
+ }
+
+ /**
+ * Reverse the Nodes and build up the filters of the individual Node from root
+ * to leaf.
+ *
+ * @param treePath tree path build from leaf to root.
+ * @return list of Nodes build from root to leaf, e.g. the root will be the
+ * first Node in the returned list and the leaf will be the last Node.
+ */
+ private List<Node> reverseListAndFilters(List<Node> treePath) {
+ List<Node> finalList = new ArrayList<>();
+ List<String> filters = new ArrayList<>();
+ for (int i = treePath.size() - 1; i >= 0; i--) {
+ String current = treePath.get(i).getFilter();
+ if (i - 1 >= 0) {
+ String child = treePath.get(i - 1).getFilter();
+ if (!current.equals(child)) {
+ filters.add(current.replace(child + " and ", ""));
+ }
+ } else {
+ filters.add(current);
+ }
+ Node node = treePath.get(i);
+ finalList.add(SerializationUtil.createNode(node.getSource(), node.getType(), node.getId(),
+ node.getIdAttribute(), Joiner.on(" and ").join(filters), node.getLabel()));
+ }
+
+ return finalList;
+ }
+
+ /**
+ * @param context ApplicationContext
+ * @param nodeLevel nodeLevel to search for nodes
+ * @param filter filter used for the search
+ * @return list with the loaded nodes
+ */
+ private List<Node> fetchNodes(ApplicationContext context, NodeLevel nodeLevel, Filter filter) {
+
+ Class<? extends Entity> entityClass = getEntityClass(nodeLevel.getIdAttribute().getEntityType());
+
+ List<Result> results = getSearchService(context).fetchResults(entityClass,
+ Arrays.asList(nodeLevel.getIdAttribute(), nodeLevel.getLabelAttribute()), filter, "");
+
+ return results.stream().map(r -> convertNode(r, context.getSourceName(), nodeLevel, filter))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * @param entityType
+ * @return entity class of the given entityType
+ * @throws NodeProviderException if entity class is not found
+ */
private Class<? extends Entity> getEntityClass(EntityType entityType) {
String entityName = ServiceUtils.workaroundForTypeMapping(entityType);
@@ -208,7 +336,7 @@
for (FilterItem filterItem : filter) {
if (filterItem.isCondition()) {
Attribute a = filterItem.getCondition().getAttribute();
- if (Environment.class.getSimpleName().equals(a.getEntityType().getName()) && "Id".equals(a.getName())) {
+ if (isEnvironment(a.getEntityType()) && "Id".equals(a.getName())) {
return sourceName.equals(filterItem.getCondition().getValue().extract(ValueType.STRING));
}
} else if (filterItem.isBooleanOperator() && filterItem.getBooleanOperator() == BooleanOperator.AND) {
@@ -225,7 +353,7 @@
*
* @param context application context
* @param node
- * @return the filter of the node
+ * @return the filter of the node or an empty Filter
*/
private Filter getFilter(ApplicationContext context, Node node) {
@@ -242,52 +370,28 @@
return filter;
}
- private Optional<NodeLevel> getChildNodeLevel(ApplicationContext context, Node parent) {
- NodeLevel nodeLevel = null;
-
- if (parent == null) {
- nodeLevel = getNodeLevel(context, Environment.class.getSimpleName(), null);
- } else {
- nodeLevel = getNodeLevel(context, parent.getType(), parent.getIdAttribute());
- if (nodeLevel == null) {
- return Optional.empty();
- }
- nodeLevel = nodeLevel.getChild();
- }
-
- return Optional.ofNullable(nodeLevel);
- }
-
- private NodeLevel getNodeLevel(ApplicationContext context, String type, String idAttribute) {
- NodeLevel n = root.getNodeLevel(getSourceName(context));
-
- while (n != null && !nodeLevelMatches(n, type, idAttribute)) {
- n = n.getChild();
- }
-
- return n;
- }
-
- private boolean nodeLevelMatches(NodeLevel nodeLevel, String type, String idAttribute) {
- return ServiceUtils.workaroundForTypeMapping(type)
- .equals(ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()))
- && (idAttribute == null || idAttribute.equals(nodeLevel.getIdAttribute().getName()));
- }
-
+ /**
+ * Converts a Result to a Node
+ *
+ * @param r Result to convert
+ * @param sourceName name of the source
+ * @param nodeLevel NodeLevel
+ * @param parentFilter Filter of the parent Node
+ * @return Result converted to a Node
+ */
private Node convertNode(Result r, String sourceName, NodeLevel nodeLevel, Filter parentFilter) {
- Map<String, Value> values = r.getRecord(nodeLevel.getEntityType()).getValues();
-
- String label = values.get(nodeLevel.getLabelAttribute().getName()).extract(nodeLevel.getContextState());
- String id = values.get(nodeLevel.getIdAttribute().getName()).extract(nodeLevel.getContextState());
+ String label = r.getValue(nodeLevel.getLabelAttribute()).extract(nodeLevel.getContextState());
+ String id = r.getValue(nodeLevel.getIdAttribute()).extract(nodeLevel.getContextState());
Filter newFilter = null;
- if (Environment.class.getSimpleName().equals(nodeLevel.getEntityType().getName())) {
- // only 1 environment per context -> no parent filter needed
- newFilter = Filter.and();
- } else if (parentFilter != null) {
- newFilter = parentFilter.copy()
- .add(ComparisonOperator.EQUAL.create(nodeLevel.getContextState(), nodeLevel.getIdAttribute(), id));
+ if (parentFilter != null) {
+ if (isEnvironment(nodeLevel.getEntityType())) {
+ newFilter = parentFilter.copy();
+ } else {
+ newFilter = parentFilter.copy().add(
+ ComparisonOperator.EQUAL.create(nodeLevel.getContextState(), nodeLevel.getIdAttribute(), id));
+ }
}
return SerializationUtil.createNode(sourceName,
@@ -295,6 +399,15 @@
nodeLevel.getIdAttribute().getName(), newFilter, label);
}
+ /**
+ * Converts a Value to a Node.
+ *
+ * @param v value to convert
+ * @param sourceName name of the source
+ * @param nodeLevel NodeLevel
+ * @param parentFilter Filter of the parent Node
+ * @return Value converted to a Node
+ */
private Node convertNode(Value v, String sourceName, NodeLevel nodeLevel, Filter parentFilter) {
ContextState contextState = nodeLevel.getContextState() == null ? ContextState.MEASURED
@@ -304,7 +417,7 @@
Filter newFilter = null;
- if (Environment.class.getSimpleName().equals(nodeLevel.getEntityType().getName())) {
+ if (isEnvironment(nodeLevel.getEntityType())) {
// only 1 environment per context -> no parent filter needed
newFilter = Filter.and();
} else if (parentFilter != null) {
@@ -313,15 +426,48 @@
}
return SerializationUtil.createNode(sourceName,
- ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()), null,
+ ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()), label,
nodeLevel.getIdAttribute().getName(), newFilter, label);
}
- private String getSourceName(ApplicationContext context) {
- String sourceName = context.getEntityManager()
- .orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class)).loadEnvironment()
- .getSourceName();
- return sourceName;
+ /**
+ * Creates a Query to load nodes for the given NodeLevel
+ *
+ * @param context ApplicationContext in which the query is created
+ * @param nodeLevel the NodeLevel of the requested nodes.
+ * @return Query for the given NodeLevel
+ */
+ private Query createQueryForNodeLevel(ApplicationContext context, NodeLevel nodeLevel) {
+ Query query = context.getQueryService().orElseThrow(() -> new ServiceNotProvidedException(QueryService.class))
+ .createQuery().select(nodeLevel.getIdAttribute(), nodeLevel.getLabelAttribute())
+ .group(nodeLevel.getIdAttribute(), nodeLevel.getLabelAttribute());
+
+ nodeLevel.getOrderAttributes().stream()
+ .forEach(oa -> query.order(oa.getAttribute(), oa.getOrder() == Order.ASCENDING));
+
+ return query;
+ }
+
+ /**
+ * Returns the SearchService
+ *
+ * @param context ApplicationContext
+ * @return the SearchService
+ * @throws ServiceNotProvidedException if no SearchService is available from the
+ * context.
+ */
+ private SearchService getSearchService(ApplicationContext context) {
+ return context.getSearchService().orElseThrow(() -> new ServiceNotProvidedException(SearchService.class));
+ }
+
+ /**
+ * Checks if entity type is an Environment
+ *
+ * @param entityType entityType to check
+ * @return true, if entity type is an Environment
+ */
+ private boolean isEnvironment(EntityType entityType) {
+ return Environment.class.getSimpleName().equals(entityType.getName());
}
/**
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
index 7acce02..4fbd8ff 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
@@ -15,6 +15,7 @@
package org.eclipse.mdm.nodeprovider.control;
import java.io.Serializable;
+import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -23,12 +24,15 @@
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
+import org.eclipse.mdm.businessobjects.boundary.ChannelResource;
import org.eclipse.mdm.connector.boundary.ConnectorService;
import org.eclipse.mdm.nodeprovider.entity.NodeProvider;
import org.eclipse.mdm.nodeprovider.entity.NodeProviderRoot;
import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;
import org.eclipse.mdm.preferences.controller.PreferenceService;
import org.eclipse.mdm.preferences.entity.PreferenceMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Repository for NodeProviders in a specific session.
@@ -39,6 +43,8 @@
private static final long serialVersionUID = -3081666937067348673L;
+ private static final Logger LOG = LoggerFactory.getLogger(ChannelResource.class);
+
private Map<String, NodeProvider> nodeProviders = new HashMap<>();
@Inject
@@ -47,44 +53,75 @@
@Inject
private PreferenceService preferenceService;
+ @Inject
+ Principal principal;
+
+ /**
+ * Default constructor
+ */
public NodeProviderRepository() {
}
+ /**
+ * Constructor for unit tests.
+ *
+ * @param connectorService
+ * @param preferenceService
+ */
public NodeProviderRepository(ConnectorService connectorService, PreferenceService preferenceService) {
this.connectorService = connectorService;
this.preferenceService = preferenceService;
init();
}
+ /**
+ * Initializes the NodeProviderRepository
+ */
@PostConstruct
public void init() {
+ LOG.debug("Initializing NodeProviderRepository for user ", principal.getName());
- System.out.println("Init NodeProviderRepository");
DefaultNodeProvider defaultNP = new DefaultNodeProvider(connectorService);
putNodeProvider("default", defaultNP);
+ LOG.trace("Registered default nodeprovider.");
List<PreferenceMessage> msgs = preferenceService.getPreferences("system", "nodeprovider.");
for (PreferenceMessage msg : msgs) {
-
try {
NodeProviderRoot root = parsePreference(msg.getValue());
putNodeProvider(root.getId(), new GenericNodeProvider(connectorService, root));
-
+ LOG.trace("Registered generic nodeprovider '{}'.", root.getId());
} catch (RuntimeException e) {
e.printStackTrace();
+ LOG.warn("Could not deserialize nodeprovider configuration: {}", msg.getValue(), e);
}
}
}
+ /**
+ * Add a nodeprovider to the repository.
+ *
+ * @param id ID of the nodeprovider
+ * @param nodeProvider {@link NodeProvider}
+ */
public void putNodeProvider(String id, NodeProvider nodeProvider) {
nodeProviders.put(id, nodeProvider);
}
+ /**
+ * @return a map of all registered nodeproviders indexed by ID
+ */
public Map<String, NodeProvider> getNodeProviders() {
return nodeProviders;
}
+ /**
+ * Parse a JSON string representing a {@link NodeProviderRoot}
+ *
+ * @param json JSON string
+ * @return parsed {@link NodeProviderRoot}
+ */
private NodeProviderRoot parsePreference(String json) {
return SerializationUtil.deserializeNodeProviderRoot(connectorService, json);
}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
index f0065bf..3d89c19 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
@@ -2,8 +2,15 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.Optional;
+import org.eclipse.mdm.api.base.model.Environment;
+import org.eclipse.mdm.api.dflt.ApplicationContext;
+import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
import org.eclipse.mdm.nodeprovider.control.GenericNodeProvider;
+import org.eclipse.mdm.protobuf.Mdm.Node;
+
+import com.google.common.base.Strings;
/**
* A {@link NodeProviderRoot} defines the configuration of a
@@ -65,6 +72,13 @@
}
/**
+ * @param contexts the contexts to set
+ */
+ public void setContexts(Map<String, NodeLevel> contexts) {
+ this.contexts = contexts;
+ }
+
+ /**
* Get the {@link NodeLevel} for the given sourceName. If the given sourceName
* has no entry, the {@link NodeLevel} for the {@link NodeProviderRoot#WILDCARD}
* is returned.
@@ -84,10 +98,74 @@
}
/**
- * @param contexts the contexts to set
+ * Find the parent NodeLevel for a given nodeLevel.
+ *
+ * @param context
+ * @param nodeLevel
+ * @return parent NodeLevel
*/
- public void setContexts(Map<String, NodeLevel> contexts) {
- this.contexts = contexts;
+ public NodeLevel getParentNodeLevel(ApplicationContext context, NodeLevel nodeLevel) {
+ NodeLevel n = getNodeLevel(context.getSourceName());
+
+ if (n.equals(nodeLevel)) {
+ return null;
+ }
+ while (n.getChild() != null && !nodeLevel.equals(n.getChild())) {
+ n = n.getChild();
+ }
+
+ return n;
+ }
+
+ /**
+ * Find the child NodeLevel for a given nodeLevel.
+ *
+ * @param context
+ * @param nodeLevel
+ * @return child NodeLevel
+ */
+ public Optional<NodeLevel> getChildNodeLevel(ApplicationContext context, Node nodeLevel) {
+ NodeLevel childNodeLevel = null;
+
+ if (nodeLevel == null) {
+ childNodeLevel = getNodeLevel(context, Environment.class.getSimpleName(), null);
+ } else {
+ childNodeLevel = getNodeLevel(context, nodeLevel.getType(), nodeLevel.getIdAttribute());
+ if (childNodeLevel == null) {
+ return Optional.empty();
+ }
+ childNodeLevel = childNodeLevel.getChild();
+ }
+
+ return Optional.ofNullable(childNodeLevel);
+ }
+
+ /**
+ * @param context
+ * @param type
+ * @param idAttribute
+ * @return NodeLevel for given type and idAttribute
+ */
+ public NodeLevel getNodeLevel(ApplicationContext context, String type, String idAttribute) {
+ NodeLevel n = getNodeLevel(context.getSourceName());
+
+ while (n != null && !nodeLevelMatches(n, type, idAttribute)) {
+ n = n.getChild();
+ }
+
+ return n;
+ }
+
+ /**
+ * @param nodeLevel
+ * @param type
+ * @param idAttribute
+ * @return true, if the given type and idAttribute matches the nodeLevel
+ */
+ private boolean nodeLevelMatches(NodeLevel nodeLevel, String type, String idAttribute) {
+ return ServiceUtils.workaroundForTypeMapping(type)
+ .equals(ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()))
+ && (Strings.isNullOrEmpty(idAttribute) || idAttribute.equals(nodeLevel.getIdAttribute().getName()));
}
}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
index 85b3220..b85e2fa 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
@@ -3,9 +3,9 @@
/**
* Enum for defining an order.
*
+ * @see SortAttribute
*/
public enum Order {
- ASCENDING,
- DESCENDING;
+ ASCENDING, DESCENDING;
}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
index 3fc96af..b877cae 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
@@ -46,11 +46,33 @@
private static ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+ /**
+ * Create a node with the specified parameters and sets the serial.
+ *
+ * @param source source name
+ * @param type source type
+ * @param id ID of the Node
+ * @param idAttribute name of the ID attribute
+ * @param filter filter
+ * @param label Label of the Node
+ * @return {@link Node}
+ */
public static Node createNode(String source, String type, String id, String idAttribute, Filter filter,
String label) {
return createNode(source, type, id, idAttribute, FilterParser.toString(filter), label);
}
+ /**
+ * Create a node with the specified parameters and sets the serial.
+ *
+ * @param source source name
+ * @param type source type
+ * @param id ID of the Node
+ * @param idAttribute name of the ID attribute
+ * @param filter filter
+ * @param label Label of the Node
+ * @return {@link Node}
+ */
public static Node createNode(String source, String type, String id, String idAttribute, String filter,
String label) {
Node node = Node.newBuilder().setSource(source).setType(type).setId(id == null ? "" : id)
@@ -61,33 +83,26 @@
}
/**
- * Serializes a node to a base64 encoded JSON string
+ * Serializes a node to a base64 encoded protobuf {@link Node} object
*
* @param node
- * @return base64 encoded JSON string
+ * @return base64 encoded protobuf Node
*/
public static String serializeNode(Node node) {
return Base64.getUrlEncoder().encodeToString(node.toByteArray());
-// Mdm.Node.newBuilder().setSource(node.getSource()).setType(node.getType())
-// .setId(node.getId()).setLabel(node.getLabel()).setIdAttribute(node.getIdAttribute())
-// .setFilter(node.getFilter()).setLeaf(node.isLeaf()).build().toByteArray());
}
/**
- * Deserialize a base64 encoded JSON string into a {@link Node}.
+ * Deserialize a base64 encoded protobuf object into a {@link Node}.
*
- * @param base64Json
+ * @param base64Protobuf
* @return {@link Node}
*/
- public static Node deserializeNode(String base64Json) {
+ public static Node deserializeNode(String base64Protobuf) {
try {
- Mdm.Node node = Mdm.Node.parseFrom(Base64.getUrlDecoder().decode(base64Json.getBytes("UTF-8")));
- return node;
-// System.out.println(JsonFormat.printer().print(node));
-// return new Node(node.getSource(), node.getType(), node.getId(), node.getIdAttribute(), node.getFilter(),
-// node.getLabel());
+ return Mdm.Node.parseFrom(Base64.getUrlDecoder().decode(base64Protobuf.getBytes("UTF-8")));
} catch (InvalidProtocolBufferException | UnsupportedEncodingException e) {
- throw new NodeProviderException("Cannot deserialize Node: " + base64Json, e);
+ throw new NodeProviderException("Cannot deserialize Node: " + base64Protobuf, e);
}
}
diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java
index 2131ae3..feeabb9 100644
--- a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java
@@ -98,7 +98,7 @@
@Before
public void init() {
- when(nodeProviderService.getNodeProviderNames()).thenReturn(Collections.emptyList());
+ when(nodeProviderService.getNodeProviderIDs()).thenReturn(Collections.emptyList());
when(nodeProviderService.getNodeProvider("default")).thenReturn(nodeProvider);
rootNode = SerializationUtil.createNode("MDM", "Project", "1", "Id", Filter.and(), "Project1");
@@ -111,7 +111,7 @@
@Test
public void testGetNodeProviders() {
- when(nodeProviderService.getNodeProviderNames()).thenReturn(Arrays.asList("default", "generic"));
+ when(nodeProviderService.getNodeProviderIDs()).thenReturn(Arrays.asList("default", "generic"));
List<String> nodeProviderNames = target("nodeprovider").request().get(new GenericType<List<String>>() {
});
assertThat(nodeProviderNames).containsExactly("default", "generic");
diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java
index 3ac7588..dc1254f 100644
--- a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java
@@ -162,9 +162,9 @@
Attribute model = vehicle.getAttribute("model");
EntityType test = mm.getEntityType("Test");
- NodeProviderService repo = mockNodeProviderService();
+ NodeProviderService npService = mockNodeProviderService();
- NodeProvider nodeProvider = repo.getNodeProvider("generic");
+ NodeProvider nodeProvider = npService.getNodeProvider("generic");
List<Node> roots = nodeProvider.getRoots();
diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProviderTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProviderTest.java
new file mode 100644
index 0000000..6c50e63
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProviderTest.java
@@ -0,0 +1,357 @@
+package org.eclipse.mdm.nodeprovider.control;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.mdm.api.odsadapter.ODSContextFactory.PARAM_NAMESERVICE;
+import static org.eclipse.mdm.api.odsadapter.ODSContextFactory.PARAM_PASSWORD;
+import static org.eclipse.mdm.api.odsadapter.ODSContextFactory.PARAM_SERVICENAME;
+import static org.eclipse.mdm.api.odsadapter.ODSContextFactory.PARAM_USER;
+import static org.mockito.ArgumentMatchers.any;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.mdm.api.base.ConnectionException;
+import org.eclipse.mdm.api.base.adapter.EntityType;
+import org.eclipse.mdm.api.base.adapter.ModelManager;
+import org.eclipse.mdm.api.base.model.Channel;
+import org.eclipse.mdm.api.base.model.ChannelGroup;
+import org.eclipse.mdm.api.base.model.Environment;
+import org.eclipse.mdm.api.base.model.Measurement;
+import org.eclipse.mdm.api.base.model.TestStep;
+import org.eclipse.mdm.api.base.search.ContextState;
+import org.eclipse.mdm.api.dflt.ApplicationContext;
+import org.eclipse.mdm.api.dflt.model.Project;
+import org.eclipse.mdm.api.odsadapter.ODSContextFactory;
+import org.eclipse.mdm.connector.boundary.ConnectorService;
+import org.eclipse.mdm.nodeprovider.entity.NodeLevel;
+import org.eclipse.mdm.nodeprovider.entity.NodeProviderRoot;
+import org.eclipse.mdm.nodeprovider.entity.SortAttribute;
+import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;
+import org.eclipse.mdm.protobuf.Mdm.Node;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.google.common.collect.ImmutableMap;
+
+import junit.framework.AssertionFailedError;
+
+@Ignore
+//FIXME 25.11.2020: this test needs a running ODS Server, that is not suitable for continous build in Jenkins.
+//Comment this in for local tests only.
+public class GenericNodeProviderTest {
+ /*
+ * ATTENTION: ==========
+ *
+ * To run this test make sure the target service is running a MDM default model
+ * and any database constraint which enforces a relation of Test to a parent
+ * entity is deactivated!
+ */
+
+ private static final String NAME_SERVICE = "corbaloc::1.2@%s:%s/NameService";
+
+ private static final String USER = "sa";
+ private static final String PASSWORD = "sa";
+
+ private static ApplicationContext context;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws ConnectionException {
+ String nameServiceHost = System.getProperty("host");
+ String nameServicePort = System.getProperty("port");
+ String serviceName = System.getProperty("service");
+
+ if (nameServiceHost == null || nameServiceHost.isEmpty()) {
+ throw new IllegalArgumentException("name service host is unknown: define system property 'host'");
+ }
+
+ nameServicePort = nameServicePort == null || nameServicePort.isEmpty() ? String.valueOf(2809) : nameServicePort;
+ if (nameServicePort == null || nameServicePort.isEmpty()) {
+ throw new IllegalArgumentException("name service port is unknown: define system property 'port'");
+ }
+
+ if (serviceName == null || serviceName.isEmpty()) {
+ throw new IllegalArgumentException("service name is unknown: define system property 'service'");
+ }
+
+ Map<String, String> connectionParameters = new HashMap<>();
+ connectionParameters.put(PARAM_NAMESERVICE, String.format(NAME_SERVICE, nameServiceHost, nameServicePort));
+ connectionParameters.put(PARAM_SERVICENAME, serviceName + ".ASAM-ODS");
+ connectionParameters.put(PARAM_USER, USER);
+ connectionParameters.put(PARAM_PASSWORD, PASSWORD);
+
+ context = new ODSContextFactory().connect(connectionParameters);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws ConnectionException {
+ if (context != null) {
+ context.close();
+ }
+
+ }
+
+ private NodeProviderRoot getNodeProviderRoot() {
+ NodeLevel nl = createStructure(context, ContextState.MEASURED);
+ NodeProviderRoot npr = new NodeProviderRoot();
+ npr.setId("generic_measured");
+ npr.setName("Measured");
+ npr.setContexts(ImmutableMap.of("*", nl));
+
+ return npr;
+ }
+
+ private GenericNodeProvider getGenericNodeProvider(NodeProviderRoot npr) {
+ ConnectorService connectorService = Mockito.mock(ConnectorService.class);
+ Mockito.when(connectorService.getContexts()).thenReturn(Arrays.asList(context));
+ Mockito.when(connectorService.getContextByName(any())).thenReturn(context);
+
+ return new GenericNodeProvider(connectorService, npr);
+ }
+
+ @Test
+ public void testGetRoots() {
+ GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());
+
+ assertThat(np.getRoots())
+ .containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"));
+ }
+
+ @Test
+ public void testGetChildren() {
+ GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());
+
+ List<Node> roots = np.getRoots();
+
+ List<Node> projects = np.getChildren(roots.get(0));
+ assertThat(projects).containsExactly(
+ SerializationUtil.createNode("NVHDEMO", "Project", "5", "Id", "Project.Id eq \"5\"", "PMV 2PV"),
+ SerializationUtil.createNode("NVHDEMO", "Project", "4", "Id", "Project.Id eq \"4\"", "PMV Model P"),
+ SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"", "PMV Summit"));
+
+ List<Node> vehicleTypes = np.getChildren(findNodeWithLabel(projects, "PMV Summit"));
+ assertThat(vehicleTypes).containsExactly(
+ SerializationUtil.createNode("NVHDEMO", "vehicle", "Model P", "model",
+ "Project.Id eq \"3\" and vehicle.model eq \"Model P\"", "Model P"),
+ SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\"", "Summit"));
+
+ List<Node> tests = np.getChildren(findNodeWithLabel(vehicleTypes, "Summit"));
+ assertThat(tests).containsExactly(SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\"",
+ "PBN_UNECE_R51_20140213130501"));
+
+ List<Node> testSteps = np.getChildren(findNodeWithLabel(tests, "PBN_UNECE_R51_20140213130501"));
+ assertThat(testSteps).containsExactly(SerializationUtil.createNode("NVHDEMO", "TestStep", "11", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\"",
+ "PBN_UNECE_R51_Left_Acc_50"),
+ SerializationUtil.createNode("NVHDEMO", "TestStep", "10", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"10\"",
+ "PBN_UNECE_R51_Left_Steady_50"),
+ SerializationUtil.createNode("NVHDEMO", "TestStep", "13", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"13\"",
+ "PBN_UNECE_R51_Right_Acc_50"),
+ SerializationUtil.createNode("NVHDEMO", "TestStep", "12", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"12\"",
+ "PBN_UNECE_R51_Right_Steady_50"));
+
+ List<Node> measurements = np.getChildren(findNodeWithLabel(testSteps, "PBN_UNECE_R51_Left_Acc_50"));
+ assertThat(measurements).containsExactly(SerializationUtil.createNode("NVHDEMO", "Measurement", "175", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"175\"",
+ "Channel"),
+ SerializationUtil.createNode("NVHDEMO", "Measurement", "176", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"176\"",
+ "Document"),
+ SerializationUtil.createNode("NVHDEMO", "Measurement", "177", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"177\"",
+ "Photo"));
+
+ List<Node> channelGroups = np.getChildren(findNodeWithLabel(measurements, "Channel"));
+ assertThat(channelGroups).containsExactly(SerializationUtil.createNode("NVHDEMO", "ChannelGroup", "2011", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"175\" and ChannelGroup.Id eq \"2011\"",
+ "Channel"));
+
+ List<Node> channels = np.getChildren(findNodeWithLabel(channelGroups, "Channel"));
+ assertThat(channels).hasSize(11).startsWith(SerializationUtil.createNode("NVHDEMO", "Channel", "8498", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"175\" and ChannelGroup.Id eq \"2011\" and Channel.Id eq \"8498\"",
+ "CHANNEL01"));
+ }
+
+ private Node findNodeWithLabel(List<Node> nodes, String label) {
+ return nodes.stream().filter(n -> n.getLabel().equals(label)).findFirst().orElseThrow(
+ () -> new AssertionFailedError("Node node with label " + label + " found in " + nodes + "."));
+ }
+
+ @Test
+ public void testGetParentNodeEnvironment() {
+ NodeProviderRoot npr = getNodeProviderRoot();
+ GenericNodeProvider np = getGenericNodeProvider(npr);
+
+ NodeLevel nlEnvironment = npr.getNodeLevel(context, "Environment", null);
+
+ assertThat(np.loadParentNode(context,
+ SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"), nlEnvironment))
+ .isNull();
+ }
+
+ @Test
+ public void testGetParentNodeProject() {
+ NodeProviderRoot npr = getNodeProviderRoot();
+ GenericNodeProvider np = getGenericNodeProvider(npr);
+
+ NodeLevel nlProject = npr.getNodeLevel(context, "Project", null);
+
+ assertThat(
+ np.loadParentNode(context,
+ SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"",
+ "PMV Summit"),
+ nlProject))
+ .isEqualTo(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id",
+ "Project.Id eq \"3\"", "MDM-NVH"));
+ }
+
+ @Test
+ public void testGetParentNodeVehicle() {
+ NodeProviderRoot npr = getNodeProviderRoot();
+ GenericNodeProvider np = getGenericNodeProvider(npr);
+
+ NodeLevel nlVehicle = npr.getNodeLevel(context, "vehicle", "model");
+
+ assertThat(np.loadParentNode(context,
+ SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model", "vehicle.model eq \"Summit\"",
+ "Summit"),
+ nlVehicle))
+ .isEqualTo(SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id",
+ "vehicle.model eq \"Summit\" and Project.Id eq \"3\"", "PMV Summit"));
+ }
+
+ @Test
+ public void testGetParentNodeTest() {
+ NodeProviderRoot npr = getNodeProviderRoot();
+ GenericNodeProvider np = getGenericNodeProvider(npr);
+
+ NodeLevel nlTest = npr.getNodeLevel(context, "Test", null);
+
+ assertThat(np.loadParentNode(context,
+ SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id", "Test.Id eq \"4\"", ""), nlTest))
+ .isEqualTo(SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",
+ "Test.Id eq \"4\" and vehicle.model eq \"Summit\"", "Summit"));
+ }
+
+ @Test
+ public void testGetParentNodeTestStep() {
+ NodeProviderRoot npr = getNodeProviderRoot();
+ GenericNodeProvider np = getGenericNodeProvider(npr);
+
+ NodeLevel nlTestStep = npr.getNodeLevel(context, "TestStep", null);
+
+ assertThat(np.loadParentNode(context,
+ SerializationUtil.createNode("NVHDEMO", "TestStep", "11", "Id", "TestStep.Id eq \"11\"", ""),
+ nlTestStep))
+ .isEqualTo(SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id",
+ "TestStep.Id eq \"11\" and Test.Id eq \"4\"", "PBN_UNECE_R51_20140213130501"));
+ }
+
+ @Test
+ public void testGetTreePathEnvironment() {
+ GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());
+ assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH")))
+ .containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"));
+ }
+
+ @Test
+ public void testGetTreePathProject() {
+ GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());
+ assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "", "PMV Summit")))
+ .containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"),
+ SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"",
+ "PMV Summit"));
+ }
+
+ @Test
+ public void testGetTreePathVehicle() {
+ GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());
+ assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model", "", "Summit")))
+ .containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"),
+ SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"",
+ "PMV Summit"),
+ SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\"", "Summit"));
+ }
+
+ @Test
+ public void testGetTreePathTest() {
+ GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());
+ assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "Test", "4", "", "", ""))).containsExactly(
+ SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"),
+ SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"", "PMV Summit"),
+ SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\"", "Summit"),
+ SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\"",
+ "PBN_UNECE_R51_20140213130501"));
+ }
+
+ @Test
+ public void testGetTreePathTestStep() {
+ GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());
+ assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "TestStep", "11", "Id", "", "Name")))
+ .containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"),
+ SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"",
+ "PMV Summit"),
+ SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\"", "Summit"),
+ SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\"",
+ "PBN_UNECE_R51_20140213130501"),
+ SerializationUtil.createNode("NVHDEMO", "TestStep", "11", "Id",
+ "Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\"",
+ "PBN_UNECE_R51_Left_Acc_50"));
+ }
+
+ private static NodeLevel createStructure(ApplicationContext context, ContextState contextState) {
+ ModelManager modelManager = context.getModelManager().get();
+
+ EntityType etEnv = modelManager.getEntityType(Environment.class);
+ NodeLevel env = new NodeLevel(etEnv);
+ env.getOrderAttributes().add(new SortAttribute(env.getLabelAttribute()));
+
+ NodeLevel project = new NodeLevel(modelManager.getEntityType(Project.class));
+ project.getOrderAttributes().add(new SortAttribute(project.getLabelAttribute()));
+
+ EntityType vehicle = modelManager.getEntityType("vehicle");
+ NodeLevel vehicleModel = new NodeLevel(vehicle, vehicle.getAttribute("model"), vehicle.getAttribute("model"));
+ vehicleModel.setVirtual(true);
+ vehicleModel.setContextState(contextState);
+ vehicleModel.getOrderAttributes().add(new SortAttribute(vehicleModel.getLabelAttribute()));
+
+ NodeLevel test = new NodeLevel(modelManager.getEntityType(org.eclipse.mdm.api.base.model.Test.class));
+ test.getOrderAttributes().add(new SortAttribute(test.getLabelAttribute()));
+
+ NodeLevel testStep = new NodeLevel(modelManager.getEntityType(TestStep.class));
+ testStep.getOrderAttributes().add(new SortAttribute(testStep.getLabelAttribute()));
+
+ NodeLevel measurement = new NodeLevel(modelManager.getEntityType(Measurement.class));
+ measurement.getOrderAttributes().add(new SortAttribute(measurement.getLabelAttribute()));
+
+ NodeLevel channelGroup = new NodeLevel(modelManager.getEntityType(ChannelGroup.class));
+ channelGroup.getOrderAttributes().add(new SortAttribute(channelGroup.getLabelAttribute()));
+
+ NodeLevel channel = new NodeLevel(modelManager.getEntityType(Channel.class));
+ channel.getOrderAttributes().add(new SortAttribute(channel.getLabelAttribute()));
+
+ env.setChild(project);
+ project.setChild(vehicleModel);
+ vehicleModel.setChild(test);
+ test.setChild(testStep);
+ testStep.setChild(measurement);
+ measurement.setChild(channelGroup);
+ channelGroup.setChild(channel);
+
+ return env;
+ }
+}
diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRootTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRootTest.java
new file mode 100644
index 0000000..b7ce35a
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRootTest.java
@@ -0,0 +1,176 @@
+package org.eclipse.mdm.nodeprovider.entity;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Map;
+
+import org.eclipse.mdm.api.atfxadapter.ATFXContextFactory;
+import org.eclipse.mdm.api.base.ConnectionException;
+import org.eclipse.mdm.api.base.adapter.EntityType;
+import org.eclipse.mdm.api.base.adapter.ModelManager;
+import org.eclipse.mdm.api.base.model.Channel;
+import org.eclipse.mdm.api.base.model.ChannelGroup;
+import org.eclipse.mdm.api.base.model.Environment;
+import org.eclipse.mdm.api.base.model.Measurement;
+import org.eclipse.mdm.api.base.model.TestStep;
+import org.eclipse.mdm.api.base.search.ContextState;
+import org.eclipse.mdm.api.dflt.ApplicationContext;
+import org.eclipse.mdm.api.dflt.model.Project;
+import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class NodeProviderRootTest {
+
+ private ApplicationContext context;
+
+ @Before
+ public void init() throws ConnectionException {
+ Map<String, String> map = ImmutableMap.of("atfxfile", "../../api/atfxadapter/src/test/resources/Right_Acc.atfx",
+ "freetext.active", "false");
+
+ context = new ATFXContextFactory().connect(map);
+ }
+
+ @After
+ public void close() throws ConnectionException {
+ context.close();
+ }
+
+ private NodeProviderRoot getNodeProviderRoot(ApplicationContext context) {
+ NodeLevel nl = createStructure(context, ContextState.MEASURED);
+ NodeProviderRoot npr = new NodeProviderRoot();
+ npr.setId("generic_measured");
+ npr.setName("Measured");
+ npr.setContexts(ImmutableMap.of("*", nl));
+
+ return npr;
+ }
+
+ @Test
+ public void testGetNodeLevel() throws ConnectionException {
+ NodeProviderRoot np = getNodeProviderRoot(context);
+
+ NodeLevel nl0 = np.getNodeLevel(context, "Environment", null);
+
+ assertThat(nl0.getEntityType().getName()).isEqualTo("Environment");
+ assertThat(nl0.getIdAttribute().getName()).isEqualTo("Id");
+ assertThat(nl0.getContextState()).isNull();
+ assertThat(nl0.isVirtual()).isFalse();
+
+ NodeLevel nl1 = np.getNodeLevel(context, "Project", null);
+ assertThat(nl1.getEntityType().getName()).isEqualTo("Project");
+ assertThat(nl1.getIdAttribute().getName()).isEqualTo("Id");
+ assertThat(nl1.getContextState()).isNull();
+ assertThat(nl1.isVirtual()).isFalse();
+
+ NodeLevel nl2 = np.getNodeLevel(context, "vehicle", "model");
+ assertThat(nl2.getEntityType().getName()).isEqualTo("vehicle");
+ assertThat(nl2.getIdAttribute().getName()).isEqualTo("model");
+ assertThat(nl2.getContextState()).isEqualTo(ContextState.MEASURED);
+ assertThat(nl2.isVirtual()).isTrue();
+
+ NodeLevel nl3 = np.getNodeLevel(context, "Test", null);
+ assertThat(nl3.getEntityType().getName()).isEqualTo("Test");
+ assertThat(nl3.getIdAttribute().getName()).isEqualTo("Id");
+ assertThat(nl3.getContextState()).isNull();
+ assertThat(nl3.isVirtual()).isFalse();
+
+ NodeLevel nl4 = np.getNodeLevel(context, "TestStep", null);
+ assertThat(nl4.getEntityType().getName()).isEqualTo("TestStep");
+ assertThat(nl4.getIdAttribute().getName()).isEqualTo("Id");
+ assertThat(nl4.getContextState()).isNull();
+ assertThat(nl4.isVirtual()).isFalse();
+
+ NodeLevel nl5 = np.getNodeLevel(context, "Measurement", null);
+ assertThat(nl5.getEntityType().getName()).isEqualTo("MeaResult");
+ assertThat(nl5.getIdAttribute().getName()).isEqualTo("Id");
+ assertThat(nl5.getContextState()).isNull();
+ assertThat(nl5.isVirtual()).isFalse();
+
+ NodeLevel nl6 = np.getNodeLevel(context, "ChannelGroup", null);
+ assertThat(nl6.getEntityType().getName()).isEqualTo("SubMatrix");
+ assertThat(nl6.getIdAttribute().getName()).isEqualTo("Id");
+ assertThat(nl6.getContextState()).isNull();
+ assertThat(nl6.isVirtual()).isFalse();
+
+ NodeLevel nl7 = np.getNodeLevel(context, "Channel", null);
+ assertThat(nl7.getEntityType().getName()).isEqualTo("MeaQuantity");
+ assertThat(nl7.getIdAttribute().getName()).isEqualTo("Id");
+ assertThat(nl7.getContextState()).isNull();
+ assertThat(nl7.isVirtual()).isFalse();
+ }
+
+ @Test
+ public void testGetNodeLevelInvalid() throws ConnectionException {
+ NodeProviderRoot np = getNodeProviderRoot(context);
+
+ assertThat(np.getNodeLevel(context, "InvalidType", null)).isNull();
+ }
+
+ @Test
+ public void testGetParentNodeLevel() throws ConnectionException {
+ NodeProviderRoot np = getNodeProviderRoot(context);
+
+ NodeLevel nl0 = np.getNodeLevel(context, "Environment", null);
+ NodeLevel nl1 = np.getNodeLevel(context, "Project", null);
+ NodeLevel nl2 = np.getNodeLevel(context, "vehicle", "model");
+ NodeLevel nl3 = np.getNodeLevel(context, "Test", null);
+
+ assertThat(np.getParentNodeLevel(context, nl0)).isNull();
+ assertThat(np.getParentNodeLevel(context, nl1)).isEqualTo(nl0);
+ assertThat(np.getParentNodeLevel(context, nl2)).isEqualTo(nl1);
+ assertThat(np.getParentNodeLevel(context, nl3)).isEqualTo(nl2);
+ }
+
+ @Test
+ public void testGetChildNodeLevel() throws ConnectionException {
+ NodeProviderRoot np = getNodeProviderRoot(context);
+
+ NodeLevel nl0 = np.getNodeLevel(context, "Environment", null);
+ NodeLevel nl1 = np.getNodeLevel(context, "Project", null);
+ NodeLevel nl2 = np.getNodeLevel(context, "vehicle", "model");
+ NodeLevel nl3 = np.getNodeLevel(context, "Test", null);
+
+ assertThat(np.getChildNodeLevel(context, null)).contains(nl0);
+ assertThat(np.getChildNodeLevel(context,
+ SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "Name"))).contains(nl1);
+ assertThat(
+ np.getChildNodeLevel(context, SerializationUtil.createNode("NVHDEMO", "Project", "", "Id", "", "Name")))
+ .contains(nl2);
+ assertThat(np.getChildNodeLevel(context,
+ SerializationUtil.createNode("NVHDEMO", "vehicle", "", "model", "", "model"))).contains(nl3);
+ }
+
+ private static NodeLevel createStructure(ApplicationContext context, ContextState contextState) {
+ ModelManager modelManager = context.getModelManager().get();
+
+ NodeLevel env = new NodeLevel(modelManager.getEntityType(Environment.class));
+ NodeLevel project = new NodeLevel(modelManager.getEntityType(Project.class));
+ EntityType vehicle = modelManager.getEntityType("vehicle");
+
+ NodeLevel vehicleModel = new NodeLevel(vehicle, vehicle.getAttribute("model"), vehicle.getAttribute("model"));
+ vehicleModel.setVirtual(true);
+ vehicleModel.setContextState(contextState);
+ NodeLevel test = new NodeLevel(modelManager.getEntityType(org.eclipse.mdm.api.base.model.Test.class));
+
+ env.setChild(project);
+ project.setChild(vehicleModel);
+ vehicleModel.setChild(test);
+
+ NodeLevel testStep = new NodeLevel(modelManager.getEntityType(TestStep.class));
+ NodeLevel measurement = new NodeLevel(modelManager.getEntityType(Measurement.class));
+ NodeLevel channelGroup = new NodeLevel(modelManager.getEntityType(ChannelGroup.class));
+ NodeLevel channel = new NodeLevel(modelManager.getEntityType(Channel.class));
+
+ test.setChild(testStep);
+ testStep.setChild(measurement);
+ measurement.setChild(channelGroup);
+ channelGroup.setChild(channel);
+
+ return env;
+ }
+}
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
index 5732364..ec8bf46 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
@@ -119,7 +119,7 @@
subscribeToNodeProviderChanges() {
this.nodeProviderSubscription = this.nodeproviderService.nodeProviderChanged.subscribe(
- np => this.reloadTree(),
+ np => this.reloadTree(),
error => this.notificationService.notifyError(
this.translateService.instant('navigator.mdm-navigator.err-cannot-update-navigation-tree'), error)
);
@@ -385,20 +385,29 @@
}
expandTreePath(treePath: MDMTreeNode[]) {
- console.log("expand", treePath);
- let current = this.nodes;
- for (let mdmTreeNode of treePath.reverse()) {
- console.log("current", current, "mdmTreeNode", mdmTreeNode);
- current = this.expandChild(current, mdmTreeNode);
- }
- console.log("current", current);
- this.onNodeSelect(current);
+ this.expandChild(this.nodes, treePath);
}
- expandChild(children: TreeNode[], treePath: MDMTreeNode) {
- let s = children.filter(node => node.data.source === treePath.source && node.data.id === treePath.id)[0];
- s.expanded = true;
- return s.children;
+ expandChild(children: TreeNode[], treePath: MDMTreeNode[]) {
+ const n = treePath.shift();
+ let nodeToExpand = children.filter(node => node.data.source === n.source && node.data.id === n.id)[0];
+
+ if (nodeToExpand) {
+ return this.getChildren(nodeToExpand.data).subscribe(
+ nodes => {
+ nodeToExpand.children = nodes;
+ nodeToExpand.expanded = true;
+ if (treePath.length > 0) {
+ this.expandChild(nodeToExpand.children, treePath);
+ } else {
+ this.selectedNodes = [ nodeToExpand ];
+ this.navigatorService.setSelectedItem(this.toMDMItem(nodeToExpand));
+ }
+ },
+ error => this.notificationService.notifyError(
+ this.translateService.instant('navigator.mdm-navigator.err-cannot-load-nodes'), error)
+ );
+ }
}
/**