Commit 36e9b494 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

API endpoint for listing backgrounds

parent a79d4005
......@@ -10,9 +10,14 @@ import org.apache.logging.log4j.Logger;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lcsb.mapviewer.common.exception.NotImplementedException;
import lcsb.mapviewer.model.Project;
import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.modelutils.serializer.ProjectAsIdSerializer;
import lcsb.mapviewer.modelutils.serializer.UserAsIdSerializer;
/**
* This object represents type of visualization for the model.
......@@ -67,6 +72,7 @@ public class ProjectBackground implements Serializable {
/**
* Does the layout present data in hierarchical view.
*/
@JsonIgnore
private boolean hierarchicalView = false;
/**
......@@ -74,18 +80,18 @@ public class ProjectBackground implements Serializable {
* level. This parameter defines the level at which it's fixed or contains null
* if it's general hierarchical view.
*/
@JsonIgnore
private Integer hierarchyViewLevel;
/**
* Project to which this data overlay is assigned.
*/
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JsonSerialize(using = ProjectAsIdSerializer.class)
private Project project;
/**
* Who created the layout.
*/
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JsonSerialize(using = UserAsIdSerializer.class)
private User creator;
/**
......@@ -104,7 +110,7 @@ public class ProjectBackground implements Serializable {
/**
* List of image folders for generated background images.
*/
@Cascade({ CascadeType.SAVE_UPDATE})
@Cascade({ CascadeType.SAVE_UPDATE })
@OneToMany(fetch = FetchType.LAZY, mappedBy = "projectBackground", orphanRemoval = true)
private Set<ProjectBackgroundImageLayer> backgrounds = new HashSet<>();
......@@ -146,7 +152,8 @@ public class ProjectBackground implements Serializable {
this.hierarchyViewLevel = layout.hierarchyViewLevel;
this.orderIndex = layout.orderIndex;
for (ProjectBackgroundImageLayer dataOverlayImageLayer : layout.getProjectBackgroundImageLayer()) {
ProjectBackgroundImageLayer dataOverlayImageLayerCopy = new ProjectBackgroundImageLayer(dataOverlayImageLayer.getModel(),
ProjectBackgroundImageLayer dataOverlayImageLayerCopy = new ProjectBackgroundImageLayer(
dataOverlayImageLayer.getModel(),
dataOverlayImageLayer.getDirectory());
this.addProjectBackgroundImageLayer(dataOverlayImageLayerCopy);
}
......
......@@ -10,8 +10,12 @@ import org.apache.logging.log4j.Logger;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lcsb.mapviewer.model.map.model.Model;
import lcsb.mapviewer.model.map.model.ModelData;
import lcsb.mapviewer.modelutils.serializer.ModelDataAsIdSerializer;
import lcsb.mapviewer.modelutils.serializer.ProjectBackgroundAsIdSerializer;
/**
* This object represents set of images generated for background visualization
......@@ -55,9 +59,10 @@ public class ProjectBackgroundImageLayer implements Serializable {
/**
* {@link ModelData} for which the images were created.
*/
@ManyToOne(fetch = FetchType.LAZY, optional=false)
@JoinColumn(name="model_id", updatable=false)
@ManyToOne(optional = false)
@JoinColumn(name = "model_id", updatable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JsonSerialize(using = ModelDataAsIdSerializer.class)
private ModelData model;
/**
......@@ -65,6 +70,7 @@ public class ProjectBackgroundImageLayer implements Serializable {
*/
@ManyToOne(fetch = FetchType.LAZY)
@OnDelete(action = OnDeleteAction.CASCADE)
@JsonSerialize(using = ProjectBackgroundAsIdSerializer.class)
private ProjectBackground projectBackground;
/**
......
package lcsb.mapviewer.modelutils.serializer;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lcsb.mapviewer.model.map.model.ModelData;
public class ModelDataAsIdSerializer extends JsonSerializer<ModelData> {
@Override
public void serialize(final ModelData model, final JsonGenerator gen, final SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeNumberField("id", model.getId());
gen.writeEndObject();
}
}
\ No newline at end of file
package lcsb.mapviewer.modelutils.serializer;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lcsb.mapviewer.model.Project;
public class ProjectAsIdSerializer extends JsonSerializer<Project> {
@Override
public void serialize(final Project project, final JsonGenerator gen, final SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeStringField("projectId", project.getProjectId());
gen.writeEndObject();
}
}
\ No newline at end of file
package lcsb.mapviewer.modelutils.serializer;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lcsb.mapviewer.model.map.layout.ProjectBackground;
public class ProjectBackgroundAsIdSerializer extends JsonSerializer<ProjectBackground> {
@Override
public void serialize(final ProjectBackground background, final JsonGenerator gen,
final SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeNumberField("id", background.getId());
gen.writeEndObject();
}
}
\ No newline at end of file
package lcsb.mapviewer.modelutils.serializer;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lcsb.mapviewer.model.user.User;
public class UserAsIdSerializer extends JsonSerializer<User> {
@Override
public void serialize(final User user, final JsonGenerator gen, final SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
gen.writeStringField("login", user.getLogin());
gen.writeEndObject();
}
}
\ No newline at end of file
......@@ -3,20 +3,21 @@ package lcsb.mapviewer.api.projects;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.ServletContext;
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.access.prepost.*;
import org.springframework.security.core.Authentication;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import lcsb.mapviewer.api.*;
import lcsb.mapviewer.model.cache.FileEntry;
import lcsb.mapviewer.model.map.layout.ProjectBackground;
import lcsb.mapviewer.model.security.PrivilegeType;
import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.services.ObjectNotFoundException;
......@@ -159,6 +160,46 @@ public class ProjectController extends BaseController {
throws QueryException {
return projectController.getSubmapConnections(projectId);
}
@PreAuthorize("hasAnyAuthority('IS_ADMIN', 'READ_PROJECT:' + #projectId) "
+ "or not @projectService.projectExists(#projectId)")
@GetMapping(value = "/{projectId}/backgrounds/")
public List<ProjectBackground> getBackgrounds(
@PathVariable(value = "projectId") String projectId)
throws lcsb.mapviewer.services.ObjectNotFoundException {
return projectController.getBackgrounds(projectId);
}
@PreAuthorize("hasAnyAuthority('IS_ADMIN', 'READ_PROJECT:' + #projectId) "
+ "or not @projectService.projectExists(#projectId)")
@GetMapping(value = "/{projectId}/backgrounds/{backgroundId}")
public ProjectBackground getBackgroundById(
@PathVariable(value = "projectId") String projectId,
@PathVariable(value = "backgroundId") Integer backgroundId) throws QueryException {
return projectController.getBackgroundById(projectId, backgroundId);
}
@PreAuthorize("hasAuthority('IS_ADMIN')" +
"or hasAuthority('IS_CURATOR') and hasAuthority('WRITE_PROJECT:' + #projectId)")
@DeleteMapping(value = "/{projectId}/backgrounds/{backgroundId}")
public Map<String, Object> removeBackground(
@PathVariable(value = "projectId") String projectId,
@PathVariable(value = "backgroundId") Integer backgroundId) throws QueryException, IOException {
return projectController.removeBackground(projectId, backgroundId);
}
@PreAuthorize("hasAuthority('IS_ADMIN')" +
" or hasAuthority('IS_CURATOR') and hasAuthority('WRITE_PROJECT:' + #projectId)")
@PatchMapping(value = "/{projectId}/backgrounds/{backgroundId}")
public ProjectBackground updateBackground(
@RequestBody String body,
@PathVariable(value = "overlayId") Integer overlayId,
@PathVariable(value = "backgroundId") String projectId)
throws QueryException, IOException {
Map<String, Object> node = parseBody(body);
Map<String, Object> data = getData(node, "overlay");
return projectController.updateOverlay(projectId, overlayId, data);
}
public ServletContext getContext() {
return context;
......
......@@ -9,6 +9,7 @@ import java.util.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.Hibernate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MultiValueMap;
......@@ -28,6 +29,7 @@ import lcsb.mapviewer.model.cache.FileEntry;
import lcsb.mapviewer.model.cache.UploadedFileEntry;
import lcsb.mapviewer.model.graphics.MapCanvasType;
import lcsb.mapviewer.model.map.*;
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.security.PrivilegeType;
......@@ -755,4 +757,24 @@ public class ProjectRestImpl extends BaseRestImpl {
}
}
public List<ProjectBackground> getBackgrounds(String projectId) throws ObjectNotFoundException {
List<ProjectBackground> result = getProjectByProjectId(projectId).getProjectBackgrounds();
for (ProjectBackground projectBackground : result) {
Hibernate.initialize(projectBackground.getProjectBackgroundImageLayer());
}
return result;
}
public ProjectBackground getBackgroundById(String projectId, Integer backgroundId) {
throw new NotImplementedException();
}
public Map<String, Object> removeBackground(String projectId, Integer backgroundId) {
throw new NotImplementedException();
}
public ProjectBackground updateOverlay(String projectId, Integer overlayId, Map<String, Object> data) {
throw new NotImplementedException();
}
}
......@@ -9,6 +9,8 @@ import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import com.fasterxml.jackson.databind.ObjectMapper;
import lcsb.mapviewer.api.SpringRestApiConfig;
@Configuration
......@@ -56,4 +58,10 @@ public class SpringWebConfig implements WebMvcConfigurer {
.setViewName("index");
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
return mapper;
}
}
......@@ -49,6 +49,7 @@ import lcsb.mapviewer.model.cache.UploadedFileEntry;
import lcsb.mapviewer.model.graphics.*;
import lcsb.mapviewer.model.map.*;
import lcsb.mapviewer.model.map.kinetics.*;
import lcsb.mapviewer.model.map.layout.*;
import lcsb.mapviewer.model.map.layout.graphics.Glyph;
import lcsb.mapviewer.model.map.model.*;
import lcsb.mapviewer.model.map.reaction.*;
......@@ -354,6 +355,20 @@ abstract public class ControllerIntegrationTest {
project.addModel(map);
project.addModel(submap);
ProjectBackground background = new ProjectBackground("Normal");
int id = 0;
for (ModelData model : project.getModels()) {
ProjectBackgroundImageLayer layer = new ProjectBackgroundImageLayer(model, "dir_" + (id++));
background.addProjectBackgroundImageLayer(layer);
}
background.setDefaultOverlay(true);
background.setOrderIndex(0);
background.setProgress(100);
background.setStatus(ProjectBackgroundStatus.OK);
background.setCreator(userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN));
project.addProjectBackground(background);
projectDao.add(project);
return project;
}
......
......@@ -21,11 +21,14 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.restdocs.request.ParameterDescriptor;
import org.springframework.restdocs.request.RequestParametersSnippet;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.request.*;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.RequestBuilder;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonParser;
import lcsb.mapviewer.api.projects.ProjectRestImpl;
......@@ -33,6 +36,8 @@ import lcsb.mapviewer.model.Project;
import lcsb.mapviewer.model.ProjectStatus;
import lcsb.mapviewer.model.cache.UploadedFileEntry;
import lcsb.mapviewer.model.graphics.MapCanvasType;
import lcsb.mapviewer.model.map.layout.ProjectBackground;
import lcsb.mapviewer.model.map.layout.ProjectBackgroundStatus;
import lcsb.mapviewer.model.map.model.SubmodelType;
import lcsb.mapviewer.model.security.Privilege;
import lcsb.mapviewer.model.security.PrivilegeType;
......@@ -61,6 +66,9 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
@Autowired
private ProjectSnippets snippets;
@Autowired
private ObjectMapper objectMapper;
private User curator;
@Before
......@@ -107,7 +115,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request)
.andExpect(status().is2xxSuccessful())
.andDo(document("projects/project_data/get_source_file",
pathParameters(parameterWithName("projectId").description("project identifier"))));
projectPathParameters()));
}
......@@ -152,7 +160,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request)
.andDo(document("projects/project_data/get_project",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
snippets.getProjectSnippet()))
.andExpect(status().is2xxSuccessful());
}
......@@ -179,7 +187,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
parameterWithName("level").description("level of log entry (warning, error)").optional(),
parameterWithName("length").description("number of log entres we want to obtain").optional(),
parameterWithName("search").description("search query used for filtering").optional()),
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
responseFields(
subsectionWithPath("data")
.description("list of log entries")
......@@ -207,7 +215,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
RequestBuilder request = get("/projects/{projectId}/statistics", TEST_PROJECT).session(session);
mockMvc.perform(request).andDo(document("projects/project_data/get_project_statistics",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
responseFields(
subsectionWithPath("reactionAnnotations")
.description("list of reaction annotation types")
......@@ -238,7 +246,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request)
.andDo(document("projects/project_privileges/grant_privilege",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
requestFields(
fieldWithPath("[]")
.description("list of privileges to grant")
......@@ -276,7 +284,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request)
.andDo(document("projects/project_privileges/revoke_privilege",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
requestFields(
fieldWithPath("[]")
.description("list of privileges to revoke")
......@@ -324,7 +332,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request)
.andDo(document("projects/project_data/update",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
requestFields(
fieldWithPath("project.version")
.description("version of the project")
......@@ -488,7 +496,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request)
.andExpect(status().is2xxSuccessful())
.andDo(document("projects/project_maps/get_connections",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
responseFields(fieldWithPath("[]").description("list of connections").type("array"))));
}
......@@ -541,7 +549,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request).andExpect(status().is2xxSuccessful())
.andDo(document("projects/project_data/create_simple",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
createProjectRequestSnippet(),
snippets.getProjectSnippet()));
......@@ -604,7 +612,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request).andExpect(status().is2xxSuccessful())
.andDo(document("projects/project_data/create_zip",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
createProjectRequestSnippet(),
snippets.getProjectSnippet()));
......@@ -822,7 +830,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
mockMvc.perform(request).andExpect(status().is2xxSuccessful())
.andDo(document("projects/project_data/delete",
pathParameters(parameterWithName("projectId").description("project identifier")),
projectPathParameters(),
snippets.getProjectSnippet()));
} finally {
......@@ -940,4 +948,120 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
}
}
@Test
public void testGetBackgrounds() throws Exception {
MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
createProjectInSeparateThread(TEST_PROJECT);
RequestBuilder request = get("/projects/{projectId}/backgrounds/", TEST_PROJECT)
.session(session);
mockMvc.perform(request)
.andExpect(status().is2xxSuccessful())
.andDo(document("projects/project_data/get_backgrounds",
projectPathParameters(),
responseFields().andWithPrefix("[].", getBackgroundFields())));
}
private List<FieldDescriptor> getBackgroundFields() {
return Arrays.asList(
fieldWithPath("id")
.type(JsonFieldType.NUMBER)
.description("identifier"),
fieldWithPath("name")
.type(JsonFieldType.STRING)
.description("name"),
fieldWithPath("description")
.type(Arrays.asList(JsonFieldType.STRING, JsonFieldType.NULL))
.description("description"),
fieldWithPath("project.projectId")
.type(JsonFieldType.STRING)
.description("project id where this background belongs to"),
fieldWithPath("creator.login")
.type(JsonFieldType.STRING)
.description("who created background"),
fieldWithPath("status")
.type(JsonFieldType.STRING)
.description("is the background ready. Available statuses are: "
+ snippets.getOptionsAsString(ProjectBackgroundStatus.class)),
fieldWithPath("progress")
.type(JsonFieldType.NUMBER)
.description("generating images progress information (in %)"),
fieldWithPath("orderIndex")
.type(JsonFieldType.NUMBER)
.description("order used when listing all backgrounds"),
fieldWithPath("projectBackgroundImageLayer[].id")
.type(JsonFieldType.NUMBER)
.ignored(),
fieldWithPath("projectBackgroundImageLayer[].projectBackground.id")
.type(JsonFieldType.NUMBER)
.ignored(),
fieldWithPath("projectBackgroundImageLayer[].directory")
.type(JsonFieldType.STRING)
.description("directory where background tiles are located"),
fieldWithPath("projectBackgroundImageLayer[].model.id")
.type(JsonFieldType.NUMBER)
.description("(sub)map for which images are described"),
fieldWithPath("defaultOverlay")
.type(JsonFieldType.BOOLEAN)
.description(
"should the background be used as default (at most one per project should be marked with true)"));
}
@Test
public void testGetBackgroundById() throws Exception {
MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
Project project = createProjectInSeparateThread(TEST_PROJECT);
RequestBuilder request = get("/projects/{projectId}/backgrounds/{backgroundId}", TEST_PROJECT,
project.getProjectBackgrounds().get(0).getId())
.session(session);
mockMvc.perform(request)
.andExpect(status().is2xxSuccessful())
.andDo(document("projects/project_data/get_background_by_id",
projectPathParameters(),
responseFields()));
}
@Test
public void testUpdateBackgroundById() throws Exception {
MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
Project project = createProjectInSeparateThread(TEST_PROJECT);
ProjectBackground background = new ProjectBackground("weird_title");
background.setId(project.getProjectBackgrounds().get(0).getId());
RequestBuilder request = get("/projects/{projectId}/backgrounds/{backgroundId}", TEST_PROJECT,
project.getProjectBackgrounds().get(0).getId())
.session(session)
.content(objectMapper.writeValueAsString(background));
String response = mockMvc.perform(request)
.andExpect(status().is2xxSuccessful())
.andDo(document("projects/project_data/update_background",
projectPathParameters(),
responseFields()))
.andReturn().getResponse().getContentAsString();
ProjectBackground newBackground = objectMapper.readValue(response, new TypeReference<ProjectBackground>() {
});
assertEquals(background.getName(), newBackground.getName());
}
@Test
public void testRemoveBackgroundById() throws Exception {
MockHttpSession session = createSession(BUILT_IN_TEST_ADMIN_LOGIN, BUILT_IN_TEST_ADMIN_PASSWORD);
Project project = createProjectInSeparateThread(TEST_PROJECT);
RequestBuilder request = delete("/projects/{projectId}/backgrounds/{backgroundId}", TEST_PROJECT,
project.getProjectBackgrounds().get(0).getId())
.session(session);