blob: 4a479528e488e98abfade5ca5e997eb99244f62c [file] [log] [blame]
/*********************************************************************************
* 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.validation.cloud.http;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.eclipse.app4mc.amalthea.model.Amalthea;
import org.eclipse.app4mc.amalthea.model.io.AmaltheaLoader;
import org.eclipse.app4mc.validation.core.IProfile;
import org.eclipse.app4mc.validation.util.ProfileManager;
import org.eclipse.app4mc.validation.util.ValidationExecutor;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ServiceScope;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component(
service=Servlet.class,
property= {
"osgi.http.whiteboard.servlet.pattern=/app4mc/validation/*",
"osgi.http.whiteboard.servlet.multipart.enabled=true"
},
scope=ServiceScope.PROTOTYPE)
public class ValidationServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String TEMP_DIR_PREFIX = "app4mc_validation_";
private final String defaultBaseDir = System.getProperty("java.io.tmpdir");
@Reference
private ProfileManager manager;
private String[] validatePath(String pathInfo) {
// request to /app4mc/validation
if (pathInfo == null || pathInfo.equals("/")){
return null;
}
String[] splitPath = pathInfo.split("/");
if (splitPath.length > 3) {
return null;
}
return splitPath;
}
// POST /app4mc/validation/upload
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String[] splitPath = validatePath(request.getPathInfo());
if (splitPath == null || splitPath.length != 2) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid path");
return;
}
if ("upload".equals(splitPath[1])){
Part part = request.getPart("file");
if (part != null) {
String filename = part.getSubmittedFileName();
try (InputStream is = part.getInputStream()) {
Path path = Files.createTempDirectory(TEMP_DIR_PREFIX);
// extract uuid from pathname
String uuid = path.toString().substring(path.toString().lastIndexOf('_') + 1);
Files.copy(is, Paths.get(path.toString(), filename));
// return uuid
response.setContentType("text/html");
response.getWriter().write(uuid);
return;
}
}
}
// no content
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
// PUT /app4mc/validation/{id}/validate
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String[] splitPath = validatePath(request.getPathInfo());
if (splitPath == null || splitPath.length != 3) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid path");
return;
}
String uuid = splitPath[1];
String action = splitPath[2];
if ("validate".equals(action)) {
// trigger validation
Path tempFolderPath = Paths.get(defaultBaseDir, TEMP_DIR_PREFIX + uuid);
List<Path> modelFilePaths =
Files.find(tempFolderPath, 1, (path, attrs) -> path.toString().endsWith(".amxmi"))
.collect(Collectors.toList());
if (modelFilePaths.isEmpty()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "No model file uploaded!");
return;
}
Path modelFilePath = modelFilePaths.get(0);
// load uploaded model file
Amalthea model = AmaltheaLoader.loadFromFile(modelFilePath.toFile());
if (model == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Error: No model loaded!");
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()))
.map(profile -> profile.getProfileClass())
.collect(Collectors.toList());
// TODO execute in background thread and introduce status resource?
ValidationExecutor executor = new ValidationExecutor(profileList);
executor.validate(model);
// dump validation result to file
Path resultFile = Files.createFile(Paths.get(tempFolderPath.toString(), "validation-results.txt"));
try (PrintStream print = new PrintStream(new FileOutputStream(resultFile.toFile()))) {
executor.dumpResultMap(print);
}
boolean failed = executor.getResults().stream()
.anyMatch(diag -> diag.getSeverity() == org.eclipse.emf.common.util.Diagnostic.ERROR);
if (!failed) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
}
return;
}
// no content
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
// GET /app4mc/validation/profiles
// GET /app4mc/validation/{id}/download
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String[] splitPath = validatePath(request.getPathInfo());
if (splitPath == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid path");
return;
}
if (splitPath.length == 2 && "profiles".equals(splitPath[1])) {
response.setContentType("application/json");
ObjectMapper objectMapper = new ObjectMapper();
try {
List<String> availableProfiles = manager.getRegisteredValidationProfiles().values().stream()
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.map(profile -> profile.getName())
.collect(Collectors.toList());
String json = objectMapper.writeValueAsString(availableProfiles);
response.getWriter().write(json);
} catch (JsonProcessingException e) {
response.getWriter().write(objectMapper.writeValueAsString(e));
}
return;
} else if (splitPath.length == 3 && "download".equals(splitPath[2])) {
Path path = Paths.get(defaultBaseDir, TEMP_DIR_PREFIX + splitPath[1], "validation-results.txt");
response.setContentType("text/plain");
response.setHeader("Content-Disposition","attachment; filename=validation-results.txt");
try (InputStream in = Files.newInputStream(path);
OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[4096];
int numBytesRead;
while ((numBytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, numBytesRead);
}
}
}
// no content
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
// DELETE /app4mc/validation/{id}/delete
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String[] splitPath = validatePath(request.getPathInfo());
if (splitPath == null || splitPath.length != 3) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid path");
return;
}
String uuid = splitPath[1];
String action = splitPath[2];
if ("delete".equals(action)) {
Path path = Paths.get(defaultBaseDir, TEMP_DIR_PREFIX + uuid);
Files.walk(path)
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
response.setStatus(HttpServletResponse.SC_OK);
return;
}
// no content
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}
}