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

Merge branch '1085-allow-to-fetch-modifier-type-by-api' into 'master'

Resolve "allow to fetch modifier type by API"

See merge request !1329
parents 2c649f66 ba100974
Pipeline #45978 failed with stage
in 36 minutes and 33 seconds
minerva (16.1.0~alpha.0) stable; urgency=medium
* Small improvement: API provides information about modifier type (#1085)
* Bug fix: api endpoints were exposed without 'api' prefix
* Bug fix: anonymous user could upload file using API
* Bug fix: when passing JSON in patch/post methods contentType was not
......
package lcsb.mapviewer.model.map.reaction;
import javax.persistence.*;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.modelutils.map.ElementUtils;
import lcsb.mapviewer.modelutils.serializer.model.map.reaction.ReactionNodeSerializer;
/**
* One of two known types of nodes in the {@link Reaction}. It defines input or
......@@ -22,6 +27,7 @@ import lcsb.mapviewer.modelutils.map.ElementUtils;
*/
@Entity
@DiscriminatorValue("GENERIC_REACTION_NODE")
@JsonSerialize(using = ReactionNodeSerializer.class)
public abstract class ReactionNode extends AbstractNode {
/**
......
......@@ -15,16 +15,9 @@ import javax.persistence.UniqueConstraint;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lcsb.mapviewer.modelutils.serializer.model.security.PrivilegeDeserializer;
import lcsb.mapviewer.modelutils.serializer.model.security.PrivilegeSerializer;
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "type", "objectId" }))
@JsonSerialize(using = PrivilegeSerializer.class)
@JsonDeserialize(using = PrivilegeDeserializer.class)
public class Privilege implements Serializable {
private static final long serialVersionUID = 1L;
......
package lcsb.mapviewer.modelutils.serializer.model.security;
package lcsb.mapviewer.modelutils.serializer.model.map.reaction;
import java.io.IOException;
......@@ -6,19 +6,19 @@ import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lcsb.mapviewer.model.security.Privilege;
import lcsb.mapviewer.model.map.reaction.ReactionNode;
public class PrivilegeSerializer extends JsonSerializer<Privilege> {
public class ReactionNodeSerializer extends JsonSerializer<ReactionNode> {
@Override
public void serialize(final Privilege entry, final JsonGenerator gen,
public void serialize(final ReactionNode node, final JsonGenerator gen,
final SerializerProvider serializers)
throws IOException {
if (entry.isObjectPrivilege()) {
gen.writeString(entry.getType()+":"+entry.getObjectId());
} else {
gen.writeString(entry.getType().name());
}
gen.writeStartObject();
gen.writeNumberField("aliasId", node.getElement().getId());
gen.writeObjectField("stoichiometry", node.getStoichiometry());
gen.writeStringField("type", node.getClass().getSimpleName());
gen.writeEndObject();
}
}
\ No newline at end of file
package lcsb.mapviewer.modelutils.serializer.model.security;
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.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lcsb.mapviewer.model.security.Privilege;
public class PrivilegeDeserializer extends StdDeserializer<Privilege> {
/**
*
*/
private static final long serialVersionUID = 1L;
public PrivilegeDeserializer() {
super(Privilege.class);
}
@Override
public Privilege deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
ObjectNode rootNode = mapper.readTree(parser);
return new PrivilegeKeyDeserializer().deserializeKey(rootNode.asText(), ctxt);
}
}
......@@ -284,6 +284,9 @@ public class ProjectController extends BaseController {
@RequestParam Map<String, Object> body,
@PathVariable(value = "projectId") String projectId) throws IOException, QueryException, SecurityException {
fixFormUrlEncodedBody(body);
if (Objects.equals(body.get("zip-entries"), "")) {
body.remove("zip-entries");
}
CreateProjectDTO data = objectMapper.readValue(objectMapper.writeValueAsString(body), CreateProjectDTO.class);
if (projectId.equals("*")) {
......
......@@ -36,7 +36,6 @@ import lcsb.mapviewer.model.map.reaction.NodeOperator;
import lcsb.mapviewer.model.map.reaction.Product;
import lcsb.mapviewer.model.map.reaction.Reactant;
import lcsb.mapviewer.model.map.reaction.Reaction;
import lcsb.mapviewer.model.map.reaction.ReactionNode;
import lcsb.mapviewer.modelutils.serializer.MathMLSerializer;
import lcsb.mapviewer.services.QueryException;
import lcsb.mapviewer.services.interfaces.IReactionService;
......@@ -113,27 +112,15 @@ public class ReactionsController extends BaseController {
value = pt.getPointOnLine(centerLine.getP1(), centerLine.getP2(), 0.5);
break;
case "products": {
List<Map<String, Object>> ids = new ArrayList<>();
for (Product product : reaction.getProducts()) {
ids.add(createReactionNode(product));
}
value = ids;
value = reaction.getProducts();
break;
}
case "reactants": {
List<Map<String, Object>> ids = new ArrayList<>();
for (Reactant reactant : reaction.getReactants()) {
ids.add(createReactionNode(reactant));
}
value = ids;
value = reaction.getReactants();
break;
}
case "modifiers": {
List<Map<String, Object>> ids = new ArrayList<>();
for (Modifier modifier : reaction.getModifiers()) {
ids.add(createReactionNode(modifier));
}
value = ids;
value = reaction.getModifiers();
break;
}
case "type":
......@@ -233,13 +220,6 @@ public class ReactionsController extends BaseController {
return result;
}
private Map<String, Object> createReactionNode(ReactionNode node) {
Map<String, Object> result = new TreeMap<>();
result.put("aliasId", node.getElement().getId());
result.put("stoichiometry", node.getStoichiometry());
return result;
}
private Map<String, Object> kineticsToMap(SbmlKinetics kinetics) {
if (kinetics == null) {
return null;
......
......@@ -109,6 +109,9 @@ public class UserController extends BaseController {
if (user == null) {
throw new ObjectNotFoundException("User doesn't exist");
}
if (user.getAnnotationSchema() == null) {
user.setAnnotationSchema(projectService.prepareUserAnnotationSchema(user, true));
}
Boolean ldapAvailable = false;
if (columnSet.contains("ldapAccountAvailable")) {
List<User> userList = new ArrayList<>();
......
......@@ -1382,4 +1382,9 @@ public class ProjectService implements IProjectService {
return new ArrayList<>();
}
}
@Override
public void add(Project project) {
projectDao.add(project);
}
}
......@@ -124,4 +124,6 @@ public interface IProjectService {
UploadedFileEntry getFileByProjectId(String projectId);
List<ProjectBackground> getBackgrounds(String projectId, boolean initializeLazy);
void add(Project project);
}
......@@ -22,7 +22,6 @@ import org.junit.runners.Suite.SuiteClasses;
ParameterControllerIntegrationTest.class,
PluginControllerIntegrationTest.class,
ProjectControllerIntegrationTest.class,
ProjectControllerIntegrationTestForAsyncCalls.class,
PublicationsControllerIntegrationTest.class,
ReactionControllerIntegrationTest.class,
SpringSecurityGeneralIntegrationTest.class,
......
......@@ -107,6 +107,7 @@ import lcsb.mapviewer.persist.dao.map.ModelDao;
import lcsb.mapviewer.persist.dao.user.ResetPasswordTokenDao;
import lcsb.mapviewer.persist.dao.user.UserDao;
import lcsb.mapviewer.services.interfaces.IDataOverlayService;
import lcsb.mapviewer.services.interfaces.IFileService;
import lcsb.mapviewer.services.interfaces.IProjectService;
import lcsb.mapviewer.services.interfaces.IUserService;
import lcsb.mapviewer.web.config.SpringWebConfig;
......@@ -161,6 +162,9 @@ abstract public class ControllerIntegrationTest {
@Autowired
private UploadedFileEntryDao fileDao;
@Autowired
private IFileService fileService;
@Autowired
private DataOverlayDao dataOverlayDao;
......@@ -444,7 +448,7 @@ abstract public class ControllerIntegrationTest {
background.setCreator(userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN));
project.addProjectBackground(background);
projectDao.add(project);
projectService.add(project);
return project;
}
......@@ -516,7 +520,7 @@ abstract public class ControllerIntegrationTest {
file.setOriginalFileName("test_file");
file.setLength(content.length);
file.setOwner(user);
fileDao.add(file);
fileService.add(file);
return file;
}
......
......@@ -16,6 +16,8 @@ import static org.springframework.restdocs.request.RequestDocumentation.pathPara
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
......@@ -30,6 +32,8 @@ 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.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.payload.ResponseFieldsSnippet;
import org.springframework.restdocs.request.PathParametersSnippet;
import org.springframework.restdocs.request.RequestParametersSnippet;
......@@ -229,62 +233,85 @@ public class MapControllerIntegrationTest extends ControllerIntegrationTest {
}
private ResponseFieldsSnippet listOfReactionResponseFields() {
return responseFields(
List<FieldDescriptor> fields = new ArrayList<>(Arrays.asList(
fieldWithPath("[].id")
.description("unique element identifier")
.type("number")
.type(JsonFieldType.NUMBER)
.optional(),
fieldWithPath("[].reactionId")
.description("reaction identifier taken from source file")
.type("string")
.type(JsonFieldType.STRING)
.optional(),
fieldWithPath("[].modelId")
.description("map identifier")
.type("number")
.type(JsonFieldType.NUMBER)
.optional(),
fieldWithPath("[].type")
.description("reaction type")
.type("string")
.type(JsonFieldType.STRING)
.optional(),
subsectionWithPath("[].centerPoint")
.description("center point")
.type("point")
.type(JsonFieldType.OBJECT)
.optional(),
subsectionWithPath("[].lines")
.description("list of lines used to draw reaction")
.type("array<line>")
.type(JsonFieldType.ARRAY)
.optional(),
fieldWithPath("[].hierarchyVisibilityLevel")
.description("at what zoom level this element becomes visible in hierarchical view")
.type("string")
.type(JsonFieldType.STRING)
.optional(),
subsectionWithPath("[].references")
.description("list of references")
.type("array<Reference>")
.type(JsonFieldType.ARRAY)
.optional(),
subsectionWithPath("[].modifiers")
fieldWithPath("[].modifiers")
.description("list of modifiers")
.type("array<object>")
.type(JsonFieldType.ARRAY)
.optional(),
subsectionWithPath("[].reactants")
fieldWithPath("[].reactants")
.description("list of reactants")
.type("array<object>")
.type(JsonFieldType.ARRAY)
.optional(),
subsectionWithPath("[].products")
fieldWithPath("[].products")
.description("list of products")
.type("array<object>")
.type(JsonFieldType.ARRAY)
.optional(),
fieldWithPath("[].notes")
.description("notes and description")
.type("string")
.type(JsonFieldType.STRING)
.optional(),
fieldWithPath("[].kineticLaw")
.description("SBML kinetics law")
.type("object")
.type(JsonFieldType.OBJECT)
.optional(),
subsectionWithPath("[].other")
.description("list of oher properties")
.type("object")
.type(JsonFieldType.OBJECT)
.optional()));
fields.addAll(reactionNodeFields("[].reactants[]"));
fields.addAll(reactionNodeFields("[].products[]"));
fields.addAll(reactionNodeFields("[].modifiers[]"));
return responseFields(fields);
}
private List<FieldDescriptor> reactionNodeFields(String prefix) {
if (!prefix.endsWith(".") && !prefix.isEmpty()) {
prefix = prefix + ".";
}
return Arrays.asList(
fieldWithPath(prefix + "aliasId")
.description("element identifier")
.type(JsonFieldType.NUMBER)
.optional(),
fieldWithPath(prefix + "type")
.description("type")
.type(JsonFieldType.STRING)
.optional(),
fieldWithPath(prefix + "stoichiometry")
.description("element stoichiometry")
.type(JsonFieldType.STRING)
.optional());
}
......
......@@ -2,6 +2,7 @@ package lcsb.mapviewer.web;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
......@@ -58,7 +59,6 @@ import lcsb.mapviewer.model.map.model.SubmodelType;
import lcsb.mapviewer.model.security.Privilege;
import lcsb.mapviewer.model.security.PrivilegeType;
import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.persist.dao.ProjectDao;
import lcsb.mapviewer.services.interfaces.IProjectService;
import lcsb.mapviewer.services.interfaces.IUserService;
import lcsb.mapviewer.web.serialization.ProjectBackgroundUpdateMixIn;
......@@ -66,8 +66,11 @@ import lcsb.mapviewer.web.serialization.ProjectBackgroundUpdateMixIn;
@RunWith(SpringJUnit4ClassRunner.class)
public class ProjectControllerIntegrationTest extends ControllerIntegrationTest {
private static final String CURATOR_PASSWORD = "test_pass";
private static final String CURATOR_LOGIN = "test_user";
private static final String CURATOR_PASSWORD = "curator_pass";
private static final String CURATOR_LOGIN = "curator_user";
private static final String USER_PASSWORD = "user_pass";
private static final String USER_LOGIN = "test_user";
private static final String TEST_PROJECT_2 = "test_project2";
Logger logger = LogManager.getLogger();
......@@ -77,9 +80,6 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
@Autowired
private IProjectService projectService;
@Autowired
private ProjectDao projectDao;
@Autowired
private ProjectSnippets snippets;
......@@ -90,15 +90,13 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
@Before
public void setup() {
callInSeparateThreadNoReturn(() -> {
curator = createCurator(CURATOR_LOGIN, CURATOR_PASSWORD);
});
curator = createCurator(CURATOR_LOGIN, CURATOR_PASSWORD);
}
@After
public void tearDown() throws Exception {
removeUserInSeparateThread(curator);
removeUserInSeparateThread(userService.getUserByLogin(USER_LOGIN));
removeProjectInSeparateThread(TEST_PROJECT);
removeProjectInSeparateThread(TEST_PROJECT_2);
}
......@@ -113,7 +111,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
.andDo(document("projects/project_data/list",
responseFields(
subsectionWithPath("[]")
.description("list of projects").type("array<Project>"))))
.description("list of projects").type(JsonFieldType.ARRAY))))
.andReturn().getResponse().getContentAsString();
int projects = new JsonParser()
......@@ -143,9 +141,7 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
userService.grantUserPrivilege(curator, PrivilegeType.READ_PROJECT, project.getProjectId());
Project project2 = new Project(TEST_PROJECT_2);
project2.setOwner(userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN));
callInSeparateThreadNoReturn(() -> {
projectDao.add(project2);
});
projectService.add(project2);
MockHttpSession session = createSession(CURATOR_LOGIN, CURATOR_PASSWORD);
......@@ -207,19 +203,19 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
responseFields(
subsectionWithPath("data")
.description("list of log entries")
.type("array<LogEntry>"),
.type(JsonFieldType.ARRAY),
fieldWithPath("filteredSize")
.description("number of entries that match filter criteria")
.type("number"),
.type(JsonFieldType.NUMBER),
fieldWithPath("length")
.description("number of entries in this response")
.type("number"),
.type(JsonFieldType.NUMBER),
fieldWithPath("start")
.description("number of first entry in this response")
.type("number"),
.type(JsonFieldType.NUMBER),
fieldWithPath("totalSize")
.description("number of all log entries")
.type("number"))))
.type(JsonFieldType.NUMBER))))
.andExpect(status().is2xxSuccessful());
}
......@@ -235,13 +231,13 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
responseFields(
subsectionWithPath("reactionAnnotations")
.description("list of reaction annotation types")
.type("map<type,number>"),
.type(JsonFieldType.OBJECT),
subsectionWithPath("elementAnnotations")
.description("list of element annotation types")
.type("map<type,number>"),
.type(JsonFieldType.OBJECT),
fieldWithPath("publications")
.description("number of publications included in the project")
.type("number"))))
.type(JsonFieldType.NUMBER))))
.andExpect(status().is2xxSuccessful());
}
......@@ -267,20 +263,17 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
requestFields(
fieldWithPath("[]")
.description("list of privileges to grant")
.type("array<Privilege>"),
.type(JsonFieldType.ARRAY),
fieldWithPath("[].privilegeType")
.description("type of privilege (READ_PROJECT/WRITE_PROJECT)")
.type("string"),
.type(JsonFieldType.STRING),
fieldWithPath("[].login")
.description("user login who should gain access")
.type("string"))))
.type(JsonFieldType.STRING))))
.andExpect(status().is2xxSuccessful());
callInSeparateThread(() -> {
assertTrue(userService.getUserByLogin(CURATOR_LOGIN).getPrivileges()
.contains(new Privilege(PrivilegeType.READ_PROJECT, TEST_PROJECT)));
return null;
});
assertTrue(userService.getUserByLogin(CURATOR_LOGIN, true).getPrivileges()
.contains(new Privilege(PrivilegeType.READ_PROJECT, TEST_PROJECT)));
}
@Test
......@@ -306,13 +299,13 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
requestFields(
fieldWithPath("[]")
.description("list of privileges to revoke")
.type("array<Privilege>"),
.type(JsonFieldType.ARRAY),
fieldWithPath("[].privilegeType")
.description("type of privilege (READ_PROJECT/WRITE_PROJECT)")
.type("string"),
.type(JsonFieldType.STRING),
fieldWithPath("[].login")
.description("user login who should lose access")
.type("string"))))
.type(JsonFieldType.STRING))))
.andExpect(status().is2xxSuccessful());
assertFalse(curator.getPrivileges().contains(new Privilege(PrivilegeType.READ_PROJECT, TEST_PROJECT)));
......@@ -355,29 +348,29 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
requestFields(
fieldWithPath("project.version")
.description("version of the project")
.type("string")
.type(JsonFieldType.STRING)
.optional(),
fieldWithPath("project.name")
.description("name of the project")
.type("string")
.type(JsonFieldType.STRING)
.optional(),
fieldWithPath("project.notifyEmail")
.description("email address that should be when something change in the project")
.type("string")
.type(JsonFieldType.STRING)
.optional(),
subsectionWithPath("project.organism")
.description("organism associated with the project")
.type("Reference")
.type(JsonFieldType.OBJECT)
.optional(),
subsectionWithPath("project.disease")
.description("disease associated with the project")
.type("Reference")
.type(JsonFieldType.OBJECT)
.optional(),
fieldWithPath("project.mapCanvasType")
.description(
"type of map canvas engine to be used when visualizing map, for now there are only two options available: "
+ snippets.getOptionsAsString(MapCanvasType.class))
.type("string")
.type(JsonFieldType.STRING)
.optional()),
snippets.getProjectSnippet()))
......@@ -573,14 +566,52 @@ public class ProjectControllerIntegrationTest extends ControllerIntegrationTest
.content(body)