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;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
/**
* Class defining entry file for zip containing complex model (consisting of
* many submodels and other files).
......@@ -10,6 +12,7 @@ import java.util.Map;
* @author Piotr Gawron
*
*/
@JsonDeserialize(using = ZipEntryFileDeserializer.class)
public abstract class ZipEntryFile {
/**
* Name of the file in zip archive.
......@@ -24,7 +27,7 @@ public abstract class ZipEntryFile {
* # key=value
* </pre>
*/
private Map<String, String> headerParameters = new HashMap<String, String>();
private Map<String, String> headerParameters = new HashMap<>();
/**
* 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;
import java.io.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
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.ZipOutputStream;
import org.apache.logging.log4j.LogManager;
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.security.access.AccessDeniedException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
......@@ -17,6 +24,7 @@ import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson;
......@@ -26,7 +34,9 @@ import lcsb.mapviewer.common.Configuration;
import lcsb.mapviewer.common.MimeType;
import lcsb.mapviewer.common.exception.InvalidStateException;
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 {
private Logger logger = LogManager.getLogger();
......@@ -55,6 +65,7 @@ public abstract class BaseController {
|| e instanceof HttpMessageNotReadableException
|| e instanceof MissingServletRequestParameterException
|| e instanceof HttpMediaTypeNotSupportedException
|| e instanceof JsonMappingException
|| e instanceof MethodArgumentTypeMismatchException) {
logger.debug(e, e);
return createErrorResponse("Query server error.", e.getMessage(), new HttpHeaders(), HttpStatus.BAD_REQUEST);
......
package lcsb.mapviewer.api.projects;
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.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
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.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
......@@ -26,40 +33,57 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
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.api.BaseController;
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.cache.FileEntry;
import lcsb.mapviewer.model.cache.UploadedFileEntry;
import lcsb.mapviewer.model.graphics.MapCanvasType;
import lcsb.mapviewer.model.map.MiriamData;
import lcsb.mapviewer.model.map.layout.ProjectBackground;
import lcsb.mapviewer.model.security.PrivilegeType;
import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.services.ObjectExistsException;
import lcsb.mapviewer.services.ObjectNotFoundException;
import lcsb.mapviewer.services.QueryException;
import lcsb.mapviewer.services.interfaces.IFileService;
import lcsb.mapviewer.services.interfaces.IMeshService;
import lcsb.mapviewer.services.interfaces.IProjectService;
import lcsb.mapviewer.services.interfaces.IUserService;
import lcsb.mapviewer.services.utils.CreateProjectParams;
@RestController
@RequestMapping(value = "/api/projects", produces = MediaType.APPLICATION_JSON_VALUE)
public class ProjectController extends BaseController {
private Logger logger = LogManager.getLogger();
private ServletContext context;
private ProjectRestImpl projectController;
private IProjectService projectService;
private IUserService userService;
private IFileService fileService;
private IMeshService meshService;
private ObjectMapper objectMapper = new ObjectMapper();
private List<Converter> modelConverters;
@Autowired
public ProjectController(ServletContext context, ProjectRestImpl projectController, IUserService userService, IProjectService projectService,
IMeshService meshService) {
IMeshService meshService, List<Converter> modelConverters, IFileService fileService) {
this.context = context;
this.projectController = projectController;
this.userService = userService;
this.projectService = projectService;
this.meshService = meshService;
this.modelConverters = modelConverters;
this.fileService = fileService;
}
@PreAuthorize("hasAnyAuthority('IS_ADMIN', 'READ_PROJECT:' + #projectId)")
......@@ -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')")
@PostMapping(value = "/{projectId:.+}")
public Project addProject(
Authentication authentication,
@RequestBody MultiValueMap<String, Object> formData,
@RequestParam Map<String, Object> body,
@PathVariable(value = "projectId") String projectId) throws IOException, QueryException, SecurityException {
fixFormUrlEncodedBody(body);
CreateProjectDTO data = objectMapper.readValue(objectMapper.writeValueAsString(body), CreateProjectDTO.class);
if (projectId.equals("*")) {
throw new QueryException("No.");
}
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.READ_PROJECT, projectId);
userService.grantPrivilegeToAllUsersWithDefaultAccess(PrivilegeType.READ_PROJECT, projectId);
userService.grantPrivilegeToAllUsersWithDefaultAccess(PrivilegeType.WRITE_PROJECT, projectId);
userService.grantUserPrivilege(user, PrivilegeType.WRITE_PROJECT,
projectId);
userService.grantUserPrivilege(user, PrivilegeType.READ_PROJECT,
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')" +
......@@ -372,4 +559,19 @@ public class ProjectController extends BaseController {
public void setContext(ServletContext 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
package lcsb.mapviewer.api.projects;
import java.io.IOException;
import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
......@@ -21,9 +18,7 @@ import org.springframework.util.MultiValueMap;
import lcsb.mapviewer.api.BaseRestImpl;
import lcsb.mapviewer.api.OperationNotAllowedException;
import lcsb.mapviewer.api.projects.ProjectController.PrivilegeDTO;
import lcsb.mapviewer.common.comparator.StringComparator;
import lcsb.mapviewer.converter.Converter;
import lcsb.mapviewer.converter.zip.GlyphZipEntryFile;
import lcsb.mapviewer.converter.zip.ImageZipEntryFile;
import lcsb.mapviewer.converter.zip.LayoutZipEntryFile;
......@@ -32,25 +27,17 @@ import lcsb.mapviewer.converter.zip.ZipEntryFile;
import lcsb.mapviewer.model.Project;
import lcsb.mapviewer.model.ProjectLogEntry;
import lcsb.mapviewer.model.cache.FileEntry;
import lcsb.mapviewer.model.cache.UploadedFileEntry;
import lcsb.mapviewer.model.graphics.MapCanvasType;
import lcsb.mapviewer.model.map.MiriamType;
import lcsb.mapviewer.model.map.layout.ProjectBackground;
import lcsb.mapviewer.model.map.model.SubmodelType;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.user.ConfigurationElementType;
import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.persist.dao.ProjectDao;
import lcsb.mapviewer.persist.dao.cache.UploadedFileEntryDao;
import lcsb.mapviewer.persist.dao.map.species.ElementProperty;
import lcsb.mapviewer.services.ObjectExistsException;
import lcsb.mapviewer.services.ObjectNotFoundException;
import lcsb.mapviewer.services.QueryException;
import lcsb.mapviewer.services.interfaces.IElementService;
import lcsb.mapviewer.services.interfaces.IProjectService;
import lcsb.mapviewer.services.interfaces.IReactionService;
import lcsb.mapviewer.services.interfaces.IUserService;
import lcsb.mapviewer.services.utils.CreateProjectParams;
@Transactional
@Service
......@@ -77,26 +64,15 @@ public class ProjectRestImpl extends BaseRestImpl {
*/
private Logger logger = LogManager.getLogger();
private IProjectService projectService;
private IUserService userService;
private IElementService elementService;
private IReactionService reactionService;
private ProjectDao projectDao;
private UploadedFileEntryDao uploadedFileEntryDao;
public ProjectRestImpl(IProjectService projectService,
ProjectDao projectDao,
UploadedFileEntryDao uploadedFileEntryDao,
IUserService userService,
public ProjectRestImpl(ProjectDao projectDao,
IElementService elementService,
IReactionService reactionService) {
this.projectService = projectService;
this.projectDao = projectDao;
this.userService = userService;
this.uploadedFileEntryDao = uploadedFileEntryDao;
this.elementService = elementService;
this.reactionService = reactionService;
}
......@@ -137,90 +113,6 @@ public class ProjectRestImpl extends BaseRestImpl {
return result;
}
public Project addProject(String projectId, MultiValueMap<String, Object> data, String path, User user)
throws QueryException, IOException, SecurityException {
Project project = getProjectService().getProjectByProjectId(projectId);
if (project != null) {
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, path);
String fileId = getFirstValue(data.get("file-id"));
if (fileId == null) {
throw new QueryException("file-id is obligatory");
}
UploadedFileEntry file = uploadedFileEntryDao.getById(super.parseInteger(fileId, "fileId"));
if (file == null) {
throw new QueryException("Invalid file id: " + fileId);
}
if (file.getOwner() == null || !file.getOwner().getLogin().equals(user.getLogin())) {
throw new SecurityException("Access denied to source file");
}
String parserClass = getFirstValue(data.get("parser"));
if (parserClass == null) {