Added handling of configuration parameter

Change-Id: I1f8b52c321528ddd40a1b9079234017665cae64e
Signed-off-by: Dirk Fauth <Dirk.Fauth@de.bosch.com>
diff --git a/manager/pom.xml b/manager/pom.xml
index bec8421..52b57cf 100644
--- a/manager/pom.xml
+++ b/manager/pom.xml
@@ -62,7 +62,7 @@
 		<dependency>
 		    <groupId>com.konghq</groupId>
 		    <artifactId>unirest-java</artifactId>
-		    <version>3.7.04</version>
+		    <version>3.11.01</version>
 		</dependency>
 		
 		<dependency>
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
new file mode 100644
index 0000000..1f21f09
--- /dev/null
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfiguration.java
@@ -0,0 +1,39 @@
+/*********************************************************************************
+ * Copyright (c) 2020 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;
+
+public class ServiceConfiguration {
+
+	private final String serviceName;
+	private ArrayList<ServiceConfigurationParameter> parameter = new ArrayList<>();
+	
+	public ServiceConfiguration(String serviceName) {
+		this.serviceName = serviceName;
+	}
+	
+	public String getServiceName() {
+		return serviceName;
+	}
+	
+	public void addParameter(ServiceConfigurationParameter param) {
+		this.parameter.add(param);
+	}
+	
+	public List<ServiceConfigurationParameter> getParameterList() {
+		return this.parameter;
+	}
+}
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
new file mode 100644
index 0000000..a96ebbf
--- /dev/null
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceConfigurationParameter.java
@@ -0,0 +1,85 @@
+/*********************************************************************************
+ * Copyright (c) 2020 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;
+
+public class ServiceConfigurationParameter {
+
+	private String name;
+	private String key;
+	private String value;
+	private String type;
+	private String cardinality = "single";
+	private boolean mandatory = false;
+	private List<String> possibleValues = new ArrayList<>();
+	
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+	
+	public String getValue() {
+		return value;
+	}
+	
+	public void setValue(String value) {
+		this.value = value;
+	}
+	
+	public String getType() {
+		return type;
+	}
+	
+	public void setType(String type) {
+		this.type = type;
+	}
+	
+	public String getCardinality() {
+		return cardinality;
+	}
+	
+	public void setCardinality(String cardinality) {
+		this.cardinality = cardinality;
+	}
+	
+	public boolean isMandatory() {
+		return mandatory;
+	}
+	
+	public void setMandatory(boolean mandatory) {
+		this.mandatory = mandatory;
+	}
+
+	public List<String> getPossibleValues() {
+		return possibleValues;
+	}
+
+	public void setPossibleValues(List<String> possibleValues) {
+		this.possibleValues = possibleValues;
+	}
+	
+}
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
index 8766f29..efa796f 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java
@@ -20,7 +20,9 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.http.HttpStatus;
 import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition;
@@ -47,6 +49,7 @@
 import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
 
 import kong.unirest.HttpResponse;
+import kong.unirest.MultipartBody;
 import kong.unirest.Unirest;
 
 @Controller
@@ -65,28 +68,6 @@
 
 	@GetMapping("/workflow")
 	public String workflow(Model model) {
-		
-		// TODO move this to a configuration resource mechanism
-		CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
-				.filter(sd -> sd.getName().equals("Validation"))
-				.findFirst()
-				.orElse(null);
-		
-		List<?> allProfiles = new ArrayList<>();
-		if (csd != null) {
-			try {
-				String baseUrl = csd.getBaseUrl();
-				if (!baseUrl.endsWith("/")) {
-					baseUrl += "/";
-				}
-				allProfiles = Unirest.get(baseUrl + "profiles").asJson().getBody().getArray().toList();
-			} catch (Exception e) {
-				// do nothing, we will handle configurations in a different way in the future
-			}
-		}
-
-		model.addAttribute("allProfiles", allProfiles);
-		
 		// render the form view
 		return "workflow";
 	}
@@ -104,8 +85,65 @@
 
 		ws.addSelectedService(selected);
 		
-		// TODO init service configuration
-		
+		if ("Validation".equals(selected)) {
+			// TODO general: check if the selected service has a configuration URL
+			// TODO build configuration based on provided service configuration
+			CloudServiceDefinition csd = this.cloudServiceDefinitions.stream()
+					.filter(sd -> sd.getName().equals("Validation"))
+					.findFirst()
+					.orElse(null);
+			
+			if (csd != null) {
+				List<String> allProfiles = new ArrayList<>();
+				try {
+					String baseUrl = csd.getBaseUrl();
+					if (!baseUrl.endsWith("/")) {
+						baseUrl += "/";
+					}
+					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()) {
+					ServiceConfiguration config = new ServiceConfiguration(selected);
+					
+					ServiceConfigurationParameter validationProfiles = new ServiceConfigurationParameter();
+					validationProfiles.setName("Validation profiles");
+					validationProfiles.setKey("profiles");
+					validationProfiles.setType("String");
+					validationProfiles.setCardinality("multiple");
+					validationProfiles.setPossibleValues(allProfiles);
+					config.addParameter(validationProfiles);
+					
+					ws.addConfiguration(selected, config);
+				}
+			}
+		} else if ("RTC Analysis".equals(selected)) {
+			ServiceConfiguration config = new ServiceConfiguration(selected);
+			
+			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 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);
+			
+			ws.addConfiguration(selected, config);
+		}
 		
 		// render the form view
 		return "workflow";
@@ -118,6 +156,8 @@
 		
 		ws.removeSelectedService(selected);
 		
+		ws.removeConfiguration(selected);
+		
 		// render the form view
 		return "workflow";
 	}
@@ -149,12 +189,13 @@
 	@PostMapping("/workflow")
 	public String handleFileUpload(
 			@RequestParam("file") MultipartFile file, 
-			@RequestParam(name = "profiles", required = false) String[] validationProfiles, 
 			Model model, 
 			@ModelAttribute WorkflowStatus ws) {
 		
 		if (ws == null) {
 			ws = new WorkflowStatus();
+		} else {
+			ws.clearResults();
 		}
 		final WorkflowStatus workflowStatus = ws;
 
@@ -249,9 +290,28 @@
 			}
 			
 			// upload to service
-			HttpResponse<?> uploadResponse = Unirest.post(baseUrl)
+			MultipartBody multipartBody = Unirest.post(baseUrl)
 					.field("file", Files.newInputStream(inputFile), originalFilename)
-					.asEmpty();
+					.field("values", Arrays.asList("value1", "value2"));
+			
+			ServiceConfiguration config = workflowStatus.getConfiguration(serviceName); 
+			if (config != null) {
+				config.getParameterList().forEach(param -> {
+					if (!StringUtils.isEmpty(param.getValue())) {
+						if ("multiple".equals(param.getCardinality())) {
+							// TODO remove query string once equinox multipart is fixed
+							multipartBody.queryString(param.getKey(), Arrays.asList(param.getValue().split(",")));
+							multipartBody.field(param.getKey(), Arrays.asList(param.getValue().split(",")));
+						} else {
+							// TODO remove query string once equinox multipart is fixed
+							multipartBody.queryString(param.getKey(), param.getValue());
+							multipartBody.field(param.getKey(), param.getValue());
+						}
+					}
+				});
+			}
+			
+			HttpResponse<?> uploadResponse = multipartBody.asEmpty();
 			
 			// extract status link from result
 			String statusUrl = null;
@@ -396,7 +456,8 @@
 										"serveFile", 
 										workflowStatus.getUuid(),
 										"_" + serviceName.toLowerCase(),
-										migrationError.getFileName().toString()).build().toUri().toString());
+										migrationError.getFileName().toString(),
+										null).build().toUri().toString());
 						
 						// extract delete
 						deleteUrl = getUrlFromLink(errorResponse.getHeaders().get("Link"), "delete", baseUrl);
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java
index 1e7eae3..1f972ae 100644
--- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java
+++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java
@@ -21,6 +21,7 @@
 
 	private String uuid;
 	private ArrayList<String> selectedServices = new ArrayList<>();
+	private HashMap<String, ServiceConfiguration> serviceConfigurations = new LinkedHashMap<>();
 	private ArrayList<String> messages = new ArrayList<>();
 	private ArrayList<String> errors = new ArrayList<>();
 	private HashMap<String, String> results = new LinkedHashMap<>();
@@ -45,6 +46,22 @@
 		this.selectedServices.remove(service);
 	}
 	
+	public ServiceConfiguration getConfiguration(String key) {
+		return this.serviceConfigurations.get(key);
+	}
+	
+	public HashMap<String, ServiceConfiguration> getConfigurations() {
+		return this.serviceConfigurations;
+	}
+	
+	public void addConfiguration(String key, ServiceConfiguration config) {
+		this.serviceConfigurations.put(key, config);
+	}
+
+	public void removeConfiguration(String key) {
+		this.serviceConfigurations.remove(key);
+	}
+	
 	public ArrayList<String> getMessages() {
 		return messages;
 	}
@@ -69,8 +86,15 @@
 		this.results.put(key, resultFile);
 	}
 	
+	public void clearResults() {
+		this.messages.clear();
+		this.errors.clear();
+		this.results.clear();
+	}
+	
 	public void clear() {
 		this.selectedServices.clear();
+		this.serviceConfigurations.clear();
 		this.messages.clear();
 		this.errors.clear();
 		this.results.clear();
diff --git a/manager/src/main/resources/templates/selectedServices.html b/manager/src/main/resources/templates/selectedServices.html
index 71058a5..60fc357 100644
--- a/manager/src/main/resources/templates/selectedServices.html
+++ b/manager/src/main/resources/templates/selectedServices.html
@@ -5,7 +5,7 @@
 </head>
 
 <body>
-    <div th:fragment="servicesList" id="selectedServices">
+    <div th:fragment="servicesList" id="selectedServices" th:object="${workflowStatus}">
 		<ul class="list-group"> 
 			<li th:each="selected : ${workflowStatus.selectedServices}" class="list-group-item d-flex justify-content-between">
 				<p class="p-0 m-0 flex-grow-1" th:text="${selected}">Service</p> 
@@ -21,6 +21,50 @@
 		    	th:selected="${service.name == selected}">
 		    </option>
 		</select><br>
+		
+		<div th:if="not *{configurations.isEmpty()}">
+			<div th:each="config : *{configurations}" class="mb-3">
+				<h4 th:text="${config.key + ' Configuration'}">Config</h4>
+				<div th:each="parameter, parameterStatus : ${config.value.parameterList}">
+					<!-- multiple possible values + cardinality multiple = checkboxes -->
+					<div th:if="${parameter.cardinality == 'multiple' and parameter.possibleValues.size() > 1}">
+						<label th:text="${parameter.name}">Label</label>
+						<div th:each="pv : ${parameter.possibleValues}" class="form-check">
+							<input
+								class="form-check-input"
+								type="checkbox"
+								th:field="*{configurations[__${config.key}__].parameterList[__${parameterStatus.index}__].value}"
+								th:value="${pv}">
+							<label class="form-check-label" th:text="${pv}">Profile</label>
+						</div>
+					</div>
+					<!-- multiple possible values + cardinality single = combobox -->
+					<div th:if="${parameter.cardinality == 'single' and parameter.possibleValues.size() > 1}">
+						<label th:text="${parameter.name}">Label</label>
+						<select
+							class="custom-select"
+							th:field="*{configurations[__${config.key}__].parameterList[__${parameterStatus.index}__].value}">
+							<option value=""></option>
+						    <option 
+						    	th:each="pv : ${parameter.possibleValues}"  
+						    	th:text="${pv}"
+						    	th:value="${pv}">
+						    </option>
+						</select>
+					</div>
+					<!-- single boolean value -->
+					<div th:if="${parameter.cardinality == 'single' and parameter.type == 'boolean'}" class="form-check">
+						<input
+							class="form-check-input"
+							type="checkbox"
+							th:field="*{configurations[__${config.key}__].parameterList[__${parameterStatus.index}__].value}"
+							value="true">
+						<label class="form-check-label" th:text="${parameter.name}" th:for="${parameter.key}">Label</label>
+					</div>
+				</div>
+			</div>
+		</div>
+		
     </div>
 </body>
 </html>
diff --git a/manager/src/main/resources/templates/workflow.html b/manager/src/main/resources/templates/workflow.html
index fdb7fef..08100a5 100644
--- a/manager/src/main/resources/templates/workflow.html
+++ b/manager/src/main/resources/templates/workflow.html
@@ -61,7 +61,7 @@
 			</div>
 		</div>
 
-		<form method="POST" enctype="multipart/form-data" action="/workflow">
+		<form method="POST" enctype="multipart/form-data" action="/workflow" th:object="${workflowStatus}">
 			<div class="form-row mb-3">
 				<div class="custom-file">
 					<input type="file" class="custom-file-input" id="customFile" name="file">
@@ -85,19 +85,6 @@
 					</div>
 				</div>
 			</div>
-			<table>
-				<tr th:if="${allProfiles != null and not allProfiles.isEmpty()}">
-					<td valign="top">Select validations to perform:</td>
-					<td>
-						<ul>
-							<li th:each="profile : ${allProfiles}">
-								<input type="checkbox" name="profiles" th:value="${profile}" />
-								<label th:text="${profile}">Amalthea</label>
-							</li>
-						</ul>
-					</td>
-				</tr>
-			</table>
 			<div class="form-row">
 				<div class="col text-center">
 					<input type="submit" value="Start workflow" class="btn btn-primary mt-2"/>
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 82c54e8..8d02b69 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
@@ -13,16 +13,19 @@
  */
 package org.eclipse.app4mc.validation.cloud.http;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
@@ -141,6 +144,8 @@
     				ServletContext context = getServletContext();
     				Map<String, String> registry = getRegistry(context);
     				registry.put(uuid, PROGRESS_MARKER);
+
+		    		List<String> selectedProfiles = getSelectedProfiles(request);
     				
     				// trigger asynchronous processing
     				executor.execute(() -> {
@@ -155,10 +160,6 @@
         		    			return;
         		    		}
         		    		
-        		    		// get profile selection out of request
-        		    		String[] profiles = request.getParameterValues("profiles");
-        		    		List<String> selectedProfiles = profiles != null ? Arrays.asList(profiles) : Arrays.asList("Amalthea Standard Validations");
-        		    		
         		    		// get selected profiles from profile manager
         		    		List<Class<? extends IProfile>> profileList = manager.getRegisteredValidationProfiles().values().stream()
         		    			.filter(profile -> selectedProfiles.contains(profile.getName()))
@@ -214,6 +215,34 @@
     	return;
     }
     
+    private List<String> getSelectedProfiles(HttpServletRequest request) throws IOException, ServletException {
+
+    	// first check if the profiles are sent as query parameter
+    	String[] profiles = request.getParameterValues("profiles");
+    	if (profiles != null) {
+    		return Arrays.asList(profiles);
+    	} else {
+    		// check if the profiles are sent as post parameter in the multipart request
+    		List<String> collected = new ArrayList<>();
+			for (Part supportPart : request.getParts()) {
+				if (supportPart.getName().equals("profiles")) {
+					try (BufferedReader reader = new BufferedReader(new InputStreamReader(supportPart.getInputStream()))) {
+						List<String> collect = reader.lines().collect(Collectors.toList());
+						if (collect != null && !collect.isEmpty()) {
+							collected.addAll(collect);
+						}
+					}
+				}
+			}
+    		if (!collected.isEmpty()) {
+    			return collected;
+    		}
+    	}
+    	
+    	// neither query parameter nor multipart post parameter found, return default
+    	return Arrays.asList("Amalthea Standard Validations");
+    }
+    
     // GET /app4mc/validation/profiles
     // GET /app4mc/validation/{id}
     // GET /app4mc/validation/{id}/download
@@ -222,7 +251,7 @@
     @Override
     protected void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
- 
+
 		response.addHeader("Link", "<" + request.getRequestURL() + ">;rel=\"self\"");
 
     	String[] splitPath = validatePath(request.getPathInfo());