Commit fd009c44 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

addProject moved to controller

parent 27a44382
package lcsb.mapviewer.converter.zip;
import com.fasterxml.jackson.core.JsonProcessingException;
public class DeserializationException extends JsonProcessingException {
protected DeserializationException(String msg) {
super(msg);
}
/**
*
*/
private static final long serialVersionUID = 1L;
}
...@@ -3,6 +3,8 @@ package lcsb.mapviewer.converter.zip; ...@@ -3,6 +3,8 @@ package lcsb.mapviewer.converter.zip;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
/** /**
* Class defining entry file for zip containing complex model (consisting of * Class defining entry file for zip containing complex model (consisting of
* many submodels and other files). * many submodels and other files).
...@@ -10,6 +12,7 @@ import java.util.Map; ...@@ -10,6 +12,7 @@ import java.util.Map;
* @author Piotr Gawron * @author Piotr Gawron
* *
*/ */
@JsonDeserialize(using = ZipEntryFileDeserializer.class)
public abstract class ZipEntryFile { public abstract class ZipEntryFile {
/** /**
* Name of the file in zip archive. * Name of the file in zip archive.
...@@ -24,7 +27,7 @@ public abstract class ZipEntryFile { ...@@ -24,7 +27,7 @@ public abstract class ZipEntryFile {
* # key=value * # key=value
* </pre> * </pre>
*/ */
private Map<String, String> headerParameters = new HashMap<String, String>(); private Map<String, String> headerParameters = new HashMap<>();
/** /**
* Default constructor. * Default constructor.
......
package lcsb.mapviewer.converter.zip;
import java.io.IOException;
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.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lcsb.mapviewer.common.exception.NotImplementedException;
import lcsb.mapviewer.model.map.model.SubmodelType;
public class ZipEntryFileDeserializer extends StdDeserializer<ZipEntryFile> {
/**
*
*/
private static final long serialVersionUID = 1L;
public ZipEntryFileDeserializer() {
this(null);
}
public ZipEntryFileDeserializer(Class<?> vc) {
super(vc);
}
@Override
public ZipEntryFile deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
ObjectNode rootNode = mapper.readTree(parser);
String filename = rootNode.get("_filename").asText();
String name = getValue(rootNode, "_data.name", "");
switch (rootNode.get("_type").asText()) {
case ("MAP"): {
String submodelTypeKey = getValue(rootNode, "_data.type.id", SubmodelType.UNKNOWN.name());
String rootKey = getValue(rootNode, "_data.root", "false");
String mappingKey = getValue(rootNode, "_data.mapping", "false");
SubmodelType mapType = SubmodelType.valueOf(submodelTypeKey);
Boolean root = "true".equalsIgnoreCase(rootKey);
Boolean mapping = "true".equalsIgnoreCase(mappingKey);
return new ModelZipEntryFile(filename, name, root, mapping, mapType);
}
case ("OVERLAY"): {
String description = getValue(rootNode, "_data.description", "");
if (name.trim().isEmpty()) {
throw new DeserializationException("overlay name cannot be empty");
}
return new LayoutZipEntryFile(filename, name, description);
}
case ("IMAGE"): {
return new ImageZipEntryFile(filename);
}
case ("GLYPH"): {
return new GlyphZipEntryFile(filename);
}
default:
throw new NotImplementedException("Unknown type: " + rootNode.get("_type"));
}
}
private String getValue(JsonNode rootNode, String path, String defaultValue) {
JsonNode node = rootNode;
for (String segment : path.split("\\.")) {
node = node.get(segment);
if (node == null) {
return defaultValue;
}
}
return node.asText(defaultValue);
}
}
package lcsb.mapviewer.api; package lcsb.mapviewer.api;
import java.io.*; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.*; import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.springframework.http.*; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpMediaTypeNotSupportedException;
...@@ -17,6 +24,7 @@ import org.springframework.web.bind.ServletRequestBindingException; ...@@ -17,6 +24,7 @@ import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson; import com.google.gson.Gson;
...@@ -26,7 +34,9 @@ import lcsb.mapviewer.common.Configuration; ...@@ -26,7 +34,9 @@ import lcsb.mapviewer.common.Configuration;
import lcsb.mapviewer.common.MimeType; import lcsb.mapviewer.common.MimeType;
import lcsb.mapviewer.common.exception.InvalidStateException; import lcsb.mapviewer.common.exception.InvalidStateException;
import lcsb.mapviewer.model.cache.FileEntry; import lcsb.mapviewer.model.cache.FileEntry;
import lcsb.mapviewer.services.*; import lcsb.mapviewer.services.ObjectExistsException;
import lcsb.mapviewer.services.ObjectNotFoundException;
import lcsb.mapviewer.services.QueryException;
public abstract class BaseController { public abstract class BaseController {
private Logger logger = LogManager.getLogger(); private Logger logger = LogManager.getLogger();
...@@ -55,6 +65,7 @@ public abstract class BaseController { ...@@ -55,6 +65,7 @@ public abstract class BaseController {
|| e instanceof HttpMessageNotReadableException || e instanceof HttpMessageNotReadableException
|| e instanceof MissingServletRequestParameterException || e instanceof MissingServletRequestParameterException
|| e instanceof HttpMediaTypeNotSupportedException || e instanceof HttpMediaTypeNotSupportedException
|| e instanceof JsonMappingException
|| e instanceof MethodArgumentTypeMismatchException) { || e instanceof MethodArgumentTypeMismatchException) {
logger.debug(e, e); logger.debug(e, e);
return createErrorResponse("Query server error.", e.getMessage(), new HttpHeaders(), HttpStatus.BAD_REQUEST); return createErrorResponse("Query server error.", e.getMessage(), new HttpHeaders(), HttpStatus.BAD_REQUEST);
......
package lcsb.mapviewer.api.projects; package lcsb.mapviewer.api.projects;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PatchMapping;
...@@ -26,40 +33,57 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -26,40 +33,57 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lcsb.mapviewer.annotation.services.annotators.AnnotatorException; import lcsb.mapviewer.annotation.services.annotators.AnnotatorException;
import lcsb.mapviewer.api.BaseController; import lcsb.mapviewer.api.BaseController;
import lcsb.mapviewer.common.exception.InvalidArgumentException; import lcsb.mapviewer.common.exception.InvalidArgumentException;
import lcsb.mapviewer.converter.Converter;
import lcsb.mapviewer.converter.zip.ZipEntryFile;
import lcsb.mapviewer.model.Project; import lcsb.mapviewer.model.Project;
import lcsb.mapviewer.model.cache.FileEntry; import lcsb.mapviewer.model.cache.FileEntry;
import lcsb.mapviewer.model.cache.UploadedFileEntry;
import lcsb.mapviewer.model.graphics.MapCanvasType; import lcsb.mapviewer.model.graphics.MapCanvasType;
import lcsb.mapviewer.model.map.MiriamData; import lcsb.mapviewer.model.map.MiriamData;
import lcsb.mapviewer.model.map.layout.ProjectBackground; import lcsb.mapviewer.model.map.layout.ProjectBackground;
import lcsb.mapviewer.model.security.PrivilegeType; import lcsb.mapviewer.model.security.PrivilegeType;
import lcsb.mapviewer.model.user.User; import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.services.ObjectExistsException;
import lcsb.mapviewer.services.ObjectNotFoundException; import lcsb.mapviewer.services.ObjectNotFoundException;
import lcsb.mapviewer.services.QueryException; import lcsb.mapviewer.services.QueryException;
import lcsb.mapviewer.services.interfaces.IFileService;
import lcsb.mapviewer.services.interfaces.IMeshService; import lcsb.mapviewer.services.interfaces.IMeshService;
import lcsb.mapviewer.services.interfaces.IProjectService; import lcsb.mapviewer.services.interfaces.IProjectService;
import lcsb.mapviewer.services.interfaces.IUserService; import lcsb.mapviewer.services.interfaces.IUserService;
import lcsb.mapviewer.services.utils.CreateProjectParams;
@RestController @RestController
@RequestMapping(value = "/api/projects", produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(value = "/api/projects", produces = MediaType.APPLICATION_JSON_VALUE)
public class ProjectController extends BaseController { public class ProjectController extends BaseController {
private Logger logger = LogManager.getLogger();
private ServletContext context; private ServletContext context;
private ProjectRestImpl projectController; private ProjectRestImpl projectController;
private IProjectService projectService; private IProjectService projectService;
private IUserService userService; private IUserService userService;
private IFileService fileService;
private IMeshService meshService; private IMeshService meshService;
private ObjectMapper objectMapper = new ObjectMapper();
private List<Converter> modelConverters;
@Autowired @Autowired
public ProjectController(ServletContext context, ProjectRestImpl projectController, IUserService userService, IProjectService projectService, public ProjectController(ServletContext context, ProjectRestImpl projectController, IUserService userService, IProjectService projectService,
IMeshService meshService) { IMeshService meshService, List<Converter> modelConverters, IFileService fileService) {
this.context = context; this.context = context;
this.projectController = projectController; this.projectController = projectController;
this.userService = userService; this.userService = userService;
this.projectService = projectService; this.projectService = projectService;
this.meshService = meshService; this.meshService = meshService;
this.modelConverters = modelConverters;
this.fileService = fileService;
} }
@PreAuthorize("hasAnyAuthority('IS_ADMIN', 'READ_PROJECT:' + #projectId)") @PreAuthorize("hasAnyAuthority('IS_ADMIN', 'READ_PROJECT:' + #projectId)")
...@@ -194,24 +218,187 @@ public class ProjectController extends BaseController { ...@@ -194,24 +218,187 @@ public class ProjectController extends BaseController {
} }
} }
static class CreateProjectDTO {
public String projectId;
@JsonProperty("file-id")
public Integer fileId;
public String parser;
@JsonProperty("auto-resize")
public String autoResize;
@JsonProperty("notify-email")
public String notifyEmail;
public String description;
public String cache;
public String disease;
public String name;
public String organism;
public String sbgn;
@JsonProperty("semantic-zoom-contains-multiple-layouts")
public String semanticZoomContainsMultipleLayouts;
public String version;
public MapCanvasType mapCanvasType;
public String annotate;
@JsonProperty("verify-annotations")
public String verifyAnnotations;
@JsonProperty("zip-entries")
public Map<String, ZipEntryFile> zipEntries;
}
@PreAuthorize("hasAnyAuthority('IS_ADMIN', 'IS_CURATOR')") @PreAuthorize("hasAnyAuthority('IS_ADMIN', 'IS_CURATOR')")
@PostMapping(value = "/{projectId:.+}") @PostMapping(value = "/{projectId:.+}")
public Project addProject( public Project addProject(
Authentication authentication, Authentication authentication,
@RequestBody MultiValueMap<String, Object> formData, @RequestParam Map<String, Object> body,
@PathVariable(value = "projectId") String projectId) throws IOException, QueryException, SecurityException { @PathVariable(value = "projectId") String projectId) throws IOException, QueryException, SecurityException {
fixFormUrlEncodedBody(body);
CreateProjectDTO data = objectMapper.readValue(objectMapper.writeValueAsString(body), CreateProjectDTO.class);
if (projectId.equals("*")) { if (projectId.equals("*")) {
throw new QueryException("No."); throw new QueryException("No.");
} }
User user = userService.getUserByLogin(authentication.getName()); User user = userService.getUserByLogin(authentication.getName());
Project project = projectController.addProject(projectId, formData, context.getRealPath("/"), user); if (projectService.projectExists(projectId)) {
throw new ObjectExistsException("Project with given id already exists");
}
if (projectId.length() > 255) {
throw new QueryException("projectId is too long");
}
CreateProjectParams params = new CreateProjectParams();
String directory = computePathForProject(projectId,
context.getRealPath("/"));
if (data.fileId == null) {
throw new QueryException("file-id is obligatory");
}
UploadedFileEntry file = fileService.getById(data.fileId);
if (file == null) {
throw new QueryException("Invalid file id: " + data.fileId);
}
if (file.getOwner() == null ||
!file.getOwner().getLogin().equals(user.getLogin())) {
throw new SecurityException("Access denied to source file");
}
if (data.parser == null) {
throw new QueryException("parser is obligatory");
}
Converter parser = getModelParser(data.parser);
if (data.zipEntries != null) {
params.complex(data.zipEntries.values().size() > 0);
for (ZipEntryFile entry : data.zipEntries.values()) {
params.addZipEntry(entry);
}
}
params.async(true);
params.parser(parser);
params.autoResize(data.autoResize);
params.cacheModel(data.cache);
params.description(data.description);
params.images(true);
params.notifyEmail(data.notifyEmail);
params.projectDir(directory);
params.projectDisease(data.disease);
params.projectFile(file);
params.projectId(projectId);
params.projectName(data.name);
if (params.getProjectName() != null && params.getProjectName().length() > 255) {
throw new QueryException("name is too long");
}
params.projectOrganism(data.organism);
params.sbgnFormat(data.sbgn);
params.semanticZoomContainsMultipleBackgrounds(data.semanticZoomContainsMultipleLayouts);
params.version(data.version);
if (params.getVersion() != null && params.getVersion().length() > 20) {
throw new QueryException("version is too long (>20 characters)");
}
params.annotations(data.annotate);
params.setUser(user);
params.mapCanvasType(data.mapCanvasType);
if (params.isUpdateAnnotations()) {
params.annotatorsMap(projectService.createClassAnnotatorTree(user));
}
params.analyzeAnnotations(data.verifyAnnotations);
if (params.isAnalyzeAnnotations()) {
params.requiredAnnotations(projectService.createClassAnnotatorTree(user));
params.validAnnotations(projectService.createClassAnnotatorTree(user));
}
projectService.createProject(params);
userService.grantUserPrivilege(user, PrivilegeType.WRITE_PROJECT, projectId); userService.grantUserPrivilege(user, PrivilegeType.WRITE_PROJECT,
userService.grantUserPrivilege(user, PrivilegeType.READ_PROJECT, projectId); projectId);
userService.grantPrivilegeToAllUsersWithDefaultAccess(PrivilegeType.READ_PROJECT, projectId); userService.grantUserPrivilege(user, PrivilegeType.READ_PROJECT,
userService.grantPrivilegeToAllUsersWithDefaultAccess(PrivilegeType.WRITE_PROJECT, projectId); projectId);
userService.grantPrivilegeToAllUsersWithDefaultAccess(PrivilegeType.READ_PROJECT,
projectId);
userService.grantPrivilegeToAllUsersWithDefaultAccess(PrivilegeType.WRITE_PROJECT,
projectId);
return getProject(projectId);
}
@SuppressWarnings("unchecked")
void fixFormUrlEncodedBody(Map<String, Object> body) {
int fixedKeys;
do {
fixedKeys = 0;
Set<String> keys = new HashSet<>(body.keySet());
for (String key : keys) {
if (key.endsWith("]")) {
int from = key.lastIndexOf("[") + 1;
int to = key.length() - 1;
String subkey = key.substring(from, to);
String rootKey = key.substring(0, from - 1);
Object value = body.get(key);
Map<String, Object> elementMap = (Map<String, Object>) body.get(rootKey);
if (elementMap == null) {
elementMap = new HashMap<>();
body.put(rootKey, elementMap);
}
if (elementMap.get(subkey) != null) {
if (elementMap.get(subkey) instanceof Map && value instanceof Map) {
((Map<String, Object>) value).putAll((Map<String, Object>) elementMap.get(subkey));
}
}
elementMap.put(subkey, value);
body.remove(key);
fixedKeys++;
}
}
} while (fixedKeys > 0);
}
return project; protected String computePathForProject(String projectId, String path) {
long id = projectService.getNextId();
return path + "/../map_images/" + md5(projectId + "-" + id) + "/";
}
/**
* Method that computes md5 hash for a given {@link String}.
*
* @param data
* input string
* @return md5 hash for input string
*/
private String md5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] mdbytes = md.digest(data.getBytes());
StringBuffer sb = new StringBuffer();
for (int i = 0; i < mdbytes.length; i++) {
// CHECKSTYLE:OFF
// this magic formula transforms integer into hex value
sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
// CHECKSTYLE:ON
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
logger.fatal("Problem with instance of MD5 encoder", e);
}
return null;
} }
@PreAuthorize("hasAuthority('IS_ADMIN')" + @PreAuthorize("hasAuthority('IS_ADMIN')" +
...@@ -372,4 +559,19 @@ public class ProjectController extends BaseController { ...@@ -372,4 +559,19 @@ public class ProjectController extends BaseController {
public void setContext(ServletContext context) { public void setContext(ServletContext context) {
this.context = context; this.context = context;
} }
protected Converter getModelParser(String handlerClass) throws QueryException {
for (Converter converter : modelConverters) {
if (converter.getClass().getCanonicalName().equals(handlerClass)) {
try {
return (Converter) Class.forName(handlerClass).getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
throw new QueryException("Problem with handler:" + handlerClass);
}
}
}
throw new QueryException("Unknown handlerClass: " + handlerClass);
}
} }
\ No newline at end of file