diff options
author | Dirk Fauth | 2021-02-16 11:35:46 +0000 |
---|---|---|
committer | Dirk Fauth | 2021-02-16 11:39:27 +0000 |
commit | b48faaaa16f009f19c2da906ed78a6b06784de09 (patch) | |
tree | 72c92543db009c0f333b200aaf6f7d07056fc263 | |
parent | f55e77a52bc978f88bf2e01647792f24cc436f26 (diff) | |
download | org.eclipse.app4mc.cloud-b48faaaa16f009f19c2da906ed78a6b06784de09.tar.gz org.eclipse.app4mc.cloud-b48faaaa16f009f19c2da906ed78a6b06784de09.tar.xz org.eclipse.app4mc.cloud-b48faaaa16f009f19c2da906ed78a6b06784de09.zip |
Bug 571223 - Add configuration resource
Change-Id: Ic0cab0fb8f898dd47c9305d684a7c85ce0a00cd0
Signed-off-by: Dirk Fauth <Dirk.Fauth@de.bosch.com>
26 files changed, 1192 insertions, 220 deletions
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/CloudServiceExecutionRunnable.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/CloudServiceExecutionRunnable.java index f5574c2..be89149 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/CloudServiceExecutionRunnable.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/CloudServiceExecutionRunnable.java @@ -53,8 +53,14 @@ public class CloudServiceExecutionRunnable implements Runnable { try { Path inputFile = this.storageService.load(this.uuid, this.originalFilename); - ServiceNodeProcessingTask rootTask = new ServiceNodeProcessingTask(this.workflowStatus.getServiceRootNode(), - inputFile, storageService, messagingTemplate, uuid, workflowStatus); + ServiceNodeProcessingTask rootTask = + new ServiceNodeProcessingTask( + this.workflowStatus.getServiceRootNode(), + inputFile, + this.storageService, + this.messagingTemplate, + this.uuid, + this.workflowStatus); logger.info("started root task"); rootTask.fork(); @@ -68,33 +74,34 @@ public class CloudServiceExecutionRunnable implements Runnable { this.workflowStatus, this.storageService.load(this.uuid, "workflowstatus.json").toFile()); - // ensure that the done message is sent in any way to ensure the ui gets the final update - // needed in case the process finishes while the page reloads - long start = System.currentTimeMillis(); - long end = System.currentTimeMillis(); - - long timeout = 10_000l; - - while (!this.workflowStatus.isConnected()) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // Restore interrupted state... - Thread.currentThread().interrupt(); - } + if (this.messagingTemplate != null) { + // ensure that the done message is sent in any way to ensure the ui gets the final update + // needed in case the process finishes while the page reloads + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis(); - end = System.currentTimeMillis(); + long timeout = 10_000l; - // don't wait longer than 10 seconds to get connected - if (timeout > 0 && (end - start) > timeout) { - break; + while (!this.workflowStatus.isConnected()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Restore interrupted state... + Thread.currentThread().interrupt(); + } + + end = System.currentTimeMillis(); + + // don't wait longer than 10 seconds to get connected + if (timeout > 0 && (end - start) > timeout) { + break; + } } + + this.messagingTemplate.convertAndSend( + "/topic/process-updates/" + this.uuid, + new ProcessLog(Action.DONE, null, this.uuid)); } - - this.messagingTemplate.convertAndSend( - "/topic/process-updates/" + this.uuid, - new ProcessLog(Action.DONE, null, this.uuid)); } } - -} +}
\ No newline at end of file diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java index f113c98..de52dff 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java @@ -1,5 +1,5 @@ /********************************************************************************* - * Copyright (c) 2020 Robert Bosch GmbH and others. + * Copyright (c) 2020, 2021 Robert Bosch GmbH and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -23,6 +23,10 @@ public class ServiceConfiguration { * Needed for user interface. */ private String serviceName; + /** + * The description of the service to show in the user interface, so a user knows what a service is supposed to do. + */ + private String serviceDescription; private ArrayList<ServiceConfigurationParameter> parameter = new ArrayList<>(); public ServiceConfiguration() { @@ -41,14 +45,20 @@ public class ServiceConfiguration { this.serviceName = serviceName; } + public String getServiceDescription() { + return serviceDescription; + } + + public void setServiceDescription(String serviceDescription) { + this.serviceDescription = serviceDescription; + } + public void addParameter(ServiceConfigurationParameter param) { this.parameter.add(param); } public List<ServiceConfigurationParameter> getParameterList() { - ArrayList<ServiceConfigurationParameter> params = new ArrayList<>(this.parameter); - params.sort((o1, o2) -> o1.getKey().compareTo(o2.getKey())); - return params; + return new ArrayList<>(this.parameter); } public ServiceConfigurationParameter getParameter(String key) { diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationDefinition.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationDefinition.java new file mode 100644 index 0000000..19e4bbe --- /dev/null +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationDefinition.java @@ -0,0 +1,140 @@ +/********************************************************************************* + * Copyright (c) 2021 Robert Bosch GmbH and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Robert Bosch GmbH - initial API and implementation + ******************************************************************************** + */ +package org.eclipse.app4mc.cloud.manager; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + * The service configuration definition that is provided by a service. + */ +@JsonDeserialize(using = ServiceConfigurationDefinitionDeserializer.class) +public class ServiceConfigurationDefinition { + + /** + * The description of the service to explain what the service does. + */ + private String description; + /** + * The file type that is accepted as input, e.g. amxmi, json, btf. + */ + private String inputType; + /** + * The accepted input version, for the Amalthea Model e.g. 0.9.9 or 1.0.0. + */ + private String inputVersion; + /** + * Whether the service supports input provided as archive. + */ + private boolean inputArchiveSupported = false; + + /** + * The file type that is produced as a result, e.g. amxmi, json, btf. + */ + private String outputType; + /** + * The produced output version, for the Amalthea Model e.g. 0.9.9 or 1.0.0. + */ + private String outputVersion; + /** + * Whether the service supports producing the output as archive. + */ + private boolean outputArchiveSupported = false; + + /** + * The configuration parameter the service supports. + */ + private ArrayList<ServiceConfigurationParameter> parameter = new ArrayList<>(); + + public ServiceConfigurationDefinition() { + // empty constructor needed for JSON serialization + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getInputType() { + return inputType; + } + + public void setInputType(String inputType) { + this.inputType = inputType; + } + + public String getInputVersion() { + return inputVersion; + } + + public void setInputVersion(String inputVersion) { + this.inputVersion = inputVersion; + } + + public boolean isInputArchiveSupported() { + return this.inputArchiveSupported; + } + + public void setInputArchiveSupported(boolean supported) { + this.inputArchiveSupported = supported; + } + + public String getOutputType() { + return outputType; + } + + public void setOutputType(String outputType) { + this.outputType = outputType; + } + + public String getOutputVersion() { + return outputVersion; + } + + public void setOutputVersion(String outputVersion) { + this.outputVersion = outputVersion; + } + + public boolean isOutputArchiveSupported() { + return this.outputArchiveSupported; + } + + public void setOutputArchiveSupported(boolean supported) { + this.outputArchiveSupported = supported; + } + + public void addParameter(ServiceConfigurationParameter param) { + this.parameter.add(param); + } + + public List<ServiceConfigurationParameter> getParameterList() { + ArrayList<ServiceConfigurationParameter> params = new ArrayList<>(this.parameter); + params.sort((o1, o2) -> o1.getName().compareTo(o2.getName())); + return params; + } + + public ServiceConfigurationParameter getParameter(String key) { + for (ServiceConfigurationParameter param : this.parameter) { + if (param.getKey().equals(key)) { + return param; + } + } + return null; + } +} diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationDefinitionDeserializer.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationDefinitionDeserializer.java new file mode 100644 index 0000000..491a476 --- /dev/null +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationDefinitionDeserializer.java @@ -0,0 +1,140 @@ +/********************************************************************************* + * Copyright (c) 2020, 2021 Robert Bosch GmbH and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Robert Bosch GmbH - initial API and implementation + ******************************************************************************** + */ +package org.eclipse.app4mc.cloud.manager; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +/** + * Custom {@link StdDeserializer} for {@link ServiceConfigurationDefinition} objects. + */ +public class ServiceConfigurationDefinitionDeserializer extends StdDeserializer<ServiceConfigurationDefinition> { + + private static final long serialVersionUID = 857606567523680918L; + + protected ServiceConfigurationDefinitionDeserializer() { + super(ServiceConfigurationDefinition.class); + } + + @Override + public ServiceConfigurationDefinition deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + ServiceConfigurationDefinition result = new ServiceConfigurationDefinition(); + JsonNode node = jp.getCodec().readTree(jp); + + JsonNode description = node.get("description"); + if (description != null) { + result.setDescription(description.asText()); + } + + JsonNode input = node.get("input"); + if (input != null) { + JsonNode typeNode = input.get("type"); + if (typeNode != null) { + result.setInputType(typeNode.asText()); + } + JsonNode versionNode = input.get("version"); + if (versionNode != null) { + result.setInputVersion(versionNode.asText()); + } + JsonNode archiveNode = input.get("archive-supported"); + if (archiveNode != null) { + result.setInputArchiveSupported(archiveNode.asBoolean()); + } + } + + JsonNode output = node.get("output"); + if (output != null) { + JsonNode typeNode = output.get("type"); + if (typeNode != null) { + result.setOutputType(typeNode.asText()); + } + JsonNode versionNode = output.get("version"); + if (versionNode != null) { + result.setOutputVersion(versionNode.asText()); + } + JsonNode archiveNode = output.get("archive-supported"); + if (archiveNode != null) { + result.setOutputArchiveSupported(archiveNode.asBoolean()); + } + } + + JsonNode parameter = node.get("parameter"); + if (parameter != null) { + parameter.fields().forEachRemaining(parameterField -> { + deserializeParameterNode(result, parameterField.getKey(), parameterField.getValue()); + }); + } + + return result; + } + + private void deserializeParameterNode( + ServiceConfigurationDefinition result, + String parameterKey, + JsonNode fields) { + + ServiceConfigurationParameter param = new ServiceConfigurationParameter(); + param.setKey(parameterKey); + + JsonNode name = fields.get("name"); + if (name != null) { + param.setName(name.asText()); + } + + JsonNode description = fields.get("description"); + if (description != null) { + param.setDescription(description.asText()); + } + + JsonNode type = fields.get("type"); + if (type != null) { + param.setType(type.asText()); + } + + JsonNode value = fields.get("value"); + if (value != null) { + param.setValue(value.asText()); + } + + JsonNode cardinality = fields.get("cardinality"); + if (cardinality != null) { + param.setCardinality(cardinality.asText()); + } + + JsonNode mandatory = fields.get("mandatory"); + if (mandatory != null) { + param.setMandatory(mandatory.asBoolean()); + } + + JsonNode values = fields.get("values"); + if (values != null) { + if (values.isArray()) { + ArrayList<String> valueList = new ArrayList<>(); + values.forEach(v -> valueList.add(v.asText())); + param.setPossibleValues(valueList); + } else { + param.setPossibleValues(Arrays.asList(values.asText())); + } + } + + result.addParameter(param); + } +} diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationParameter.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationParameter.java index 9f21b40..317eddc 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationParameter.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationParameter.java @@ -1,5 +1,5 @@ /********************************************************************************* - * Copyright (c) 2020 Robert Bosch GmbH and others. + * Copyright (c) 2020, 2021 Robert Bosch GmbH and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -19,9 +19,10 @@ import java.util.List; public class ServiceConfigurationParameter { private String name; + private String description; private String key; private String value; - private String type; + private String type = "String"; private String cardinality = "single"; private boolean mandatory = false; private List<String> possibleValues = new ArrayList<>(); @@ -35,6 +36,14 @@ public class ServiceConfigurationParameter { this.name = name; } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + public String getKey() { return key; } diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNodeProcessingTask.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNodeProcessingTask.java index 92b9b4b..a6a17b8 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNodeProcessingTask.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNodeProcessingTask.java @@ -51,8 +51,14 @@ public class ServiceNodeProcessingTask extends RecursiveTask<Path> { private final Logger logger = LoggerFactory.getLogger(this.getClass()); - public ServiceNodeProcessingTask(ServiceNode n, Path inputFile, StorageService storageService, - SimpMessageSendingOperations messagingTemplate, String uuid, WorkflowStatus ws) { + public ServiceNodeProcessingTask( + ServiceNode n, + Path inputFile, + StorageService storageService, + SimpMessageSendingOperations messagingTemplate, + String uuid, + WorkflowStatus ws) { + this.storageService = storageService; this.messagingTemplate = messagingTemplate; this.workflowStatus = ws; @@ -63,41 +69,31 @@ public class ServiceNodeProcessingTask extends RecursiveTask<Path> { @Override protected Path compute() { - try { - if (this.serviceNode.isStructuralNode()) { - logger.info("created structural node thread: {}", this.serviceNode.getId()); - Path retval = processStructuralNode(this.serviceNode, this.inputFile); - return retval; - } else { - logger.info("created branch node thread: {}", this.serviceNode.getId()); - Path retval = processNonStructuralNode(this.serviceNode, this.inputFile); - if (this.workflowStatus.isCancelled()) { - addMessage(this.workflowStatus, "Workflow cancelled by user"); - } - return retval; + if (this.serviceNode.isStructuralNode()) { + logger.info("created structural node thread: {}", this.serviceNode.getId()); + return processStructuralNode(this.serviceNode, this.inputFile); + } else { + logger.info("created branch node thread: {}", this.serviceNode.getId()); + Path retval = processNonStructuralNode(this.serviceNode, this.inputFile); + if (this.workflowStatus.isCancelled()) { + addMessage(this.workflowStatus, "Workflow cancelled by user"); } - } catch (ProcessingFailedException e) { - this.workflowStatus.addError(e.getMessage()); - return null; + return retval; } - } private Path processStructuralNode(ServiceNode node, Path inputFile) { Path nextInput = inputFile; - for (ServiceNode n : node.getChildren()) { - try { - nextInput = processNonStructuralNode(n, nextInput); - // if an error occurs without throwing an exception, successor tasks do not get executed - if (n.isFailed()) { - break; - } - if (this.workflowStatus.isCancelled()) { - addMessage(this.workflowStatus, "Workflow cancelled by user"); - break; - } - } catch (ProcessingFailedException e) { - this.workflowStatus.addError(e.getMessage()); + for (ServiceNode childNode : node.getChildren()) { + nextInput = processNonStructuralNode(childNode, nextInput); + + // if an error occurs without throwing an exception, successor tasks do not get executed + if (childNode.isFailed()) { + break; + } + + if (this.workflowStatus.isCancelled()) { + addMessage(this.workflowStatus, "Workflow cancelled by user"); break; } } @@ -105,52 +101,53 @@ public class ServiceNodeProcessingTask extends RecursiveTask<Path> { } private Path processNonStructuralNode(ServiceNode node, Path inputFile) { - if (node.getChildren().isEmpty()) { - return processLeafNode(node, inputFile); - } else if (node.getChildren().size() > 1) { - try { - Path currOutput = executeCloudService(node, inputFile); - // if an error occurs without throwing an exception, successor tasks do not get executed - if (node.isFailed()) { - return null; - } - List<ServiceNodeProcessingTask> lActiveProcessingTask = new ArrayList<>(); - for (ServiceNode n : node.getChildren()) { - lActiveProcessingTask.add(new ServiceNodeProcessingTask(n, currOutput, storageService, - messagingTemplate, uuid, workflowStatus)); - } - logger.info("launching new branches"); - ForkJoinTask.invokeAll(lActiveProcessingTask); - return currOutput; - } catch (ProcessingFailedException e) { - this.workflowStatus.addError(e.getMessage()); - return null; + + // first execute the service of the provided node + Path currOutput = null; + try { + currOutput = executeCloudService(node, inputFile); + } catch (ProcessingFailedException e) { + addError(this.workflowStatus, e.getMessage(), node); + } + + // check if the service node execution failed + // if an error occurred without throwing an exception, successor tasks do not get executed + if (node.isFailed()) { + return null; + } + + if (node.getChildren().size() > 1) { + ArrayList<ServiceNodeProcessingTask> childTasks = new ArrayList<>(); + for (ServiceNode childNode : node.getChildren()) { + childTasks.add(new ServiceNodeProcessingTask( + childNode, + currOutput, + storageService, + messagingTemplate, + uuid, + workflowStatus)); } - } else { - try { - Path currOutput = executeCloudService(node, inputFile); - // if an error occurs without throwing an exception, successor tasks do not get executed - if (node.isFailed()) { - return null; - } - ServiceNode nextNode = node.getChildren().get(0); - if (nextNode.isStructuralNode()) { - ServiceNodeProcessingTask nextNodeTask = new ServiceNodeProcessingTask(nextNode, currOutput, - storageService, messagingTemplate, uuid, workflowStatus); - ForkJoinPool.commonPool().invoke(nextNodeTask); - } else { - processNonStructuralNode(nextNode, currOutput); - } - return currOutput; - } catch (ProcessingFailedException e) { - this.workflowStatus.addError(e.getMessage()); - return null; + + logger.info("launching new branches"); + ForkJoinTask.invokeAll(childTasks); + } else if (node.getChildren().size() == 1) { + ServiceNode nextNode = node.getChildren().get(0); + if (nextNode.isStructuralNode()) { + ServiceNodeProcessingTask nextNodeTask = + new ServiceNodeProcessingTask( + nextNode, + currOutput, + storageService, + messagingTemplate, + uuid, + workflowStatus); + ForkJoinPool.commonPool().invoke(nextNodeTask); + } else { + processNonStructuralNode(nextNode, currOutput); } } - } - - private Path processLeafNode(ServiceNode node, Path inputFile) { - return executeCloudService(node, inputFile); + + return currOutput; } private Path executeCloudService(ServiceNode node, Path inputFile) { @@ -160,6 +157,10 @@ public class ServiceNodeProcessingTask extends RecursiveTask<Path> { String serviceName = csd.getName(); String baseUrl = csd.getBaseUrl(); + if (workflowStatus.isCancelled()) { + return null; + } + // upload to service MultipartBody multipartBody = Unirest.post(baseUrl) .field("file", Files.newInputStream(inputFile), inputFile.getFileName().toString()); @@ -196,11 +197,13 @@ public class ServiceNodeProcessingTask extends RecursiveTask<Path> { // error Object body = uploadResponse.getBody(); if (body != null && !body.toString().isEmpty()) { - workflowStatus.addError("Upload to " + serviceName + " failed! Error code: " + uploadResponse.getStatus() + " - " + body +" - Workflow stopped!"); - node.markFailed(); + addError(workflowStatus, + "Upload to " + serviceName + " failed! Error code: " + uploadResponse.getStatus() + " - " + body +" - Workflow stopped!", + node); } else { - workflowStatus.addError("Upload to " + serviceName + " failed! Error code: " + uploadResponse.getStatus() + " - Workflow stopped!"); - node.markFailed(); + addError(workflowStatus, + "Upload to " + serviceName + " failed! Error code: " + uploadResponse.getStatus() + " - Workflow stopped!", + node); } return null; } @@ -331,16 +334,14 @@ public class ServiceNodeProcessingTask extends RecursiveTask<Path> { } if (error) { - workflowStatus.addError(serviceName + " failed with errors"); - node.markFailed(); + addError(workflowStatus, serviceName + " failed with errors", node); } else { addMessage(workflowStatus, serviceName + " successfull"); } } else { String errorUrl = HeaderHelper.getUrlFromLink(linkHeaders, "error", baseUrl); if (errorUrl != null) { - workflowStatus.addError(serviceName + " processing finished with error"); - node.markFailed(); + addError(workflowStatus, serviceName + " processing finished with error", node); // download error file HttpResponse<File> errorResponse = Unirest.get(errorUrl) @@ -361,8 +362,7 @@ public class ServiceNodeProcessingTask extends RecursiveTask<Path> { // extract delete deleteUrl = HeaderHelper.getUrlFromLink(errorResponse.getHeaders().get("Link"), "delete", baseUrl); } else { - workflowStatus.addError(serviceName + " has no result and no error"); - node.markFailed(); + addError(workflowStatus, serviceName + " has no result and no error", node); } } @@ -426,4 +426,8 @@ public class ServiceNodeProcessingTask extends RecursiveTask<Path> { } } + private void addError(WorkflowStatus workflowStatus, String message, ServiceNode node) { + workflowStatus.addError(message); + node.markFailed(); + } } diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java index 19810dd..7d54b44 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java @@ -14,19 +14,19 @@ package org.eclipse.app4mc.cloud.manager; import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; +import kong.unirest.HttpResponse; +import kong.unirest.JsonNode; import kong.unirest.Unirest; public final class WorkflowStatusHelper { @@ -48,71 +48,45 @@ public final class WorkflowStatusHelper { */ public static ServiceConfiguration getConfigurationForService(CloudServiceDefinition csd) { String selected = csd.getKey(); - ServiceConfiguration config = null; - - if ("validation".equals(selected)) { - // TODO general: check if the selected service has a configuration URL - // TODO build configuration based on provided service configuration - - List<String> allProfiles = new ArrayList<>(); + ServiceConfiguration config = new ServiceConfiguration(csd.getName()); + + if (csd.isConfigurationAvailable()) { try { - String baseUrl = csd.getBaseUrl(); - if (!baseUrl.endsWith("/")) { - baseUrl += "/"; + String configUrl = csd.getBaseUrl(); + if (!configUrl.endsWith("/")) { + configUrl += "/"; + } + configUrl += "config"; + HttpResponse<JsonNode> configResponse = Unirest.get(configUrl).asJson(); + if (configResponse.getStatus() == 200) { + JsonNode configJson = configResponse.getBody(); + if (configJson != null) { + ServiceConfigurationDefinition configDefinition = WorkflowStatusHelper.loadServiceConfigurationDefinition(configJson.toString()); + configDefinition.getParameterList().forEach(p -> config.addParameter(p)); + if (configDefinition.getDescription() != null) { + config.setServiceDescription(configDefinition.getDescription()); + } + } else { + LOG.info("Failed to access configuration resource for " + csd.getName() + ": No JSON body in response"); + } + } else { + LOG.info("Failed to access configuration resource for " + csd.getName() + ": " + configResponse.getStatusText()); } - List<?> jsonResult = Unirest.get(baseUrl + "profiles").asJson().getBody().getArray().toList(); - allProfiles = jsonResult.stream().map(Object::toString).collect(Collectors.toList()); } catch (Exception e) { - // do nothing, we will handle configurations in a different way in the future - } - - if (allProfiles != null && !allProfiles.isEmpty()) { - config = new ServiceConfiguration(csd.getName()); - - ServiceConfigurationParameter validationProfiles = new ServiceConfigurationParameter(); - validationProfiles.setName("Validation profiles"); - validationProfiles.setKey("profiles"); - validationProfiles.setType("String"); - validationProfiles.setCardinality("multiple"); - validationProfiles.setPossibleValues(allProfiles); - config.addParameter(validationProfiles); + LOG.info("Failed to access configuration resource for " + csd.getName() + ": " + e.getMessage()); } - } else if ("rtc_analysis".equals(selected)) { - config = new ServiceConfiguration(csd.getName()); - - ServiceConfigurationParameter ascPriorities = new ServiceConfigurationParameter(); - ascPriorities.setName("Ascending priorities"); - ascPriorities.setKey("analysis-ascending-priorities"); - ascPriorities.setType("boolean"); - config.addParameter(ascPriorities); - - ServiceConfigurationParameter enableFlows = new ServiceConfigurationParameter(); - enableFlows.setName("Include flow analysis"); - enableFlows.setKey("analysis-enable-flows"); - enableFlows.setType("boolean"); - config.addParameter(enableFlows); - - ServiceConfigurationParameter ignoreOverUtil = new ServiceConfigurationParameter(); - ignoreOverUtil.setName("Ignore over utilization"); - ignoreOverUtil.setKey("analysis-ignore-overutilization"); - ignoreOverUtil.setType("boolean"); - config.addParameter(ignoreOverUtil); - - ServiceConfigurationParameter timeUnit = new ServiceConfigurationParameter(); - timeUnit.setName("Time unit"); - timeUnit.setKey("analysis-time-unit"); - timeUnit.setType("String"); - timeUnit.setPossibleValues(Arrays.asList("s", "ms", "us", "ns")); - config.addParameter(timeUnit); } - // specify default configuration parameter - if (config == null) { - config = new ServiceConfiguration(csd.getName()); + if (!StringUtils.isEmpty(csd.getDescription())) { + // service description is provided in the manager administration panel, so we + // override the value provided by the service itself + config.setServiceDescription(csd.getDescription()); } - + + // specify default configuration parameter for the manager ServiceConfigurationParameter timeOut = new ServiceConfigurationParameter(); timeOut.setName("Timeout (in ms)"); + timeOut.setDescription("The number of milliseconds the workflow should wait for the service to finish.\n-1 means to wait infinitely."); timeOut.setKey("timeout"); timeOut.setValue("60000"); if ("app4mc_sim".equals(selected)) { @@ -124,6 +98,7 @@ public final class WorkflowStatusHelper { ServiceConfigurationParameter deleteResult = new ServiceConfigurationParameter(); deleteResult.setName("Delete result "); + deleteResult.setDescription("Flag to configure if the result should be deleted in the cloud service.\nDefault is true to keep the infrastructure clean."); deleteResult.setKey("deleteResult"); deleteResult.setValue("true"); deleteResult.setType("boolean"); @@ -242,4 +217,24 @@ public final class WorkflowStatusHelper { } return null; } + + /** + * Deserializes the given JSON String. + * + * @param input The String to deserialize. + * @return The deserialized {@link ServiceConfigurationDefinition}. + */ + public static ServiceConfigurationDefinition loadServiceConfigurationDefinition(String input) { + try { + ObjectMapper mapper = new ObjectMapper(); + + SimpleModule module = new SimpleModule(); + mapper.registerModule(module); + + return mapper.readValue(input, ServiceConfigurationDefinition.class); + } catch (Exception e) { + LOG.error("Failed to load service configuration definition", e); + } + return null; + } } diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusSerializer.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusSerializer.java index 553cc36..01d1055 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusSerializer.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusSerializer.java @@ -1,5 +1,5 @@ /********************************************************************************* - * Copyright (c) 2020 Robert Bosch GmbH and others. + * Copyright (c) 2020, 2021 Robert Bosch GmbH and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -31,11 +31,7 @@ public class WorkflowStatusSerializer extends StdSerializer<WorkflowStatus> { private static final long serialVersionUID = 8920512086262532108L; public WorkflowStatusSerializer() { - this(null); - } - - public WorkflowStatusSerializer(Class<WorkflowStatus> t) { - super(t); + super(WorkflowStatus.class); } @Override @@ -84,11 +80,16 @@ public class WorkflowStatusSerializer extends StdSerializer<WorkflowStatus> { if (!node.isStructuralNode()) { ServiceConfiguration configuration = node.getServiceConfiguration(); if (configuration != null) { - for (ServiceConfigurationParameter param : configuration.getParameterList()) { - if (param.getValue() != null) { - gen.writeObjectField(param.getKey(), param.getValue()); - } - } + configuration.getParameterList().stream() + .sorted((o1, o2) -> o1.getKey().compareTo(o2.getKey())) + .filter(p -> p.getValue() != null) + .forEach(param -> { + try { + gen.writeObjectField(param.getKey(), param.getValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); } } diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java index 263d549..ce3a56c 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/ApplicationConfig.java @@ -62,7 +62,8 @@ public class ApplicationConfig { String name = split[1]; String url = split[2]; String desc = split.length >= 4 ? split[3] : ""; - return new CloudServiceDefinition(key, name, url, desc); + boolean configAvailable = split.length >= 5 ? Boolean.getBoolean(split[4]) : true; + return new CloudServiceDefinition(key, name, url, desc, configAvailable); }).collect(Collectors.toList()); definitions.addAll(services); } catch (IOException e) { diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinition.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinition.java index da9b347..8229b4e 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinition.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/administration/CloudServiceDefinition.java @@ -1,5 +1,5 @@ /********************************************************************************* - * Copyright (c) 2020 Robert Bosch GmbH and others. + * Copyright (c) 2020, 2021 Robert Bosch GmbH and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -19,16 +19,22 @@ public class CloudServiceDefinition { String name; String baseUrl; String description; + boolean configurationAvailable = true; public CloudServiceDefinition() { // empty default constructor } public CloudServiceDefinition(String key, String name, String baseUrl, String description) { + this(key, name, baseUrl, description, true); + } + + public CloudServiceDefinition(String key, String name, String baseUrl, String description, boolean configAvailable) { this.key = key; this.name = name; this.baseUrl = baseUrl; this.description = description; + this.configurationAvailable = configAvailable; } public String getKey() { @@ -62,5 +68,13 @@ public class CloudServiceDefinition { public void setDescription(String description) { this.description = description; } + + public boolean isConfigurationAvailable() { + return configurationAvailable; + } + + public void setConfigurationAvailable(boolean configurationAvailable) { + this.configurationAvailable = configurationAvailable; + } } diff --git a/manager/src/main/resources/services.txt b/manager/src/main/resources/services.txt index ac82627..318335e 100644 --- a/manager/src/main/resources/services.txt +++ b/manager/src/main/resources/services.txt @@ -1,6 +1,6 @@ -migration;Migration;http://localhost:8080/app4mc/converter/;Migrates an input model file to model version 0.9.9 -validation;Validation;http://localhost:8181/app4mc/validation/;Validates the input model file -rtc_analysis;RTC Analysis;http://localhost:8081/app4mc/analysis/;Executes Real-Time Calculus (RTC) techniques on a provided Amalthea model to analyse timing properties of heterogeneous embedded systems +migration;Migration;http://localhost:8080/app4mc/converter/ +validation;Validation;http://localhost:8181/app4mc/validation/ +rtc_analysis;RTC Analysis;http://localhost:8081/app4mc/analysis/ rtc_interpreter;RTC Evaluation;http://localhost:8082/app4mc/interpreter/rtc/;Converts the results of a RTC Analysis into an input for the Chart Visualizer label_core_interpreter;Label per Core Evaluation;http://localhost:8084/app4mc/interpreter/label-per-core/;Parses an Amalthea model to inspect label access operations per core label_memory_interpreter;Label per Memory Evaluation;http://localhost:8084/app4mc/interpreter/label-per-memory/;Parses an Amalthea model to inspect label access operations per memory @@ -8,6 +8,6 @@ label_task_interpreter;Label per Task Evaluation;http://localhost:8084/app4mc/in label_size_interpreter;Label Size Evaluation;http://localhost:8084/app4mc/interpreter/label-size/;Parses an Amalthea model to inspect the number of labels grouped by size chart_visualizer;Chart Visualizer;http://localhost:8083/app4mc/visualization/barchart/;Visualization of interpreter results amlt2inchron;Amalthea -> INCHRON;https://am2inc.dev1.inchron.de/projects;Transforms an Amalthea model to an INCHRON model -amlt2systemc;Amalthea -> SystemC;http://localhost:8282/app4mc/amlt2systemc/;Transforms an Amalthea model to simulation code +amlt2systemc;Amalthea -> SystemC;http://localhost:8282/app4mc/amlt2systemc/ app4mc_sim;APP4MC.Sim;http://139.30.201.29:2323/app4mc/simulation/;Executes simulation and generates a BTF traces inchron_btf_visualization;INCHRON BTF Trace Visualization;https://trace.dev1.inchron.de/traces/;Visualization of trace data provided by INCHRON
\ No newline at end of file diff --git a/manager/src/main/resources/services_online.txt b/manager/src/main/resources/services_online.txt index 85f8073..98cc92e 100644 --- a/manager/src/main/resources/services_online.txt +++ b/manager/src/main/resources/services_online.txt @@ -1,6 +1,6 @@ -migration;Migration;https://app4mc.eclipseprojects.io/app4mc/converter/;Migrates an input model file to model version 0.9.9 -validation;Validation;https://app4mc.eclipseprojects.io/app4mc/validation/;Validates the input model file -rtc_analysis;RTC Analysis;https://app4mc.eclipseprojects.io/app4mc/analysis/;Executes Real-Time Calculus (RTC) techniques on a provided Amalthea model to analyse timing properties of heterogeneous embedded systems +migration;Migration;https://app4mc.eclipseprojects.io/app4mc/converter/ +validation;Validation;https://app4mc.eclipseprojects.io/app4mc/validation/ +rtc_analysis;RTC Analysis;https://app4mc.eclipseprojects.io/app4mc/analysis/ rtc_interpreter;RTC Evaluation;https://app4mc.eclipseprojects.io/app4mc/interpreter/rtc/;Converts the results of a RTC Analysis into an input for the Chart Visualizer label_core_interpreter;Label per Core Evaluation;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-per-core/;Parses an Amalthea model to inspect label access operations per core label_memory_interpreter;Label per Memory Evaluation;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-per-memory/;Parses an Amalthea model to inspect label access operations per memory @@ -8,6 +8,6 @@ label_task_interpreter;Label per Task Evaluation;https://app4mc.eclipseprojects. label_size_interpreter;Label Size Evaluation;https://app4mc.eclipseprojects.io/app4mc/interpreter/label-size/;Parses an Amalthea model to inspect the number of labels grouped by size chart_visualizer;Chart Visualizer;https://app4mc.eclipseprojects.io/app4mc/visualization/barchart/;Visualization of interpreter results amlt2inchron;Amalthea -> INCHRON;https://am2inc.dev1.inchron.de/projects;Transforms an Amalthea model to an INCHRON model -amlt2systemc;Amalthea -> SystemC;https://app4mc.eclipseprojects.io/app4mc/amlt2systemc/;Transforms an Amalthea model to simulation code +amlt2systemc;Amalthea -> SystemC;https://app4mc.eclipseprojects.io/app4mc/amlt2systemc/ app4mc_sim;APP4MC.Sim;http://139.30.201.29:2323/app4mc/simulation/;Executes simulation and generates a BTF traces inchron_btf_visualization;INCHRON BTF Trace Visualization;https://trace.dev1.inchron.de/traces/;Visualization of trace data provided by INCHRON
\ No newline at end of file diff --git a/manager/src/main/resources/templates/admin.html b/manager/src/main/resources/templates/admin.html index 15d2528..06d6bab 100644 --- a/manager/src/main/resources/templates/admin.html +++ b/manager/src/main/resources/templates/admin.html @@ -25,6 +25,7 @@ <th>Service Name</th> <th>Service Base URL</th> <th>Service Description</th> + <th>Service Configuration</th> <th></th> </tr> </thead> @@ -50,6 +51,13 @@ th:field="*{services[__${stat.index}__].description}" class="form-control fm-ctrl-right" /> </td> + <td class="text-center"> + <input + type="checkbox" + th:field="*{services[__${stat.index}__].configurationAvailable}" + class="form-check-input" + style="text-center" /> + </td> <td> <a th:if="*{services[__${stat.index}__].name != null}" th:attr="onclick=|removeService('*{services[__${stat.index}__].key}')|" diff --git a/manager/src/main/resources/templates/selectedServices.html b/manager/src/main/resources/templates/selectedServices.html index d8383ae..26b0114 100644 --- a/manager/src/main/resources/templates/selectedServices.html +++ b/manager/src/main/resources/templates/selectedServices.html @@ -39,29 +39,39 @@ <div th:if="not *{selectedServices.isEmpty()}"> <div th:each="node, nodeStatus : *{selectedServices}" class="mb-3"> <h4 th:text="${node.service.name + ' Configuration'}">Config</h4> + <div th:text="${node.serviceConfiguration.serviceDescription}" class="font-italic mb-1">Description</div> <div th:each="parameter, parameterStatus : ${node.serviceConfiguration.parameterList}"> <!-- single non-boolean value --> <div th:if="${parameter.cardinality == 'single' and parameter.type != 'boolean' and parameter.possibleValues.isEmpty()}"> <label class="form-check-label" th:text="${parameter.name}">Label</label> <input - class="form-control" + class="form-control mb-1" type="text" th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}" - th:disabled="*{uuid != null}"/> + th:disabled="*{uuid != null}" + th:title="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].description}" + data-toggle="tooltip"/> </div> <!-- single boolean value --> <div th:if="${parameter.cardinality == 'single' and parameter.type == 'boolean'}" class="form-check"> <input - class="form-check-input" + class="form-check-input mb-1" type="checkbox" th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}" th:value="true" th:disabled="*{uuid != null}"> - <label class="form-check-label" th:text="${parameter.name}" th:for="${parameter.key}">Label</label> + <label + class="form-check-label" + th:text="${parameter.name}" + th:for="${parameter.key}" + th:title="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].description}" + data-toggle="tooltip">Label</label> </div> <!-- multiple possible values + cardinality multiple = checkboxes --> <div th:if="${parameter.cardinality == 'multiple' and parameter.possibleValues.size() > 1}"> - <label th:text="${parameter.name}">Label</label> + <label + th:text="${parameter.name}"th:title="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].description}" + data-toggle="tooltip">Label</label> <div th:each="pv : ${parameter.possibleValues}" class="form-check"> <input type="checkbox" @@ -75,9 +85,11 @@ <div th:if="${parameter.cardinality == 'single' and parameter.possibleValues.size() > 1}"> <label th:text="${parameter.name}">Label</label> <select - class="custom-select" + class="form-control mb-1" th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}" - th:disabled="*{uuid != null}"> + th:disabled="*{uuid != null}" + th:title="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].description}" + data-toggle="tooltip"> <option value=""></option> <option th:each="pv : ${parameter.possibleValues}" diff --git a/manager/src/test/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelperTest.java b/manager/src/test/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelperTest.java index 19dd243..3681757 100644 --- a/manager/src/test/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelperTest.java +++ b/manager/src/test/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelperTest.java @@ -20,7 +20,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.ArrayList; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -44,39 +49,46 @@ public class WorkflowStatusHelperTest { "migration", "Migration", "http://localhost:8080/app4mc/converter/", - "Migrates an input model file to model version 0.9.9"); + "Migrates an input model file to model version 0.9.9", + false); private static CloudServiceDefinition VALIDATION = new CloudServiceDefinition( "validation", "Validation", "http://localhost:8181/app4mc/validation/", - "Validates the input model file"); + "Validates the input model file", + false); private static CloudServiceDefinition LABEL_CORE = new CloudServiceDefinition( "label_core_interpreter", "Label per Core Interpreter", "http://localhost:8084/app4mc/interpreter/label-per-core/", - "Parses an Amalthea model to inspect label access operations per core"); + "Parses an Amalthea model to inspect label access operations per core", + false); private static CloudServiceDefinition LABEL_MEMORY = new CloudServiceDefinition( "label_memory_interpreter", "Label per Memory Interpreter", "http://localhost:8084/app4mc/interpreter/label-per-memory/", - "Parses an Amalthea model to inspect label access operations per memory"); + "Parses an Amalthea model to inspect label access operations per memory", + false); private static CloudServiceDefinition LABEL_TASK = new CloudServiceDefinition( "label_task_interpreter", "Label per Task Interpreter", "http://localhost:8084/app4mc/interpreter/label-per-task/", - "Parses an Amalthea model to inspect label access operations per task"); + "Parses an Amalthea model to inspect label access operations per task", + false); private static CloudServiceDefinition LABEL_SIZE = new CloudServiceDefinition( "label_size_interpreter", "Label Size Interpreter", "http://localhost:8084/app4mc/interpreter/label-size/", - "Parses an Amalthea model to inspect the number of labels grouped by size"); + "Parses an Amalthea model to inspect the number of labels grouped by size", + false); private static CloudServiceDefinition CHART_VISUALIZER = new CloudServiceDefinition( "chart_visualizer", "Chart Visualizer", "http://localhost:8083/app4mc/visualization/barchart/", - "Visualization of interpreter results"); + "Visualization of interpreter results", + false); @BeforeAll public static void beforeAll() { @@ -510,4 +522,165 @@ public class WorkflowStatusHelperTest { assertEquals(1, serviceNode.getChildren().size()); } + + @Test + public void shouldDeserializeMigrationServiceDefinition() throws IOException { + Path resourceDirectory = Paths.get("src", "test", "resources", "conf_def_migration.json"); + StringBuilder builder = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(resourceDirectory)))) { + String line; + while ((line = br.readLine()) != null) { + builder.append(line).append("\n"); + } + } + + ServiceConfigurationDefinition definition = + WorkflowStatusHelper.loadServiceConfigurationDefinition(builder.toString()); + + assertEquals("Migrates an input model file to model version 1.0.0", definition.getDescription()); + + assertEquals("amxmi", definition.getInputType()); + assertNull(definition.getInputVersion()); + assertTrue(definition.isInputArchiveSupported()); + + assertEquals("amxmi", definition.getOutputType()); + assertEquals("1.0.0", definition.getOutputVersion()); + assertTrue(definition.isOutputArchiveSupported()); + + assertEquals(1, definition.getParameterList().size()); + + ServiceConfigurationParameter versionParam = definition.getParameter("version"); + assertNotNull(versionParam); + assertEquals("version", versionParam.getKey()); + assertEquals("Output Model Version", versionParam.getName()); + assertEquals("The model version to which the input should be migrated to", versionParam.getDescription()); + assertEquals("1.0.0", versionParam.getValue()); + assertEquals("String", versionParam.getType()); + assertEquals("single", versionParam.getCardinality()); + assertFalse(versionParam.isMandatory()); + assertFalse(versionParam.isManagerParameter()); + + List<String> possibleValues = versionParam.getPossibleValues(); + assertNotNull(possibleValues); + assertEquals(4, possibleValues.size()); + assertTrue(possibleValues.contains("1.0.0")); + assertTrue(possibleValues.contains("0.9.9")); + assertTrue(possibleValues.contains("0.9.8")); + assertTrue(possibleValues.contains("0.9.7")); + } + + @Test + public void shouldDeserializeValidationServiceDefinition() throws IOException { + Path resourceDirectory = Paths.get("src", "test", "resources", "conf_def_validation.json"); + StringBuilder builder = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(resourceDirectory)))) { + String line; + while ((line = br.readLine()) != null) { + builder.append(line).append("\n"); + } + } + + ServiceConfigurationDefinition definition = + WorkflowStatusHelper.loadServiceConfigurationDefinition(builder.toString()); + + assertEquals("amxmi", definition.getInputType()); + assertEquals("1.0.0", definition.getInputVersion()); + assertTrue(definition.isInputArchiveSupported()); + + assertNull(definition.getOutputType()); + assertNull(definition.getOutputVersion()); + assertFalse(definition.isOutputArchiveSupported()); + + assertEquals(1, definition.getParameterList().size()); + + ServiceConfigurationParameter profilesParam = definition.getParameter("profiles"); + assertNotNull(profilesParam); + assertEquals("profiles", profilesParam.getKey()); + assertEquals("Validation profiles", profilesParam.getName()); + assertNull(profilesParam.getValue()); + assertEquals("String", profilesParam.getType()); + assertEquals("multiple", profilesParam.getCardinality()); + assertFalse(profilesParam.isMandatory()); + assertFalse(profilesParam.isManagerParameter()); + + List<String> possibleValues = profilesParam.getPossibleValues(); + assertNotNull(possibleValues); + assertEquals(3, possibleValues.size()); + assertTrue(possibleValues.contains("Amalthea")); + assertTrue(possibleValues.contains("INCHRON")); + assertTrue(possibleValues.contains("Timing Architects")); + } + + @Test + public void shouldDeserializeRtcServiceDefinition() throws IOException { + Path resourceDirectory = Paths.get("src", "test", "resources", "conf_def_rtc.json"); + StringBuilder builder = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(resourceDirectory)))) { + String line; + while ((line = br.readLine()) != null) { + builder.append(line).append("\n"); + } + } + + ServiceConfigurationDefinition definition = + WorkflowStatusHelper.loadServiceConfigurationDefinition(builder.toString()); + + assertEquals("amxmi", definition.getInputType()); + assertEquals("1.0.0", definition.getInputVersion()); + assertFalse(definition.isInputArchiveSupported()); + + assertEquals("json", definition.getOutputType()); + assertNull(definition.getOutputVersion()); + assertFalse(definition.isOutputArchiveSupported()); + + assertEquals(4, definition.getParameterList().size()); + + ServiceConfigurationParameter param = definition.getParameter("analysis-ascending-priorities"); + assertNotNull(param); + assertEquals("analysis-ascending-priorities", param.getKey()); + assertEquals("Ascending priorities", param.getName()); + assertNull(param.getValue()); + assertEquals("boolean", param.getType()); + assertEquals("single", param.getCardinality()); + assertFalse(param.isMandatory()); + assertFalse(param.isManagerParameter()); + + param = definition.getParameter("analysis-enable-flows"); + assertNotNull(param); + assertEquals("analysis-enable-flows", param.getKey()); + assertEquals("Include flow analysis", param.getName()); + assertNull(param.getValue()); + assertEquals("boolean", param.getType()); + assertEquals("single", param.getCardinality()); + assertFalse(param.isMandatory()); + assertFalse(param.isManagerParameter()); + + param = definition.getParameter("analysis-ignore-overutilization"); + assertNotNull(param); + assertEquals("analysis-ignore-overutilization", param.getKey()); + assertEquals("Ignore over utilization", param.getName()); + assertNull(param.getValue()); + assertEquals("boolean", param.getType()); + assertEquals("single", param.getCardinality()); + assertFalse(param.isMandatory()); + assertFalse(param.isManagerParameter()); + + param = definition.getParameter("analysis-time-unit"); + assertNotNull(param); + assertEquals("analysis-time-unit", param.getKey()); + assertEquals("Time unit", param.getName()); + assertNull(param.getValue()); + assertEquals("String", param.getType()); + assertEquals("single", param.getCardinality()); + assertFalse(param.isMandatory()); + assertFalse(param.isManagerParameter()); + + List<String> possibleValues = param.getPossibleValues(); + assertNotNull(possibleValues); + assertEquals(4, possibleValues.size()); + assertTrue(possibleValues.contains("s")); + assertTrue(possibleValues.contains("ms")); + assertTrue(possibleValues.contains("us")); + assertTrue(possibleValues.contains("ns")); + } } diff --git a/manager/src/test/resources/conf_def_migration.json b/manager/src/test/resources/conf_def_migration.json new file mode 100644 index 0000000..c894d25 --- /dev/null +++ b/manager/src/test/resources/conf_def_migration.json @@ -0,0 +1,20 @@ +{ + "description" : "Migrates an input model file to model version 1.0.0", + "input" : { + "type" : "amxmi", + "archive-supported" : true + }, + "output" : { + "type" : "amxmi", + "version" : "1.0.0", + "archive-supported" : true + }, + "parameter" : { + "version" : { + "name" : "Output Model Version", + "description" : "The model version to which the input should be migrated to", + "value" : "1.0.0", + "values" : ["1.0.0", "0.9.9", "0.9.8", "0.9.7"] + } + } +}
\ No newline at end of file diff --git a/manager/src/test/resources/conf_def_rtc.json b/manager/src/test/resources/conf_def_rtc.json new file mode 100644 index 0000000..842f360 --- /dev/null +++ b/manager/src/test/resources/conf_def_rtc.json @@ -0,0 +1,29 @@ +{ + "input" : { + "type" : "amxmi", + "version" : "1.0.0", + "archive-supported" : false + }, + "output" : { + "type" : "json", + "archive-supported" : false + }, + "parameter" : { + "analysis-ascending-priorities" : { + "name" : "Ascending priorities", + "type" : "boolean" + }, + "analysis-enable-flows" : { + "name" : "Include flow analysis", + "type" : "boolean" + }, + "analysis-ignore-overutilization" : { + "name" : "Ignore over utilization", + "type" : "boolean" + }, + "analysis-time-unit" : { + "name" : "Time unit", + "values" : ["s", "ms", "us", "ns"] + } + } +}
\ No newline at end of file diff --git a/manager/src/test/resources/conf_def_validation.json b/manager/src/test/resources/conf_def_validation.json new file mode 100644 index 0000000..c6ccc62 --- /dev/null +++ b/manager/src/test/resources/conf_def_validation.json @@ -0,0 +1,14 @@ +{ + "input" : { + "type" : "amxmi", + "version" : "1.0.0", + "archive-supported" : true + }, + "parameter" : { + "profiles" : { + "name" : "Validation profiles", + "cardinality" : "multiple", + "values" : ["Amalthea", "INCHRON", "Timing Architects"] + } + } +}
\ No newline at end of file diff --git a/org.eclipse.app4m.amlt2systemc.cloud/openapi.yaml b/org.eclipse.app4m.amlt2systemc.cloud/openapi.yaml new file mode 100644 index 0000000..3e41242 --- /dev/null +++ b/org.eclipse.app4m.amlt2systemc.cloud/openapi.yaml @@ -0,0 +1,198 @@ +openapi: 3.0.0 +info: + version: 0.1.0 + title: APP4MC amlt to SystemC Transformation API + description: APP4MC Transformation API to transform an Amalthea model file into simulation code. + +servers: + - url: http://localhost:8080/app4mc/validation + - url: https://app4mc.eclipseprojects.io/app4mc/amlt2systemc + +paths: + /: + post: + summary: Upload the file to transform and start the transformation process asynchronously + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + responses: + '201': + description: Upload succeeded and transformation process started + content: + application/json: + schema: + type: object + properties: + id: + type: string + description: ID of the created transformation resource + headers: + Location: + schema: + type: string + format: uri + description: The URI to the status URL + links: + status: + operationId: getStatus + parameters: + statusId: '$response.body#/id' + description: > + The `id` value returned in the response can be used as + the `statusId` parameter in `GET /{statusId}`. + '400': + description: No model file provided as upload + '404': + description: Upload failed + + /{statusId}: + get: + summary: Get the status of the triggered transformation process + operationId: getStatus + parameters: + - in: path + name: statusId + required: true + schema: + type: string + responses: + '200': + description: Processing finished successfully + headers: + Cache-Control: + schema: + type: string + enum: + - private, no-store, no-cache, must-revalidate + links: + result: + operationId: getDownload + parameters: + statusId: $request.path.statusId + '202': + description: Transformation process in progress + headers: + Cache-Control: + schema: + type: string + enum: + - private, no-store, no-cache, must-revalidate + '204': + description: Processing finished with an error + headers: + Cache-Control: + schema: + type: string + enum: + - private, no-store, no-cache, must-revalidate + links: + result: + operationId: getError + parameters: + statusId: $request.path.statusId + '404': + description: Resource not available + + delete: + summary: Delete the uploaded and result resource from the server + operationId: deleteResource + parameters: + - in: path + name: statusId + required: true + schema: + type: string + responses: + '200': + description: Resource deleted successfully + '404': + description: Resource not available + + /{statusId}/download: + get: + summary: Download the transformation result file + operationId: getDownload + parameters: + - in: path + name: statusId + required: true + schema: + type: string + responses: + '200': + description: Transformation successful + content: + application/octet-stream: + schema: + type: string + format: binary + links: + delete: + operationId: deleteResource + parameters: + statusId: $request.path.statusId + '400': + description: Transformation finished with errors + content: + application/octet-stream: + schema: + type: string + format: binary + links: + delete: + operationId: deleteResource + parameters: + statusId: $request.path.statusId + '404': + description: Progress still running / Resource not available / No result available + content: + application/json: + schema: + type: string + + /{statusId}/error: + get: + summary: Download the error file of the transformation process + operationId: getError + parameters: + - in: path + name: statusId + required: true + schema: + type: string + responses: + '200': + description: Error occured + content: + application/octet-stream: + schema: + type: string + format: binary + links: + delete: + operationId: deleteResource + parameters: + statusId: $request.path.statusId + '404': + description: Resource not available / No error occured + content: + application/json: + schema: + type: string + + /config: + get: + summary: Get the configuration definition of the service + responses: + '200': + description: The configuration definition of the service + content: + application/json: + schema: + type: string diff --git a/org.eclipse.app4m.amlt2systemc.cloud/org.eclipse.app4mc.amlt2systemc.cloud.http/META-INF/MANIFEST.MF b/org.eclipse.app4m.amlt2systemc.cloud/org.eclipse.app4mc.amlt2systemc.cloud.http/META-INF/MANIFEST.MF index abc415b..de0d068 100644 --- a/org.eclipse.app4m.amlt2systemc.cloud/org.eclipse.app4mc.amlt2systemc.cloud.http/META-INF/MANIFEST.MF +++ b/org.eclipse.app4m.amlt2systemc.cloud/org.eclipse.app4mc.amlt2systemc.cloud.http/META-INF/MANIFEST.MF @@ -7,8 +7,10 @@ Automatic-Module-Name: org.eclipse.app4mc.validation.cloud.http Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: com.fasterxml.jackson.core;version="2.9.9", com.fasterxml.jackson.databind;version="2.9.93", + com.fasterxml.jackson.databind.node;version="2.9.93", javax.servlet;version="3.1.0", javax.servlet.http;version="3.1.0", + org.eclipse.app4mc.amalthea.model, org.osgi.framework;version="1.9.0", org.osgi.service.component.annotations;version="1.3.0";resolution:=optional, org.osgi.service.event;version="1.4.0", diff --git a/org.eclipse.app4m.amlt2systemc.cloud/org.eclipse.app4mc.amlt2systemc.cloud.http/src/org/eclipse/app4mc/amlt2systemc/cloud/http/TransformationServlet.java b/org.eclipse.app4m.amlt2systemc.cloud/org.eclipse.app4mc.amlt2systemc.cloud.http/src/org/eclipse/app4mc/amlt2systemc/cloud/http/TransformationServlet.java index 2051956..aa74ed2 100644 --- a/org.eclipse.app4m.amlt2systemc.cloud/org.eclipse.app4mc.amlt2systemc.cloud.http/src/org/eclipse/app4mc/amlt2systemc/cloud/http/TransformationServlet.java +++ b/org.eclipse.app4m.amlt2systemc.cloud/org.eclipse.app4mc.amlt2systemc.cloud.http/src/org/eclipse/app4mc/amlt2systemc/cloud/http/TransformationServlet.java @@ -1,5 +1,5 @@ /********************************************************************************* - * Copyright (c) 2020 Robert Bosch GmbH and others. + * Copyright (c) 2020, 2021 Robert Bosch GmbH and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -38,6 +38,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; +import org.eclipse.app4mc.amalthea.model.AmaltheaPackage; import org.eclipse.app4mc.transformation.ServiceConstants; import org.eclipse.app4mc.transformation.TransformationProcessor; import org.osgi.framework.BundleContext; @@ -53,6 +54,10 @@ import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + @Component( service=Servlet.class, property= { @@ -74,6 +79,18 @@ public class TransformationServlet extends HttpServlet { private static final String ERROR_FILE = "error.txt"; + private static final String MODEL_VERSION; + static { + // Extracting namespace from AmaltheaPackage + String nsURI = AmaltheaPackage.eNS_URI; + + // Extracting AMALTHEA metamodel version + MODEL_VERSION = nsURI.lastIndexOf('/') != -1 + ? nsURI.substring(nsURI.lastIndexOf('/') + 1) + : nsURI; + + } + private final String defaultBaseDir = System.getProperty("java.io.tmpdir"); private ExecutorService executor = Executors.newFixedThreadPool(1); @@ -212,6 +229,7 @@ public class TransformationServlet extends HttpServlet { return; } + // GET /app4mc/amlt2systemc/config // GET /app4mc/amlt2systemc/{id} // GET /app4mc/amlt2systemc/{id}/download // GET /app4mc/amlt2systemc/{id}/error @@ -229,7 +247,33 @@ public class TransformationServlet extends HttpServlet { return; } - if (splitPath.length == 2) { + if (splitPath.length == 2 && "config".equals(splitPath[1])) { + response.setContentType("application/json"); + ObjectMapper mapper = new ObjectMapper(); + + ObjectNode config = mapper.createObjectNode(); + config.put("description", "Transform an Amalthea model to simulation code."); + + ObjectNode input = mapper.createObjectNode(); + input.put("type", "amxmi"); + input.put("version", MODEL_VERSION); + input.put("archive-supported", true); + config.set("input", input); + + ObjectNode output = mapper.createObjectNode(); + output.put("type", "app4mc-sim-model"); + output.put("archive-supported", true); + config.set("output", output); + + try { + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(config); + response.getWriter().write(json); + } catch (JsonProcessingException e) { + response.getWriter().write(mapper.writeValueAsString(e)); + } + + return; + } else if (splitPath.length == 2) { response.setHeader("Cache-Control", "private, no-store, no-cache, must-revalidate"); String uuid = splitPath[1]; diff --git a/org.eclipse.app4mc.converter.cloud/converter-service/src/main/java/org/eclipse/app4mc/org/eclipse/app4mc/converter/cloud/MigrationRestService.java b/org.eclipse.app4mc.converter.cloud/converter-service/src/main/java/org/eclipse/app4mc/org/eclipse/app4mc/converter/cloud/MigrationRestService.java index 6800b89..c438c52 100644 --- a/org.eclipse.app4mc.converter.cloud/converter-service/src/main/java/org/eclipse/app4mc/org/eclipse/app4mc/converter/cloud/MigrationRestService.java +++ b/org.eclipse.app4mc.converter.cloud/converter-service/src/main/java/org/eclipse/app4mc/org/eclipse/app4mc/converter/cloud/MigrationRestService.java @@ -1,5 +1,5 @@ /********************************************************************************* - * Copyright (c) 2020 Robert Bosch GmbH and others. + * Copyright (c) 2020, 2021 Robert Bosch GmbH and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -13,13 +13,16 @@ */ package org.eclipse.app4mc.org.eclipse.app4mc.converter.cloud; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -64,6 +67,11 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + @Component(service=MigrationRestService.class) @JaxrsResource @Produces(MediaType.APPLICATION_JSON) @@ -88,6 +96,47 @@ public class MigrationRestService { @Reference MigrationProcessor migrationProcessor; + @GET + @Path("config") + public String config() { + ObjectMapper mapper = new ObjectMapper(); + + ObjectNode config = mapper.createObjectNode(); + config.put("description", "Migrate the input model file to the specified output model version."); + + ObjectNode input = mapper.createObjectNode(); + input.put("type", "amxmi"); + input.put("archive-supported", true); + config.set("input", input); + + ObjectNode output = mapper.createObjectNode(); + output.put("type", "amxmi"); + output.put("version", ModelVersion.getLatestVersion()); + output.put("archive-supported", true); + config.set("output", output); + + ObjectNode parameter = mapper.createObjectNode(); + ObjectNode version = mapper.createObjectNode(); + version.put("name", "Output Model Version"); + version.put("description", "The model version to which the input should be migrated to."); + // the default value to use is the latest supported version + version.put("value", ModelVersion.getLatestVersion()); + ArrayNode values = version.putArray("values"); + + List<String> versions = ModelVersion.getAllSupportedVersions(); + Collections.reverse(versions); + versions.forEach(v -> values.add(v)); + + parameter.set("version", version); + config.set("parameter", parameter); + + try { + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(config); + } catch (JsonProcessingException e) { + return "Error in generating configuration definition: " + e.getMessage(); + } + } + @POST @Consumes(MediaType.MULTIPART_FORM_DATA) public Response upload(@Context HttpServletRequest request, @Context UriInfo uriInfo, @Context ServletContext context) throws IOException, ServletException { @@ -111,11 +160,12 @@ public class MigrationRestService { // mark uuid in progress getRegistry(context).put(uuid, PROGRESS_MARKER); + // check if the output model version was provided as parameter + String outputModelVersion = getOutputModelVersion(request); + // trigger asynchronous processing executor.execute(() -> { - String outputModelVersion = ModelVersion.getLatestVersion(); - try (MigrationSettings migrationSettings = new MigrationSettings()) { migrationSettings.setProject(uploaded.getParent().toFile()); migrationSettings.setMigrationModelVersion(outputModelVersion); @@ -157,6 +207,19 @@ public class MigrationRestService { .build(); } + private String getOutputModelVersion(HttpServletRequest request) throws IOException, ServletException { + String outputModelVersion = ModelVersion.getLatestVersion(); + + Part versionPart = request.getPart("version"); + if (versionPart != null) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(versionPart.getInputStream()))) { + outputModelVersion = reader.readLine(); + } + } + + return outputModelVersion; + } + @Path("{uuid}") @GET public Response status(@PathParam("uuid") String uuid, @Context UriInfo uriInfo, @Context ServletContext context) throws IOException { diff --git a/org.eclipse.app4mc.converter.cloud/openapi.yaml b/org.eclipse.app4mc.converter.cloud/openapi.yaml index 78d0491..07bdbaa 100644 --- a/org.eclipse.app4mc.converter.cloud/openapi.yaml +++ b/org.eclipse.app4mc.converter.cloud/openapi.yaml @@ -1,11 +1,12 @@ openapi: 3.0.0 info: - version: 0.9.9 + version: 1.0.0 title: APP4MC Migration API description: APP4MC Model Migration API to migrate older model versions to the current one servers: - url: http://localhost:8080/app4mc/converter + - url: https://app4mc.eclipseprojects.io/app4mc/converter paths: /: @@ -20,6 +21,9 @@ paths: file: type: string format: binary + version: + type: string + description: The version to which the model should be converted to. Parameter is optional and defaults to the latest supported version in this service. responses: '201': description: Upload succeeded and migration process started @@ -172,3 +176,14 @@ paths: application/json: schema: type: string + + /config: + get: + summary: Get the configuration definition of the service + responses: + '200': + description: The configuration definition of the service + content: + application/json: + schema: + type: string diff --git a/org.eclipse.app4mc.validation.cloud/openapi.yaml b/org.eclipse.app4mc.validation.cloud/openapi.yaml index 61f8369..44aed55 100644 --- a/org.eclipse.app4mc.validation.cloud/openapi.yaml +++ b/org.eclipse.app4mc.validation.cloud/openapi.yaml @@ -1,11 +1,12 @@ openapi: 3.0.0 info: - version: 0.9.9 + version: 1.0.0 title: APP4MC Validation API description: APP4MC Validation API to validate an Amalthea model file servers: - url: http://localhost:8080/app4mc/validation + - url: https://app4mc.eclipseprojects.io/app4mc/validation paths: /: @@ -20,6 +21,9 @@ paths: file: type: string format: binary + profiles: + type: string + description: The validation profiles to execute. Parameter is optional and defaults to the Amalthea Standard Validations profile. responses: '201': description: Upload succeeded and validation process started @@ -171,7 +175,7 @@ paths: /{statusId}/error: get: - summary: Download the error file of the migration process + summary: Download the error file of the validation process operationId: getError parameters: - in: path @@ -198,3 +202,14 @@ paths: application/json: schema: type: string + + /config: + get: + summary: Get the configuration definition of the service + responses: + '200': + description: The configuration definition of the service + content: + application/json: + schema: + type: string diff --git a/org.eclipse.app4mc.validation.cloud/org.eclipse.app4mc.validation.cloud.http/META-INF/MANIFEST.MF b/org.eclipse.app4mc.validation.cloud/org.eclipse.app4mc.validation.cloud.http/META-INF/MANIFEST.MF index 799db2f..0e57afe 100644 --- a/org.eclipse.app4mc.validation.cloud/org.eclipse.app4mc.validation.cloud.http/META-INF/MANIFEST.MF +++ b/org.eclipse.app4mc.validation.cloud/org.eclipse.app4mc.validation.cloud.http/META-INF/MANIFEST.MF @@ -7,6 +7,7 @@ Automatic-Module-Name: org.eclipse.app4mc.validation.cloud.http Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: com.fasterxml.jackson.core;version="2.9.9", com.fasterxml.jackson.databind;version="2.9.93", + com.fasterxml.jackson.databind.node;version="2.9.93", javax.servlet;version="3.1.0", javax.servlet.http;version="3.1.0", org.osgi.service.component.annotations;version="[1.3.0,1.4.0)";resolution:=optional, diff --git a/org.eclipse.app4mc.validation.cloud/org.eclipse.app4mc.validation.cloud.http/src/org/eclipse/app4mc/validation/cloud/http/ValidationServlet.java b/org.eclipse.app4mc.validation.cloud/org.eclipse.app4mc.validation.cloud.http/src/org/eclipse/app4mc/validation/cloud/http/ValidationServlet.java index 8d02b69..a7f2499 100644 --- a/org.eclipse.app4mc.validation.cloud/org.eclipse.app4mc.validation.cloud.http/src/org/eclipse/app4mc/validation/cloud/http/ValidationServlet.java +++ b/org.eclipse.app4mc.validation.cloud/org.eclipse.app4mc.validation.cloud.http/src/org/eclipse/app4mc/validation/cloud/http/ValidationServlet.java @@ -1,5 +1,5 @@ /********************************************************************************* - * Copyright (c) 2020 Robert Bosch GmbH and others. + * Copyright (c) 2020, 2021 Robert Bosch GmbH and others. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -45,6 +45,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import org.eclipse.app4mc.amalthea.model.Amalthea; +import org.eclipse.app4mc.amalthea.model.AmaltheaPackage; import org.eclipse.app4mc.amalthea.model.io.AmaltheaLoader; import org.eclipse.app4mc.validation.core.IProfile; import org.eclipse.app4mc.validation.util.ProfileManager; @@ -57,6 +58,8 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; @Component( service=Servlet.class, @@ -82,6 +85,18 @@ public class ValidationServlet extends HttpServlet { private static final String PASSED_MARKER = "passed"; private static final String FAILED_MARKER = "failed"; + private static final String MODEL_VERSION; + static { + // Extracting namespace from AmaltheaPackage + String nsURI = AmaltheaPackage.eNS_URI; + + // Extracting AMALTHEA metamodel version + MODEL_VERSION = nsURI.lastIndexOf('/') != -1 + ? nsURI.substring(nsURI.lastIndexOf('/') + 1) + : nsURI; + + } + private final String defaultBaseDir = System.getProperty("java.io.tmpdir"); private ExecutorService executor = Executors.newFixedThreadPool(1); @@ -244,6 +259,7 @@ public class ValidationServlet extends HttpServlet { } // GET /app4mc/validation/profiles + // GET /app4mc/validation/config // GET /app4mc/validation/{id} // GET /app4mc/validation/{id}/download // GET /app4mc/validation/{id}/error @@ -276,6 +292,47 @@ public class ValidationServlet extends HttpServlet { } return; + } else if (splitPath.length == 2 && "config".equals(splitPath[1])) { + response.setContentType("application/json"); + ObjectMapper mapper = new ObjectMapper(); + + ObjectNode config = mapper.createObjectNode(); + config.put("description", "Validate the input model file."); + + ObjectNode input = mapper.createObjectNode(); + input.put("type", "amxmi"); + input.put("version", MODEL_VERSION); + input.put("archive-supported", true); + config.set("input", input); + + // the validation produces no output that is consumed by a following service + + ObjectNode parameter = mapper.createObjectNode(); + ObjectNode profiles = mapper.createObjectNode(); + profiles.put("name", "Validation Profiles"); + profiles.put("description", "The validation profiles that should be executed.\nIf nothing is selected the Amalthea Standard Validations profile is executed."); + profiles.put("cardinality", "multiple"); + // the default value to use is the Amalthea Standard Validations profile + profiles.put("value", "Amalthea Standard Validations"); + ArrayNode values = profiles.putArray("values"); + + List<String> availableProfiles = manager.getRegisteredValidationProfiles().values().stream() + .sorted((p1, p2) -> p1.getName().compareTo(p2.getName())) + .map(profile -> profile.getName()) + .collect(Collectors.toList()); + availableProfiles.forEach(v -> values.add(v)); + + parameter.set("profiles", profiles); + config.set("parameter", parameter); + + try { + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(config); + response.getWriter().write(json); + } catch (JsonProcessingException e) { + response.getWriter().write(mapper.writeValueAsString(e)); + } + + return; } else if (splitPath.length == 2) { response.setHeader("Cache-Control", "private, no-store, no-cache, must-revalidate"); |