diff --git a/.circleci/config.yml b/.circleci/config.yml index 534002bdd..c8f9dc3ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,13 +31,13 @@ jobs: command: | docker create -v /etc/newman --name mms_test_configs alpine:3.4 /bin/true docker cp example/. mms_test_configs:/etc/newman - docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run crud.postman_collection.json -e test-env.json --delay-request 300 - docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run cameo.postman_collection.json -e test-env.json --delay-request 300 - docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run jupyter.postman_collection.json -e test-env.json --delay-request 300 - docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run localauth.postman_collection.json -e test-env.json --delay-request 300 - docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run permissions.postman_collection.json -e test-env.json --delay-request 300 + docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run crud.postman_collection.json -e test-env.json --delay-request 500 + docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run cameo.postman_collection.json -e test-env.json --delay-request 1000 + docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run jupyter.postman_collection.json -e test-env.json --delay-request 500 + docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run localauth.postman_collection.json -e test-env.json --delay-request 500 + docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run permissions.postman_collection.json -e test-env.json --delay-request 500 docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run search.postman_collection.json -e test-env.json --delay-request 1000 - docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run artifacts.postman_collection.json -e test-env.json --delay-request 300 + docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run artifacts.postman_collection.json -e test-env.json --delay-request 500 - persist_to_workspace: root: /home/circleci/ diff --git a/build.gradle b/build.gradle index 7c02a7ca2..82c35ae8e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,8 @@ buildscript { } repositories { mavenCentral() - maven { url 'https://repo.spring.io/plugins-release' } gradlePluginPortal() + maven { url 'https://repo.spring.io/plugins-release' } } } diff --git a/core/src/main/java/org/openmbee/mms/core/dao/ProjectDAO.java b/core/src/main/java/org/openmbee/mms/core/dao/ProjectDAO.java index c90f0072b..244f543c9 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/ProjectDAO.java +++ b/core/src/main/java/org/openmbee/mms/core/dao/ProjectDAO.java @@ -15,4 +15,6 @@ public interface ProjectDAO { void delete(Project p); List findAll(); + + List findAllByOrgId(String id); } diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/elements/ElementsController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/elements/ElementsController.java index f711affd7..23bb377a2 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/elements/ElementsController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/elements/ElementsController.java @@ -1,12 +1,19 @@ package org.openmbee.mms.crud.controllers.elements; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.Map; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; import org.openmbee.mms.core.objects.ElementsRequest; import org.openmbee.mms.core.objects.ElementsResponse; @@ -16,10 +23,11 @@ import org.openmbee.mms.core.services.NodeService; import org.openmbee.mms.core.pubsub.EmbeddedHookService; import org.openmbee.mms.crud.hooks.ElementUpdateHook; -import org.openmbee.mms.crud.services.DefaultCommitService; +import org.openmbee.mms.json.ElementJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.HttpStatus; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -44,6 +52,9 @@ public class ElementsController extends BaseController { private EmbeddedHookService embeddedHookService; private CommitService commitService; + @Value("${mms.stream.batch.size:5000}") + private int streamLimit; + @Autowired public void setEmbeddedHookService(EmbeddedHookService embeddedHookService) { this.embeddedHookService = embeddedHookService; @@ -54,6 +65,8 @@ public void setCommitService(@Qualifier("defaultCommitService")CommitService com this.commitService = commitService; } + private static final JsonFactory jfactory = new JsonFactory(); + @GetMapping @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_READ', true)") @ApiResponse(responseCode = "200", content = { @@ -112,6 +125,55 @@ public ElementsResponse createOrUpdateElements( } throw new BadRequestException(response.addMessage("Empty")); } + /* + @PostMapping(value = "/stream", consumes = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_EDIT_CONTENT', false)") + */ + public ResponseEntity createOrUpdateElementsStream( + @PathVariable String projectId, + @PathVariable String refId, + @RequestParam(required = false) Map params, + @Parameter(hidden = true) @RequestHeader(value = "Accept", defaultValue = "application/json") String accept, + Authentication auth, + HttpEntity requestEntity) { + + String commitId = UUID.randomUUID().toString(); // Generate a commitId from the start + params.put("commitId", commitId); + ElementsRequest req = new ElementsRequest(); + List elements = new ArrayList<>(); + + InputStream stream = new ByteArrayInputStream(Objects.requireNonNull(requestEntity.getBody())); + StreamingResponseBody response = outputStream -> { + ObjectMapper om = new ObjectMapper(); + try (JsonParser parser = jfactory.createParser(stream)) { + if(parser.nextToken() != JsonToken.START_OBJECT) { + throw new BadRequestException("Expected an object"); + } + while (parser.nextToken() != JsonToken.END_OBJECT) { + logger.debug("Current Token: " + parser.getCurrentName()); + if (parser.nextToken() == JsonToken.START_ARRAY && "elements".equals(parser.getCurrentName())) { + logger.debug("Found Array: " + parser.getCurrentName()); + while (parser.nextToken() != JsonToken.END_OBJECT) { + ElementJson node = om.readValue(parser, ElementJson.class); + elements.add(node); + //outputStream.write(node.getType().getBytes(StandardCharsets.UTF_8)); + //outputStream.write(node.get("id").toString().getBytes(StandardCharsets.UTF_8)); + } + } + } + req.setElements(elements); + if (!req.getElements().isEmpty()) { + NodeService nodeService = getNodeService(projectId); + nodeService.createOrUpdate(projectId, refId, req, params, auth.getName()); + } + } catch (IOException e) { + logger.debug("Error in stream handling: ", e); + } + }; + return ResponseEntity.ok() + .header("Content-Type", accept.equals("application/x-ndjson") ? accept : "application/json") + .body(response); + } @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_READ', true)") diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java index ebf197ee0..8dc62b6a9 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/projects/ProjectsController.java @@ -1,10 +1,7 @@ package org.openmbee.mms.crud.controllers.projects; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; + import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.core.config.Privileges; import org.openmbee.mms.core.config.ProjectSchemas; @@ -35,6 +32,12 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + @RestController @RequestMapping("/projects") @Tag(name = "Projects") @@ -55,10 +58,10 @@ public ProjectsController(ProjectDAO projectRepository, ProjectIndex projectInde } @GetMapping - public ProjectsResponse getAllProjects(Authentication auth) { - + public ProjectsResponse getAllProjects(Authentication auth, + @RequestParam(required = false) String orgId) { ProjectsResponse response = new ProjectsResponse(); - List allProjects = projectRepository.findAll(); + List allProjects = orgId != null ? projectRepository.findAllByOrgId(orgId) : projectRepository.findAll(); for (Project proj : allProjects) { if (mss.hasProjectPrivilege(auth, proj.getProjectId(), Privileges.PROJECT_READ.name(), true)) { ContextHolder.setContext(proj.getProjectId()); diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultBranchService.java b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultBranchService.java index 3a7f4e813..784118ee3 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultBranchService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultBranchService.java @@ -150,7 +150,7 @@ public RefJson createBranch(String projectId, RefJson branch) { Optional parentCommit = commitRepository.findLatestByRef(refOption.get()); parentCommit.ifPresent(parent -> { b.setParentCommit(parent.getId()); - branch.setParentCommitId(parent.getDocId()); //commit id is same as its docId + branch.setParentCommitId(parent.getCommitId()); //commit id is same as its docId }); } @@ -172,6 +172,7 @@ public RefJson createBranch(String projectId, RefJson branch) { return branch; } catch (Exception e) { logger.error("Couldn't create branch: {}", branch.getId(), e); + //TODO should clean up any created tables/rows? throw new InternalErrorException(e); } } diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultCommitService.java b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultCommitService.java index 863a9527a..6479e146b 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultCommitService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultCommitService.java @@ -77,8 +77,10 @@ public CommitsResponse getRefCommits(String projectId, String refId, Map { @@ -114,7 +116,7 @@ public CommitsResponse getElementCommits(String projectId, String refId, String List refCommits = commitRepository.findByRefAndTimestampAndLimit(ref.get(), null, 0); Set commitIds = new HashSet<>(); for (Commit commit: refCommits) { - commitIds.add(commit.getDocId()); + commitIds.add(commit.getCommitId()); } res.getCommits().addAll(commitIndex.elementHistory(elementId, commitIds)); return res; diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultNodeService.java b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultNodeService.java index f660e6284..19ca028a6 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultNodeService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultNodeService.java @@ -189,10 +189,11 @@ public ElementsResponse createOrUpdate(String projectId, String refId, ElementsR ContextHolder.setContext(projectId, refId); boolean overwriteJson = Boolean.parseBoolean(params.get("overwrite")); nodePostHelper.setPreserveTimestamps(Boolean.parseBoolean(params.get("preserveTimestamps"))); + String commitId = params.get("commitId"); NodeChangeInfo info = nodePostHelper .processPostJson(req.getElements(), overwriteJson, - createCommit(user, refId, projectId, req), this); + createCommit(user, refId, projectId, req, commitId), this); commitChanges(info); @@ -220,16 +221,23 @@ protected void commitChanges(NodeChangeInfo info) { } this.nodeIndex.removeFromRef(info.getOldDocIds()); - Commit commit = new Commit(); - commit.setBranchId(cmjs.getRefId()); - commit.setCommitType(CommitType.COMMIT); - commit.setCreator(cmjs.getCreator()); - commit.setDocId(cmjs.getId()); - commit.setTimestamp(now); - commit.setComment(cmjs.getComment()); - - this.commitRepository.save(commit); + Optional existing = this.commitRepository.findByCommitId(cmjs.getId()); + existing.ifPresentOrElse( + current -> { + this.logger.debug(String.format("Commit object %s already exists. Skipping record creation.", current.getCommitId())); + }, + () -> { + Commit commit = new Commit(); + commit.setCommitId(cmjs.getId()); + commit.setBranchId(cmjs.getRefId()); + commit.setCommitType(CommitType.COMMIT); + commit.setCreator(cmjs.getCreator()); + commit.setTimestamp(now); + commit.setComment(cmjs.getComment()); + this.commitRepository.save(commit); + }); this.commitIndex.index(cmjs); + this.nodeRepository.getTransactionManager().commit(status); } catch (Exception e) { logger.error("commitChanges error: ", e); @@ -282,7 +290,7 @@ public ElementsResponse delete(String projectId, String refId, ElementsRequest r ContextHolder.setContext(projectId, refId); NodeChangeInfo info = nodeDeleteHelper - .processDeleteJson(req.getElements(), createCommit(user, refId, projectId, req), + .processDeleteJson(req.getElements(), createCommit(user, refId, projectId, req, null), this); ElementsResponse response = new ElementsResponse(); @@ -294,13 +302,18 @@ public ElementsResponse delete(String projectId, String refId, ElementsRequest r } private CommitJson createCommit(String creator, String refId, String projectId, - ElementsRequest req) { + ElementsRequest req, String commitId) { CommitJson cmjs = new CommitJson(); cmjs.setCreator(creator); cmjs.setComment(req.getComment()); cmjs.setSource(req.getSource()); cmjs.setRefId(refId); cmjs.setProjectId(projectId); + + if (commitId != null && !commitId.isEmpty()) { + cmjs.setId(commitId); + } + return cmjs; } diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/NodeGetHelper.java b/crud/src/main/java/org/openmbee/mms/crud/services/NodeGetHelper.java index c043ddd89..611238ce4 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/NodeGetHelper.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/NodeGetHelper.java @@ -113,7 +113,7 @@ public NodeGetInfo processGetJson(List elements, Instant time, Node if (ref.isPresent()) { Optional c = commitRepository.findLatestByRef(ref.get()); if (c.isPresent()) { - return processGetJson(elements, c.get().getDocId(), service); + return processGetJson(elements, c.get().getCommitId(), service); } else { throw new BadRequestException("invalid time"); } @@ -148,7 +148,7 @@ public List processGetAll(Instant time, NodeService service) { if (ref.isPresent()) { Optional c = commitRepository.findByRefAndTimestamp(ref.get(), time); if (c.isPresent()) { - result.addAll(processGetAll(c.get().getDocId(), service)); + result.addAll(processGetAll(c.get().getCommitId(), service)); } else { throw new BadRequestException("invalid time"); } @@ -167,7 +167,7 @@ protected List getRefCommitIds(Instant time) { ref.ifPresent(current -> { List refCommits = commitRepository.findByRefAndTimestampAndLimit(current, time, 0); for (Commit c : refCommits) { - commitIds.add(c.getDocId()); + commitIds.add(c.getCommitId()); } }); return commitIds; diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/NodeOperation.java b/crud/src/main/java/org/openmbee/mms/crud/services/NodeOperation.java index 284c5f124..fe41caa25 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/NodeOperation.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/NodeOperation.java @@ -65,8 +65,13 @@ public void setBranchRepository(BranchDAO branchRepository) { } public void initCommitJson(CommitJson cmjs, Instant now) { - cmjs.setId(UUID.randomUUID().toString()); - cmjs.setDocId(cmjs.getId()); + if (cmjs.getId() == null || cmjs.getId().isEmpty()) { + cmjs.setId(UUID.randomUUID().toString()); + cmjs.setDocId(cmjs.getId()); + } + if (cmjs.getDocId() == null || cmjs.getDocId().isEmpty()) { + cmjs.setDocId(UUID.randomUUID().toString()); + } cmjs.setCreated(formatter.format(now)); cmjs.setAdded(new ArrayList<>()); cmjs.setDeleted(new ArrayList<>()); @@ -130,7 +135,7 @@ public void processElementAdded(ElementJson e, Node n, NodeChangeInfo info) { cmjs.getAdded().add(newObj); n.setNodeId(e.getId()); - n.setInitialCommit(e.getDocId()); + n.setInitialCommit(e.getCommitId()); } public void processElementUpdated(ElementJson e, Node n, NodeChangeInfo info) { diff --git a/data/src/main/java/org/openmbee/mms/data/domains/scoped/Branch.java b/data/src/main/java/org/openmbee/mms/data/domains/scoped/Branch.java index cf1013d5d..b1f609f83 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/scoped/Branch.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/scoped/Branch.java @@ -19,11 +19,13 @@ public class Branch { private String description; - @Column(unique = true) + @Column(unique = true, length = 512) private String branchId; private String docId; + @Column(length = 512) private String branchName; + @Column(length = 512) private String parentRefId; private Long parentCommit; diff --git a/data/src/main/java/org/openmbee/mms/data/domains/scoped/Commit.java b/data/src/main/java/org/openmbee/mms/data/domains/scoped/Commit.java index 8d02bf217..f21eec2e2 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/scoped/Commit.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/scoped/Commit.java @@ -23,10 +23,12 @@ public class Commit implements Serializable { private Instant timestamp; @Column(unique = true) - private String docId; + private String commitId; + @Column(length = 512) private String branchId; private String creator; + @Column(length = 512) private String comment; @Column(columnDefinition = "smallint") @@ -48,12 +50,12 @@ public void setTimestamp(Instant timestamp) { this.timestamp = timestamp; } - public String getDocId() { - return docId; + public String getCommitId() { + return commitId; } - public void setDocId(String docId) { - this.docId = docId; + public void setCommitId(String commitId) { + this.commitId = commitId; } public String getBranchId() { @@ -81,7 +83,7 @@ public void setCommitType(CommitType commitType) { } public String getComment() { - return comment; + return comment == null ? "" : comment; } public void setComment(String comment) { diff --git a/data/src/main/java/org/openmbee/mms/data/domains/scoped/Node.java b/data/src/main/java/org/openmbee/mms/data/domains/scoped/Node.java index 66cbc33d3..1359c9d9f 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/scoped/Node.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/scoped/Node.java @@ -16,7 +16,7 @@ public class Node { @Column(name = "id", updatable = false, nullable = false) Long id; - @Column(unique = true) + @Column(unique = true, length = 512) private String nodeId; private String docId; private String lastCommit; diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/BaseElasticDAOImpl.java b/elastic/src/main/java/org/openmbee/mms/elastic/BaseElasticDAOImpl.java index c574a955e..f75de49bb 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/BaseElasticDAOImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/BaseElasticDAOImpl.java @@ -40,7 +40,7 @@ public abstract class BaseElasticDAOImpl> { - private final Logger logger = LoggerFactory.getLogger(getClass()); + final Logger logger = LoggerFactory.getLogger(getClass()); @Value("${elasticsearch.limit.result:10000}") protected int resultLimit; @@ -181,8 +181,7 @@ public void indexAll(String index, Collection jsons) { public void index(String index, BaseJson json) { try { - client.index(new IndexRequest(index).id(json.getDocId()).source(json), - REQUEST_OPTIONS); + client.index(new IndexRequest(index).id(json.getDocId()).source(json), REQUEST_OPTIONS); } catch (IOException e) { throw new RuntimeException(e); } @@ -209,7 +208,7 @@ public E update(String index, BaseJson json) { return response; } - private BulkProcessor getBulkProcessor(RestHighLevelClient client) { + protected BulkProcessor getBulkProcessor(RestHighLevelClient client) { return getBulkProcessor(client, null); } diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/CommitElasticDAOImpl.java b/elastic/src/main/java/org/openmbee/mms/elastic/CommitElasticDAOImpl.java index cbc2add81..d22dbbae0 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/CommitElasticDAOImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/CommitElasticDAOImpl.java @@ -1,12 +1,9 @@ package org.openmbee.mms.elastic; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; + import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; @@ -21,11 +18,15 @@ import org.openmbee.mms.elastic.utils.Index; import org.openmbee.mms.json.BaseJson; import org.openmbee.mms.json.CommitJson; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class CommitElasticDAOImpl extends BaseElasticDAOImpl implements CommitIndexDAO { + @Value("${elasticsearch.limit.commit:10000}") + int commitLimit; + protected CommitJson newInstance() { return new CommitJson(); } @@ -35,27 +36,72 @@ public void indexAll(Collection jsons) { } public void index(BaseJson json) { - this.index(getIndex(), json); + index((CommitJson) json); + } + + public void index(CommitJson json) { + int commitCount = getCommitSize(json); + List broken = new ArrayList<>(); + if (commitCount > commitLimit) { + List> allActions = new ArrayList<>(); + allActions.addAll(json.getAdded().stream().peek(toAdd -> toAdd.put("action", "added")).collect(Collectors.toList())); + allActions.addAll(json.getUpdated().stream().peek(toUpdate -> toUpdate.put("action", "updated")).collect(Collectors.toList())); + allActions.addAll(json.getDeleted().stream().peek(toDelete -> toDelete.put("action", "deleted")).collect(Collectors.toList())); + + while (!allActions.isEmpty()) { + CommitJson currentCommitCopy = CommitJson.copy(new CommitJson(), json); + currentCommitCopy.setAdded(new ArrayList<>()); + currentCommitCopy.setUpdated(new ArrayList<>()); + currentCommitCopy.setDeleted(new ArrayList<>()); + currentCommitCopy.setDocId(UUID.randomUUID().toString()); + do { + Map action = allActions.remove(0); + String compare = action.getOrDefault("action", "none").toString(); + action.remove("action"); + switch (compare) { + case "added": + currentCommitCopy.getAdded().add(action); + break; + case "updated": + currentCommitCopy.getUpdated().add(action); + break; + case "deleted": + currentCommitCopy.getDeleted().add(action); + break; + } + } while(getCommitSize(currentCommitCopy) < commitLimit && !allActions.isEmpty()); + broken.add(currentCommitCopy); + + } + this.indexAll(broken); + + } else { + this.index(getIndex(), json); + } } - public Optional findById(String docId) { - return this.findById(getIndex(), docId); + public Optional findById(String commitId) { + return getFullCommit(commitId); } - public List findAllById(Set docIds) { - return this.findAllById(getIndex(), docIds); + public List findAllById(Set commitIds) { + return getFullCommits(commitIds); } - public void deleteById(String docId) { - this.deleteById(getIndex(), docId); + public void deleteById(String commitId) { + List docs = getDocs(commitId); + docs.forEach(commit -> { + this.deleteById(getIndex(), commit.getDocId()); + }); } public void deleteAll(Collection jsons) { this.deleteAll(getIndex(), jsons); } - public boolean existsById(String docId) { - return this.existsById(getIndex(), docId); + public boolean existsById(String commitId) { + List docs = getDocs(commitId); + return !docs.isEmpty(); } /** @@ -82,7 +128,7 @@ private QueryBuilder getCommitHistoryQuery(String id, Set commitIds) { /** * Returns the commit history of a element - *

Returns a list of commit metadata for the specificed id + *

Returns a list of commit metadata for the specified id * *

* @@ -94,22 +140,18 @@ private QueryBuilder getCommitHistoryQuery(String id, Set commitIds) { public List elementHistory(String nodeId, Set commitIds) { try { List commits = new ArrayList<>(); - SearchRequest searchRequest = new SearchRequest(getIndex()); QueryBuilder query = getCommitHistoryQuery(nodeId, commitIds); - SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); - sourceBuilder.query(query); - sourceBuilder.size(this.resultLimit); // TODO handle paging requests - sourceBuilder.sort(new FieldSortBuilder(CommitJson.CREATED).order(SortOrder.DESC)); - searchRequest.source(sourceBuilder); - SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); - SearchHits hits = searchResponse.getHits(); + SearchHits hits = getCommitResults(query); if (hits.getTotalHits().value == 0) { - return commits; + return new ArrayList<>(); } for (SearchHit hit : hits.getHits()) { Map source = hit.getSourceAsMap();// gets "_source" CommitJson ob = newInstance(); ob.putAll(source); + ob.remove(CommitJson.ADDED); + ob.remove(CommitJson.UPDATED); + ob.remove(CommitJson.DELETED); commits.add(ob); } return commits; @@ -127,4 +169,66 @@ protected String getIndex() { public CommitJson update(CommitJson commitJson) { return this.update(getIndex(), commitJson); } + + private List getDocs(String commitId) { + try { + QueryBuilder commitQuery = QueryBuilders.boolQuery() + .filter(QueryBuilders.termQuery(CommitJson.ID, commitId)); + SearchHits hits = getCommitResults(commitQuery); + if (hits.getTotalHits().value == 0) { + return new ArrayList<>(); + } + List rawCommits = new ArrayList<>(); + for (SearchHit hit : hits.getHits()) { + CommitJson ob = new CommitJson(); + ob.putAll(hit.getSourceAsMap()); + rawCommits.add(ob); // gets "_source" + } + return rawCommits; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + private List getFullCommits(Collection commitIds) { + return commitIds.stream().map(this::getFullCommit).filter(Optional::isPresent) + .map(Optional::get).collect(Collectors.toList()); + } + + private Optional getFullCommit(String commitId) { + List commits = getDocs(commitId); + if (commits.isEmpty()) { + return Optional.empty(); + } + return Optional.of(mungCommits(commits)); + } + + private SearchHits getCommitResults(QueryBuilder query) throws IOException { + SearchRequest searchRequest = new SearchRequest(getIndex()); + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); + sourceBuilder.query(query); + sourceBuilder.size(this.resultLimit); // TODO handle paging requests + sourceBuilder.sort(new FieldSortBuilder(CommitJson.CREATED).order(SortOrder.DESC)); + searchRequest.source(sourceBuilder); + SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); + return searchResponse.getHits(); + } + + private static CommitJson mungCommits(List commits) { + return commits.stream().reduce(new CommitJson(), CommitJson::copy); + } + + private static int getCommitSize(CommitJson commitJson) { + int commitCount = 0; + if (commitJson.getAdded() != null && !commitJson.getAdded().isEmpty()) { + commitCount = commitCount + commitJson.getAdded().size(); + } + if (commitJson.getUpdated() != null && !commitJson.getUpdated().isEmpty()) { + commitCount = commitCount + commitJson.getUpdated().size(); + } + if (commitJson.getDeleted() != null && !commitJson.getDeleted().isEmpty()) { + commitCount = commitCount + commitJson.getDeleted().size(); + } + return commitCount; + } } diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/NodeElasticDAOImpl.java b/elastic/src/main/java/org/openmbee/mms/elastic/NodeElasticDAOImpl.java index 5088a8ec6..2804f20b8 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/NodeElasticDAOImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/NodeElasticDAOImpl.java @@ -7,7 +7,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import org.elasticsearch.action.bulk.BulkRequest; +import java.util.concurrent.TimeUnit; +import org.elasticsearch.action.bulk.BulkProcessor; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.update.UpdateRequest; @@ -101,7 +102,7 @@ private void bulkUpdateRefWithScript(Set docIds, String script) { if (docIds.isEmpty()) { return; } - BulkRequest bulk = new BulkRequest(); + BulkProcessor bulkProcessor = getBulkProcessor(client); Map parameters = Collections.singletonMap("refId", ContextHolder.getContext().getBranchId()); for (String docId : docIds) { @@ -109,12 +110,14 @@ private void bulkUpdateRefWithScript(Set docIds, String script) { Script inline = new Script(ScriptType.INLINE, "painless", script, parameters); request.script(inline); - bulk.add(request); + bulkProcessor.add(request); } try { - client.bulk(bulk, RequestOptions.DEFAULT); - } catch (IOException e) { - throw new RuntimeException(e); + if (!bulkProcessor.awaitClose(1200L, TimeUnit.SECONDS)) { + logger.error("Timed out in bulk processing"); + } + } catch (InterruptedException e) { + logger.error("Index all interrupted: ", e); } } diff --git a/example/crud.postman_collection.json b/example/crud.postman_collection.json index 826e78ede..a3f223e61 100644 --- a/example/crud.postman_collection.json +++ b/example/crud.postman_collection.json @@ -2514,7 +2514,7 @@ { "listen": "test", "script": { - "id": "2ba7a873-1bdf-4e86-8329-7069064232a9", + "id": "2ba7a873-1bdf-4e86-8329-7069064232a9", "exec": [ "pm.test(\"webhook created with returned id\", function () {", " var jsonData = pm.response.json();", @@ -2872,6 +2872,58 @@ }, "response": [] }, + { + "name": "get org c projects", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has 1 project random\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.projects.length).to.eql(1);", + " pm.expect(jsonData.projects[0].name).to.eql('random');", + "});" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{host}}/projects?orgId={{orgIdC}}", + "host": [ + "{{host}}" + ], + "path": [ + "projects" + ], + "query": [ + { + "key": "orgId", + "value": "{{orgIdC}}" + } + ] + } + }, + "response": [] + }, { "name": "add element random to project random without id", "event": [ @@ -3386,7 +3438,46 @@ } }, "response": [] - } + }, + { + "name": "get nonexistent commit", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 404\", function () {", + " pm.response.to.have.status(404);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "url": { + "raw": "{{host}}/projects/aa/commits/missing", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "aa", + "commits", + "missing" + ] + } + }, + "response": [] + } ], "auth": { "type": "bearer", diff --git a/example/src/main/resources/application.properties.example b/example/src/main/resources/application.properties.example index 001b74fce..367f032d3 100644 --- a/example/src/main/resources/application.properties.example +++ b/example/src/main/resources/application.properties.example @@ -53,6 +53,7 @@ elasticsearch.limit.term=1000 elasticsearch.limit.scrollTimeout=1000 elasticsearch.limit.get=100000 elasticsearch.limit.index=5000 +elasticsearch.limit.commit=100000 #Configuration for TWC #port is for REST interface diff --git a/json/json.gradle b/json/json.gradle index 88de0b9ad..0daf19acc 100644 --- a/json/json.gradle +++ b/json/json.gradle @@ -1,4 +1,6 @@ dependencies { + api commonDependencies.'jackson-databind' + implementation commonDependencies.'jackson-annotations' implementation commonDependencies.'swagger-annotations' testImplementation commonDependencies.'spring-boot-starter-test' diff --git a/json/src/main/java/org/openmbee/mms/json/CommitJson.java b/json/src/main/java/org/openmbee/mms/json/CommitJson.java index c41e50c2f..2a8ac6160 100644 --- a/json/src/main/java/org/openmbee/mms/json/CommitJson.java +++ b/json/src/main/java/org/openmbee/mms/json/CommitJson.java @@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema.AccessMode; + +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -17,6 +19,34 @@ public class CommitJson extends BaseJson { public static final String UPDATED = "updated"; public static final String SOURCE = "source"; + public static CommitJson copy(CommitJson copy, CommitJson original) { + if (copy.getAdded() == null) { + copy.setAdded(new ArrayList<>()); + } + if (copy.getUpdated() == null) { + copy.setUpdated(new ArrayList<>()); + } + if (copy.getDeleted() == null) { + copy.setDeleted(new ArrayList<>()); + } + + if (original.getAdded() != null) { + copy.getAdded().addAll(original.getAdded()); + original.remove(CommitJson.ADDED); + } + if (original.getUpdated() != null) { + copy.getUpdated().addAll(original.getUpdated()); + original.remove(CommitJson.UPDATED); + } + if (original.getDeleted() != null) { + copy.getDeleted().addAll(original.getDeleted()); + original.remove(CommitJson.DELETED); + } + + copy.putAll(original); + return copy; + } + @Schema(accessMode = AccessMode.READ_ONLY) public String getComment() { return (String) this.get(COMMENT); diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/ProjectDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/ProjectDAOImpl.java index f7421d412..427cd2f81 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/ProjectDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/ProjectDAOImpl.java @@ -41,6 +41,11 @@ public Optional findByProjectName(String name) { return projectRepository.findByProjectName(name); } + @Override + public List findAllByOrgId(String id) { + return projectRepository.findAllByOrganizationOrganizationId(id); + } + @Override public Project save(Project proj) { if (proj.getId() == null) { diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/ProjectRepository.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/ProjectRepository.java index a726afeeb..be7b12cfc 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/ProjectRepository.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/ProjectRepository.java @@ -1,6 +1,9 @@ package org.openmbee.mms.rdb.repositories; +import java.util.List; import java.util.Optional; + +import org.openmbee.mms.data.domains.global.Organization; import org.openmbee.mms.data.domains.global.Project; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -12,4 +15,6 @@ public interface ProjectRepository extends JpaRepository { Optional findByProjectName(String name); + List findAllByOrganizationOrganizationId(String id); + } diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java index 4c94490e2..eb4e55fdc 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/commit/CommitDAOImpl.java @@ -33,7 +33,7 @@ public void setBranchRepository(BranchDAO branchRepository) { } public Commit save(Commit commit) { - String sql = "INSERT INTO commits (commitType, creator, docid, branchId, timestamp, comment) VALUES (?, ?, ?, ?, ?, ?)"; + String sql = "INSERT INTO commits (commitType, creator, commitid, branchId, timestamp, comment) VALUES (?, ?, ?, ?, ?, ?)"; KeyHolder keyHolder = new GeneratedKeyHolder(); getConn().update(new PreparedStatementCreator() { @@ -42,7 +42,7 @@ public PreparedStatement createPreparedStatement(Connection connection) PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"}); ps.setInt(1, commit.getCommitType().getId()); ps.setString(2, commit.getCreator()); - ps.setString(3, commit.getDocId()); + ps.setString(3, commit.getCommitId()); ps.setString(4, commit.getBranchId()); ps.setTimestamp(5, Timestamp.from(commit.getTimestamp())); ps.setString(6, commit.getComment()); @@ -69,7 +69,7 @@ public Optional findById(long id) { } public Optional findByCommitId(String commitId) { - String sql = "SELECT * FROM commits WHERE docid = ?"; + String sql = "SELECT * FROM commits WHERE commitid = ?"; List l = getConn() .query(sql, new Object[]{commitId}, new CommitRowMapper()); diff --git a/twc/src/main/java/org/openmbee/mms/twc/services/TwcRevisionMmsCommitMapService.java b/twc/src/main/java/org/openmbee/mms/twc/services/TwcRevisionMmsCommitMapService.java index 1d689a0ed..6361a60bd 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/services/TwcRevisionMmsCommitMapService.java +++ b/twc/src/main/java/org/openmbee/mms/twc/services/TwcRevisionMmsCommitMapService.java @@ -75,7 +75,7 @@ public List getTwcRevisionList(String projectId, String refId, Boole List refCommits = commitRepository.findByRefAndTimestampAndLimit(ref.get(), null, 0); Set commitIds = new HashSet<>(); refCommits.stream().forEach(commit -> { - commitIds.add(commit.getDocId()); + commitIds.add(commit.getCommitId()); }); List commitJsonList = commitIndex.findAllById(commitIds); if (null != commitJsonList && commitJsonList.size() > 0) {