diff --git a/.circleci/config.yml b/.circleci/config.yml index 54171256a..2df37eca6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,7 +38,7 @@ jobs: 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 getAtCommits.postman_collection.json -e test-env.json --delay-request 500 docker run --volumes-from mms_test_configs --network container:mms -t postman/newman run makeBranchFromCommit.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 cameo.postman_collection.json -e test-env.json --delay-request 500 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 diff --git a/artifacts/src/main/java/org/openmbee/mms/artifacts/crud/ArtifactsContext.java b/artifacts/src/main/java/org/openmbee/mms/artifacts/crud/ArtifactsContext.java new file mode 100644 index 000000000..1e24d8697 --- /dev/null +++ b/artifacts/src/main/java/org/openmbee/mms/artifacts/crud/ArtifactsContext.java @@ -0,0 +1,13 @@ +package org.openmbee.mms.artifacts.crud; + +public class ArtifactsContext { + private static ThreadLocal artifactContext = new ThreadLocal<>(); + + public static void setArtifactContext(Boolean isArtifactContext) { + artifactContext.set(isArtifactContext); + } + + public static boolean isArtifactContext() { + return Boolean.TRUE.equals(artifactContext.get()); + } +} diff --git a/artifacts/src/main/java/org/openmbee/mms/artifacts/crud/ArtifactsPersistenceNodeUpdateFilter.java b/artifacts/src/main/java/org/openmbee/mms/artifacts/crud/ArtifactsPersistenceNodeUpdateFilter.java new file mode 100644 index 000000000..1c53ab9ca --- /dev/null +++ b/artifacts/src/main/java/org/openmbee/mms/artifacts/crud/ArtifactsPersistenceNodeUpdateFilter.java @@ -0,0 +1,24 @@ +package org.openmbee.mms.artifacts.crud; + +import org.openmbee.mms.artifacts.json.ArtifactJson; +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.crud.domain.NodeUpdateFilter; +import org.openmbee.mms.json.ElementJson; +import org.springframework.stereotype.Component; + +@Component +public class ArtifactsPersistenceNodeUpdateFilter implements NodeUpdateFilter { + @Override + public boolean filterUpdate(NodeChangeInfo nodeChangeInfo, ElementJson updated, ElementJson existing) { + if(ArtifactsContext.isArtifactContext()) { + return true; + } + //Ensure artifacts aren't cleared or added by the regular element update process + if(existing.containsKey(ArtifactJson.ARTIFACTS)) { + updated.put(ArtifactJson.ARTIFACTS, existing.get(ArtifactJson.ARTIFACTS)); + } else { + updated.remove(ArtifactJson.ARTIFACTS); + } + return true; + } +} diff --git a/artifacts/src/main/java/org/openmbee/mms/artifacts/pubsub/ArtifactsElementsHookSubscriber.java b/artifacts/src/main/java/org/openmbee/mms/artifacts/pubsub/ArtifactsElementsHookSubscriber.java index a606360ac..e9c7da19c 100644 --- a/artifacts/src/main/java/org/openmbee/mms/artifacts/pubsub/ArtifactsElementsHookSubscriber.java +++ b/artifacts/src/main/java/org/openmbee/mms/artifacts/pubsub/ArtifactsElementsHookSubscriber.java @@ -22,9 +22,9 @@ public void acceptHook(Object payload) { ElementUpdateHook elementUpdateHook = (ElementUpdateHook) payload; if(elementUpdateHook.getElements() != null) { //Ignore any artifact changes coming in outside of the Artifacts Controller - elementUpdateHook.getElements().parallelStream().forEach(v -> { - v.remove(ArtifactJson.ARTIFACTS); - }); + elementUpdateHook.getElements().parallelStream().forEach(v -> + v.remove(ArtifactJson.ARTIFACTS) + ); } } } diff --git a/artifacts/src/main/java/org/openmbee/mms/artifacts/service/DefaultArtifactService.java b/artifacts/src/main/java/org/openmbee/mms/artifacts/service/DefaultArtifactService.java index 5c4aae384..98df304b1 100644 --- a/artifacts/src/main/java/org/openmbee/mms/artifacts/service/DefaultArtifactService.java +++ b/artifacts/src/main/java/org/openmbee/mms/artifacts/service/DefaultArtifactService.java @@ -1,9 +1,10 @@ package org.openmbee.mms.artifacts.service; +import org.openmbee.mms.artifacts.crud.ArtifactsContext; import org.openmbee.mms.artifacts.storage.ArtifactStorage; import org.openmbee.mms.artifacts.ArtifactConstants; import org.openmbee.mms.artifacts.objects.ArtifactResponse; -import org.openmbee.mms.core.dao.ProjectDAO; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.ConflictException; import org.openmbee.mms.core.exceptions.NotFoundException; @@ -11,9 +12,9 @@ import org.openmbee.mms.core.objects.ElementsResponse; import org.openmbee.mms.core.services.NodeService; import org.openmbee.mms.crud.services.ServiceFactory; -import org.openmbee.mms.data.domains.global.Project; import org.openmbee.mms.artifacts.json.ArtifactJson; import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.json.ProjectJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; @@ -30,7 +31,7 @@ public class DefaultArtifactService implements ArtifactService { private ArtifactStorage artifactStorage; private ServiceFactory serviceFactory; - private ProjectDAO projectRepository; + private ProjectPersistence projectPersistence; @Autowired public void setArtifactStorage(ArtifactStorage artifactStorage) { @@ -43,16 +44,19 @@ public void setServiceFactory(ServiceFactory serviceFactory) { } @Autowired - public void setProjectRepository(ProjectDAO projectRepository) { - this.projectRepository = projectRepository; + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; } @Override public ArtifactResponse get(String projectId, String refId, String id, Map params) { NodeService nodeService = getNodeService(projectId); ElementJson elementJson = getElement(nodeService, projectId, refId, id, params); - - ArtifactJson artifact = getExistingArtifact(ArtifactJson.getArtifacts(elementJson), params, elementJson); + List artifacts = ArtifactJson.getArtifacts(elementJson); + ArtifactJson artifact = new ArtifactJson(); + if (artifacts != null) { + artifact = getExistingArtifact(artifacts, params, elementJson); + } byte[] data = artifactStorage.get(artifact.getLocation(), elementJson, artifact.getMimeType()); ArtifactResponse response = new ArtifactResponse(); response.setData(data); @@ -90,7 +94,12 @@ public ElementsResponse createOrUpdate(String projectId, String refId, String id elementJson = attachOrUpdateArtifact(elementJson, artifactLocation, fileExtension, mimeType, "internal", checksum); ElementsRequest elementsRequest = new ElementsRequest(); elementsRequest.setElements(Arrays.asList(elementJson)); - return nodeService.createOrUpdate(projectId, refId, elementsRequest, params, user); + try { + ArtifactsContext.setArtifactContext(true); + return nodeService.createOrUpdate(projectId, refId, elementsRequest, params, user); + } finally { + ArtifactsContext.setArtifactContext(false); + } } @Override @@ -99,20 +108,28 @@ public ElementsResponse disassociate(String projectId, String refId, String id, ElementJson elementJson = getElement(nodeService, projectId, refId, id, params); List artifacts = ArtifactJson.getArtifacts(elementJson); + if (artifacts == null) { + throw new NotFoundException("Artifacts not found"); + } ArtifactJson artifact = getExistingArtifact(artifacts, params, elementJson); artifacts.remove(artifact); ArtifactJson.setArtifacts(elementJson, artifacts); ElementsRequest elementsRequest = new ElementsRequest(); elementsRequest.setElements(Arrays.asList(elementJson)); - return nodeService.createOrUpdate(projectId, refId, elementsRequest, params, user); + try { + ArtifactsContext.setArtifactContext(true); + return nodeService.createOrUpdate(projectId, refId, elementsRequest, params, user); + } finally { + ArtifactsContext.setArtifactContext(false); + } } private ElementJson getElement(NodeService nodeService, String projectId, String refId, String id, Map params) { ElementsResponse elementsResponse = nodeService.read(projectId, refId, id, params); - if(elementsResponse.getElements() == null || elementsResponse.getElements().isEmpty()) { + if (elementsResponse.getElements() == null || elementsResponse.getElements().isEmpty()) { throw new NotFoundException("Element not found"); - } else if(elementsResponse.getElements().size() > 1) { + } else if (elementsResponse.getElements().size() > 1) { throw new ConflictException("Multiple elements found with id " + id); } else { return elementsResponse.getElements().get(0); @@ -124,10 +141,18 @@ private ElementJson attachOrUpdateArtifact(ElementJson elementJson, String artif List artifacts = ArtifactJson.getArtifacts(elementJson); ArtifactJson artifact; try { - artifact = getExistingArtifact(artifacts, mimeType, null, elementJson); + //nested try/nullcheck OK? + if (artifacts != null) { + artifact = getExistingArtifact(artifacts, mimeType, null, elementJson); + } else { + throw new NotFoundException("Null artifact exception"); + } + } catch(NotFoundException ex) { artifact = new ArtifactJson(); - artifacts.add(artifact); + if (artifacts != null) { + artifacts.add(artifact); + } } artifact.setLocation(artifactLocation); @@ -145,14 +170,14 @@ private ArtifactJson getExistingArtifact(List artifacts, Map artifacts, String mimeType, String extension, ElementJson element) { - if(mimeType == null && extension == null) { + if (mimeType == null && extension == null) { throw new BadRequestException("Missing mimetype or extension"); } //Element representation is unique by mimeType and extension - Optional existing = artifacts.stream().filter(v -> { - return (mimeType != null && mimeType.equals(v.getMimeType())) || (extension != null && extension.equals(v.getExtension())); - }).findFirst(); - if(existing.isPresent()) { + Optional existing = artifacts.stream().filter(v -> + (mimeType != null && mimeType.equals(v.getMimeType())) || (extension != null && extension.equals(v.getExtension())) + ).findFirst(); + if (existing.isPresent()) { return existing.get(); } throw new NotFoundException(element); @@ -160,9 +185,9 @@ private ArtifactJson getExistingArtifact(List artifacts, String mi private String getFileExtension(MultipartFile file) { String originalFilename = file.getOriginalFilename(); - if(originalFilename != null) { + if (originalFilename != null) { int inx = originalFilename.lastIndexOf('.'); - if(inx > 0) { + if (inx > 0) { return originalFilename.substring(inx + 1); } } @@ -191,8 +216,8 @@ private String getProjectType(String projectId) { return getProject(projectId).getProjectType(); } - private Project getProject(String projectId) { - Optional p = projectRepository.findByProjectId(projectId); + private ProjectJson getProject(String projectId) { + Optional p = projectPersistence.findById(projectId); if (p.isPresent()) { return p.get(); } diff --git a/authenticator/authenticator.gradle b/authenticator/authenticator.gradle index 82e02c61d..ca8f17887 100644 --- a/authenticator/authenticator.gradle +++ b/authenticator/authenticator.gradle @@ -1,14 +1,19 @@ dependencies { - implementation project(':rdb') api project(':core') api commonDependencies.'spring-security-web' implementation commonDependencies.'servlet-api' - implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.10.5' + implementation commonDependencies.'jjwt-api' - runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.10.5' - runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.10.5' + runtimeOnly commonDependencies.'jjwt-impl' + runtimeOnly commonDependencies.'jjwt-jackson' testImplementation commonDependencies.'spring-boot-starter-test' } + +tasks { + processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} \ No newline at end of file diff --git a/authenticator/src/main/java/org/openmbee/mms/authenticator/security/JwtTokenGenerator.java b/authenticator/src/main/java/org/openmbee/mms/authenticator/security/JwtTokenGenerator.java index 2c7fbce00..a667211b1 100644 --- a/authenticator/src/main/java/org/openmbee/mms/authenticator/security/JwtTokenGenerator.java +++ b/authenticator/src/main/java/org/openmbee/mms/authenticator/security/JwtTokenGenerator.java @@ -101,7 +101,10 @@ private Date generateExpirationDate() { private boolean isTokenExpired(String token) { final Date expirationDate = getExpirationDateFromToken(token); - return expirationDate.before(new Date()); + if (expirationDate != null) { + return expirationDate.before(new Date()); + } + return true; } @Override diff --git a/build.gradle b/build.gradle index 5df7cf391..daf69f28b 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,9 @@ ext { 'jackson-databind' : "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 'jackson-datatype-hibernate5' : "com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonVersion", 'jackson-datatype-jsr310' : "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion", + 'jjwt-api' : "io.jsonwebtoken:jjwt-api:$jwtTokenVersion", + 'jjwt-impl' : "io.jsonwebtoken:jjwt-impl:$jwtTokenVersion", + 'jjwt-jackson' : "io.jsonwebtoken:jjwt-jackson:$jwtTokenVersion", 'spring-boot-starter-test' : 'org.springframework.boot:spring-boot-starter-test:2.2.4.RELEASE' ] } diff --git a/cameo/cameo.gradle b/cameo/cameo.gradle index 21102b288..8884015e7 100644 --- a/cameo/cameo.gradle +++ b/cameo/cameo.gradle @@ -1,5 +1,6 @@ dependencies { implementation project(':crud') + implementation project(':view') testImplementation commonDependencies.'spring-boot-starter-test' } diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/CameoConstants.java b/cameo/src/main/java/org/openmbee/mms/cameo/CameoConstants.java index 6e609a542..5893edad2 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/CameoConstants.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/CameoConstants.java @@ -14,6 +14,7 @@ public class CameoConstants { public static final String CLASSIFIERIDS = "classifierIds"; public static final String CLIENTDEPENDENCYIDS = "clientDependencyIds"; public static final String COLLABORATIONUSEIDS = "collaborationUseIds"; + public static final String COMMITID = "commitId"; public static final String DATATYPEID = "datatypeId"; public static final String DEFAULTVALUE = "defaultValue"; public static final String DEFININGFEATUREID = "definingFeatureId"; @@ -46,12 +47,15 @@ public class CameoConstants { public static final String NAME = "name"; public static final String NAMEEXPRESSION = "nameExpression"; public static final String NAVIGABLEOWNEDENDIDS = "navigableOwnedEndIds"; + public static final String NONE = "none"; public static final String OWNEDATTRIBUTEIDS = "ownedAttributeIds"; public static final String OWNEDENDIDS = "ownedEndIds"; public static final String OWNERID = "ownerId"; public static final String PACKAGEIMPORTIDS = "packageImportIds"; public static final String PACKAGEMERGEIDS = "packageMergeIds"; + public static final String PARENTID = "_parentId"; public static final String POWERTYPEEXTENTIDS = "powertypeExtentIds"; + public static final String PROPERTY = "Property"; public static final String PROPERTYID = "propertyId"; public static final String PROPERTYTYPE = "propertyType"; public static final String PROFILEAPPLICATIONIDS = "profileApplicationIds"; diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoCommitService.java b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoCommitService.java index 4994e106b..a5999f50e 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoCommitService.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoCommitService.java @@ -1,9 +1,8 @@ package org.openmbee.mms.cameo.services; -import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.core.services.CommitService; import org.openmbee.mms.crud.services.DefaultCommitService; -import org.openmbee.mms.data.domains.scoped.Commit; +import org.openmbee.mms.json.CommitJson; import org.springframework.stereotype.Service; import java.util.List; @@ -12,8 +11,7 @@ public class CameoCommitService extends DefaultCommitService implements CommitService { @Override public boolean isProjectNew(String projectId) { - ContextHolder.setContext(projectId); - List commits = commitRepository.findAll(); + List commits = commitPersistence.findByProjectAndRefAndTimestampAndLimit(projectId, "master", null, 2); return commits == null || commits.size() <= 1; // a cameo project gets 1 auto commit, so its still "new" } } diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoHelper.java b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoHelper.java index ce8e28105..edc32c50a 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoHelper.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoHelper.java @@ -6,12 +6,14 @@ import java.util.Set; import org.openmbee.mms.cameo.CameoConstants; import org.openmbee.mms.cameo.CameoNodeType; +import org.openmbee.mms.core.utils.ElementUtils; import org.openmbee.mms.json.ElementJson; import org.springframework.stereotype.Component; @Component -public class CameoHelper { +public class CameoHelper implements ElementUtils { + @Override public CameoNodeType getNodeType(ElementJson e) { if (isDocument(e)) { return CameoNodeType.DOCUMENT; diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoNodeService.java b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoNodeService.java index 0e68cf134..4669d1ff8 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoNodeService.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoNodeService.java @@ -1,24 +1,19 @@ package org.openmbee.mms.cameo.services; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.openmbee.mms.cameo.CameoNodeType; import org.openmbee.mms.cameo.CameoConstants; -import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.cameo.CameoNodeType; import org.openmbee.mms.core.config.Privileges; +import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.objects.ElementsRequest; +import org.openmbee.mms.core.objects.ElementsResponse; import org.openmbee.mms.core.security.MethodSecurityService; +import org.openmbee.mms.core.services.HierarchicalNodeService; import org.openmbee.mms.core.services.NodeChangeInfo; import org.openmbee.mms.core.services.NodeGetInfo; -import org.openmbee.mms.json.ElementJson; -import org.openmbee.mms.core.objects.ElementsResponse; +import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.services.DefaultNodeService; -import org.openmbee.mms.core.services.NodeService; -import org.openmbee.mms.data.domains.scoped.Node; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.ElementJson; import org.openmbee.mms.json.MountJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.util.Pair; @@ -26,8 +21,10 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import java.util.*; + @Service("cameoNodeService") -public class CameoNodeService extends DefaultNodeService implements NodeService { +public class CameoNodeService extends DefaultNodeService implements HierarchicalNodeService { protected CameoHelper cameoHelper; private MethodSecurityService mss; @@ -46,22 +43,30 @@ public void setMss(MethodSecurityService mss) { public ElementsResponse read(String projectId, String refId, ElementsRequest req, Map params) { - String commitId = params.getOrDefault("commitId", null); - ContextHolder.setContext(projectId, refId); - NodeGetInfo info = nodeGetHelper.processGetJson(req.getElements(), commitId, this); + String commitId = params.getOrDefault(CrudConstants.COMMITID, null); + if (commitId == null) { + Optional commitJson = commitPersistence.findLatestByProjectAndRef(projectId, refId); + if (!commitJson.isPresent()) { + throw new InternalErrorException("Could not find latest commit for project and ref"); + } + commitId = commitJson.get().getId(); + } + NodeGetInfo info = getNodePersistence().findAll(projectId, refId, commitId, req.getElements()); + info.getActiveElementMap().values().forEach((e) -> e.setRefId(refId)); if (!info.getRejected().isEmpty()) { //continue looking in visible mounted projects for elements if not all found NodeGetInfo curInfo = info; List> usages = new ArrayList<>(); - getProjectUsages(projectId, refId, commitId, usages); + getProjectUsages(projectId, refId, commitId, usages, true); int i = 1; //0 is entry project, already gotten while (!curInfo.getRejected().isEmpty() && i < usages.size()) { + final int j = i; ElementsRequest reqNext = buildRequest(curInfo.getRejected().keySet()); - ContextHolder.setContext(usages.get(i).getFirst(), usages.get(i).getSecond()); - //TODO use the right commitId in child if commitId is present in params - curInfo = nodeGetHelper.processGetJson(reqNext.getElements(), "", this); + //TODO use the right commitId in child if commitId is present in params :: same commit Id is not working for child + curInfo = getNodePersistence().findAll(usages.get(i).getFirst(), usages.get(i).getSecond(), "", reqNext.getElements()); + curInfo.getActiveElementMap().values().forEach((e) -> e.setRefId(usages.get(j).getSecond())); info.getActiveElementMap().putAll(curInfo.getActiveElementMap()); curInfo.getActiveElementMap().forEach((id, json) -> info.getRejected().remove(id)); curInfo.getRejected().forEach((id, rejection) -> { @@ -76,42 +81,42 @@ public ElementsResponse read(String projectId, String refId, ElementsRequest req ElementsResponse response = new ElementsResponse(); response.getElements().addAll(info.getActiveElementMap().values()); response.setRejected(new ArrayList<>(info.getRejected().values())); + response.setCommitId(commitId); return response; } @Override - public void extraProcessPostedElement(ElementJson element, Node node, NodeChangeInfo info) { - node.setNodeType(cameoHelper.getNodeType(element).getValue()); + public void extraProcessPostedElement(NodeChangeInfo info, ElementJson element) { //remove _childViews if posted element.remove(CameoConstants.CHILDVIEWS); } @Override - public void extraProcessGotElement(ElementJson element, Node node, NodeGetInfo info) { - //TODO extended info? (qualified name/id) - } - - public MountJson getProjectUsages(String projectId, String refId, String commitId, List> saw) { - ContextHolder.setContext(projectId, refId); + public MountJson getProjectUsages(String projectId, String refId, String commitId, List> saw, + boolean restrictOnPermissions) { saw.add(Pair.of(projectId, refId)); - List mountNodes = nodeRepository.findAllByNodeType(CameoNodeType.PROJECTUSAGE.getValue()); - Set mountIds = new HashSet<>(); - mountNodes.forEach(n -> mountIds.add(n.getNodeId())); - Map params = new HashMap<>(); - params.put("commitId", commitId); - ElementsResponse mountsJson = super.read(projectId, refId, buildRequest(mountIds), params); + List mounts = getNodePersistence().findAllByNodeType(projectId, refId, commitId, + CameoNodeType.PROJECTUSAGE.getValue()); Authentication auth = SecurityContextHolder.getContext().getAuthentication(); List mountValues = new ArrayList<>(); - for (ElementJson mount: mountsJson.getElements()) { + for (ElementJson mount : mounts) { String mountedProjectId = (String)mount.get(CameoConstants.MOUNTEDELEMENTPROJECTID); String mountedRefId = (String)mount.get(CameoConstants.MOUNTEDREFID); + if (mountedProjectId == null) { + logger.error("Could not find Mounted Project Id"); + continue; + } + if (mountedRefId == null) { + logger.error("Could not find Mounted Ref Id for Project ID: {}" , mountedProjectId); + continue; + } if (saw.contains(Pair.of(mountedProjectId, mountedRefId))) { //prevent circular dependencies or dups - should it be by project or by project and ref? continue; } try { - if (!mss.hasBranchPrivilege(auth, mountedProjectId, mountedRefId, + if (restrictOnPermissions && !mss.hasBranchPrivilege(auth, mountedProjectId, mountedRefId, Privileges.BRANCH_READ.name(), true)) { //should permission be considered here? continue; @@ -120,7 +125,12 @@ public MountJson getProjectUsages(String projectId, String refId, String commitI continue; } //doing a depth first traversal TODO get appropriate commitId - mountValues.add(getProjectUsages(mountedProjectId, mountedRefId, "", saw)); + try { + mountValues.add(getProjectUsages(mountedProjectId, mountedRefId, "", saw, restrictOnPermissions)); + } catch (Exception e) { + //log the error and move on + logger.debug(String.format("Could not get project usages from nested project %s" , mountedProjectId), e); + } } MountJson res = new MountJson(); res.setId(projectId); diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoProjectService.java b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoProjectService.java index f11da5116..a080b71ea 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoProjectService.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoProjectService.java @@ -47,11 +47,10 @@ public ProjectJson create(ProjectJson project) { } @Override - public RefJson createRefJson(ProjectJson project, String docId){ - RefJson branchJson = super.createRefJson(project, docId); + public RefJson createMasterRefJson(ProjectJson project){ + RefJson branchJson = super.createMasterRefJson(project); branchJson.put("twcId",Constants.MASTER_BRANCH); return branchJson; - } private static ElementJson createNode(String id, String name, ProjectJson projectJson) { diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java index c878ee5cb..c0749c4a6 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java +++ b/cameo/src/main/java/org/openmbee/mms/cameo/services/CameoViewService.java @@ -10,44 +10,52 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; + import org.openmbee.mms.cameo.CameoConstants; import org.openmbee.mms.cameo.CameoNodeType; import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.dao.NodePersistence; import org.openmbee.mms.core.objects.ElementsRequest; import org.openmbee.mms.core.objects.ElementsResponse; import org.openmbee.mms.core.services.NodeChangeInfo; import org.openmbee.mms.core.services.NodeGetInfo; -import org.openmbee.mms.data.domains.scoped.Node; +import org.openmbee.mms.crud.domain.JsonDomain; import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.view.services.PropertyData; +import org.openmbee.mms.view.services.ViewService; import org.springframework.stereotype.Service; @Service("cameoViewService") -public class CameoViewService extends CameoNodeService { +public class CameoViewService extends CameoNodeService implements ViewService { + @Override public ElementsResponse getDocuments(String projectId, String refId, Map params) { - ContextHolder.setContext(projectId, refId); - List documents = this.nodeRepository.findAllByNodeType(CameoNodeType.DOCUMENT.getValue()); - ElementsResponse res = this.getViews(projectId, refId, buildRequest(nodeGetHelper.convertNodesToMap(documents).keySet()), params); + String commitId = params.getOrDefault(CameoConstants.COMMITID, null); + List documents = getNodePersistence().findAllByNodeType(projectId, refId, + commitId, CameoNodeType.DOCUMENT.getValue()); + ElementsResponse res = this.getViews(projectId, refId, buildRequestFromJsons(documents), params); for (ElementJson e: res.getElements()) { - Optional parent = nodeGetHelper.getFirstRelationshipOfType(e, - Arrays.asList(CameoNodeType.GROUP.getValue()), CameoConstants.OWNERID); - if (parent.isPresent()) { - e.put("_groupId", parent.get().getId()); - } + Optional parent = getFirstRelationshipOfType(projectId, refId, commitId, e, + List.of(CameoNodeType.GROUP.getValue()), CameoConstants.OWNERID); + parent.ifPresent(elementJson -> e.put(CameoConstants.SITECHARACTERIZATIONID, elementJson.getId())); + } return res; } + @Override public ElementsResponse getView(String projectId, String refId, String elementId, Map params) { return this.getViews(projectId, refId, buildRequest(elementId), params); } + @Override public ElementsResponse getViews(String projectId, String refId, ElementsRequest req, Map params) { ElementsResponse res = this.read(projectId, refId, req, params); addChildViews(res, params); return res; } + @Override public void addChildViews(ElementsResponse res, Map params) { for (ElementJson element: res.getElements()) { if (cameoHelper.isView(element)) { @@ -57,11 +65,11 @@ public void addChildViews(ElementsResponse res, Map params) { } ElementsResponse ownedAttributes = this.read(element.getProjectId(), element.getRefId(), buildRequest(ownedAttributeIds), params); - List sorted = nodeGetHelper.sort(ownedAttributeIds, ownedAttributes.getElements()); + List filtered = JsonDomain.filter(ownedAttributeIds, ownedAttributes.getElements()); List childViews = new ArrayList<>(); - for (ElementJson attr : sorted) { + for (ElementJson attr : filtered) { String childId = (String) attr.get(CameoConstants.TYPEID); - if ("Property".equals(attr.getType()) && childId != null && !childId.isEmpty()) { + if (CameoConstants.PROPERTY.equals(attr.getType()) && childId != null && !childId.isEmpty()) { Map child = new HashMap<>(); child.put(ElementJson.ID, childId); child.put(CameoConstants.AGGREGATION, (String) attr.get(CameoConstants.AGGREGATION)); @@ -75,42 +83,47 @@ public void addChildViews(ElementsResponse res, Map params) { } public ElementsResponse getGroups(String projectId, String refId, Map params) { - ContextHolder.setContext(projectId, refId); - List groups = this.nodeRepository.findAllByNodeType(CameoNodeType.GROUP.getValue()); - ElementsResponse res = this.read(projectId, refId, buildRequest(nodeGetHelper.convertNodesToMap(groups).keySet()), params); - for (ElementJson e: res.getElements()) { - Optional parent = nodeGetHelper.getFirstRelationshipOfType(e, - Arrays.asList(CameoNodeType.GROUP.getValue()), CameoConstants.OWNERID); - if (parent.isPresent()) { - e.put("_parentId", parent.get().getId()); - } + String commitId = params.getOrDefault(CameoConstants.COMMITID, null); + List groups = getNodePersistence().findAllByNodeType(projectId, refId, commitId, + CameoNodeType.GROUP.getValue()); + + ElementsResponse res = new ElementsResponse().setElements(groups); + for (ElementJson e: groups) { + Optional parent = getFirstRelationshipOfType(projectId, refId, commitId, e, + List.of(CameoNodeType.GROUP.getValue()), CameoConstants.OWNERID); + parent.ifPresent(elementJson -> e.put(CameoConstants.PARENTID, elementJson.getId())); } return res; } @Override - public void extraProcessPostedElement(ElementJson element, Node node, NodeChangeInfo info) { + public void extraProcessPostedElement(NodeChangeInfo info, ElementJson element) { //handle _childViews List> newChildViews = (List)element.remove(CameoConstants.CHILDVIEWS); if (newChildViews == null) { - super.extraProcessPostedElement(element, node, info); + super.extraProcessPostedElement(info, element); return; } //gather data on "old" attributes List oldOwnedAttributeIds = (List)element.get(CameoConstants.OWNEDATTRIBUTEIDS); //use helper to get access to Nodes - NodeGetInfo oldInfo = nodeGetHelper.processGetJson(buildRequest(oldOwnedAttributeIds).getElements(), null); + String projectId = info.getCommitJson().getProjectId(); + String refId = info.getCommitJson().getRefId(); + NodeGetInfo oldInfo = getNodePersistence().findAll(projectId, refId, null, + buildRequest(oldOwnedAttributeIds).getElements()); List oldProperties = new ArrayList<>(); Map oldPropertiesTypeMapping = new HashMap<>(); //typeId to PropertyData for (String oldOwnedAttributeId: oldOwnedAttributeIds) { if (!oldInfo.getActiveElementMap().containsKey(oldOwnedAttributeId)) { continue; //property doesn't exist anymore? indicates existing model inconsistency } - Node oldNode = oldInfo.getExistingNodeMap().get(oldOwnedAttributeId); + //TODO This probably breaks view editor. move to federated domain somehow + //Node oldNode = oldInfo.getExistingNodeMap().get(oldOwnedAttributeId); ElementJson oldJson = oldInfo.getActiveElementMap().get(oldOwnedAttributeId); PropertyData oldData = new PropertyData(); oldData.setPropertyJson(oldJson); - oldData.setPropertyNode(oldNode); + //TODO This probably breaks view editor. move to federated domain somehow + //oldData.setPropertyNode(oldNode); oldProperties.add(oldData); String typeId = (String)oldJson.get(CameoConstants.TYPEID); if (typeId == null || typeId.isEmpty()) { @@ -120,7 +133,7 @@ public void extraProcessPostedElement(ElementJson element, Node node, NodeChange } //include project usages when finding types ElementsResponse oldTypeJsons = this.read(element.getProjectId(), element.getRefId(), - buildRequest(oldPropertiesTypeMapping.keySet()), Collections.EMPTY_MAP); + buildRequest(oldPropertiesTypeMapping.keySet()), Collections.emptyMap()); for (ElementJson oldType: oldTypeJsons.getElements()) { oldPropertiesTypeMapping.get(oldType.getId()).setTypeJson(oldType); oldPropertiesTypeMapping.get(oldType.getId()).setView(cameoHelper.isView(oldType)); @@ -132,7 +145,7 @@ public void extraProcessPostedElement(ElementJson element, Node node, NodeChange //go through requested _childView changes //get the first package element that's in the owner chain of parent class // cameo/sysml1 requires associations to be placed in the first owning package, is this rule still valid? - Optional p = nodePostHelper.getFirstRelationshipOfType(element, + Optional p = getFirstRelationshipOfType(projectId, refId, null, element, Arrays.asList(CameoNodeType.PACKAGE.getValue(), CameoNodeType.GROUP.getValue()), CameoConstants.OWNERID); String packageId = p.isPresent() ? p.get().getId() : CameoConstants.HOLDING_BIN_PREFIX + element.getProjectId(); List newProperties = new ArrayList<>(); @@ -143,21 +156,21 @@ public void extraProcessPostedElement(ElementJson element, Node node, NodeChange //existing property and type, reuse PropertyData data = oldPropertiesTypeMapping.get(typeId); newProperties.add(data); - newAttributeIds.add(data.getPropertyNode().getNodeId()); + newAttributeIds.add(data.getPropertyJson().getId()); continue; } //create new properties and association - PropertyData newProperty = createElementsForView(newChildView.get(CameoConstants.AGGREGATION), - typeId, element.getId(), packageId, info); + PropertyData newProperty = createElementsForView(info, newChildView.get(CameoConstants.AGGREGATION), + typeId, element.getId(), packageId); newProperties.add(newProperty); - newAttributeIds.add(newProperty.getPropertyNode().getNodeId()); + newAttributeIds.add(newProperty.getPropertyJson().getId()); } //go through old attributes and add back any that wasn't to a view and delete ones that's to a view but not in newProperties List toDelete = new ArrayList<>(); for (PropertyData oldProperty: oldProperties) { if (!oldProperty.isView()) { newProperties.add(oldProperty); - newAttributeIds.add(oldProperty.getPropertyNode().getNodeId()); + newAttributeIds.add(oldProperty.getPropertyJson().getId()); continue; } if (newProperties.contains(oldProperty)) { @@ -165,17 +178,14 @@ public void extraProcessPostedElement(ElementJson element, Node node, NodeChange } toDelete.add(oldProperty); } - deletePropertyElements(toDelete, info); + deletePropertyElements(projectId, refId, toDelete, info); //new derived ownedAttributeIds based on changes element.put(CameoConstants.OWNEDATTRIBUTEIDS, newAttributeIds); - super.extraProcessPostedElement(element, node, info); + super.extraProcessPostedElement(info, element); } - private PropertyData createElementsForView(String aggregation, String typeId, String parentId, String packageId, NodeChangeInfo info) { + private PropertyData createElementsForView(NodeChangeInfo info, String aggregation, String typeId, String parentId, String packageId) { //create new properties and association - Node newPropertyNode = new Node(); - Node newAssocNode = new Node(); - Node newAssocPropertyNode = new Node(); String newPropertyId = UUID.randomUUID().toString(); String newAssocId = UUID.randomUUID().toString(); String newAssocPropertyId = UUID.randomUUID().toString(); @@ -184,30 +194,26 @@ private PropertyData createElementsForView(String aggregation, String typeId, St ElementJson newAssocJson = cameoHelper.createAssociation(newAssocId, packageId, newAssocPropertyId, newPropertyId); ElementJson newAssocPropertyJson = cameoHelper.createProperty(newAssocPropertyId, "", - newAssocId, "none", parentId, newAssocId); - nodePostHelper.processElementAdded(newPropertyJson, newPropertyNode, info); - nodePostHelper.processElementAdded(newAssocJson, newAssocNode, info); - nodePostHelper.processElementAdded(newAssocPropertyJson, newAssocPropertyNode, info); - super.extraProcessPostedElement(newPropertyJson, newPropertyNode, info); - super.extraProcessPostedElement(newAssocJson, newAssocNode, info); - super.extraProcessPostedElement(newAssocPropertyJson, newAssocPropertyNode, info); + newAssocId, CameoConstants.NONE, parentId, newAssocId); + + NodePersistence nodeChangeDomain = getNodePersistence(); + nodeChangeDomain.prepareAddsUpdates(info, List.of(newPropertyJson, newAssocJson, newAssocPropertyJson)); + super.extraProcessPostedElement(info, newPropertyJson); + super.extraProcessPostedElement(info, newAssocJson); + super.extraProcessPostedElement(info, newAssocPropertyJson); PropertyData newProperty = new PropertyData(); newProperty.setAssocJson(newAssocJson); - newProperty.setAssocNode(newAssocNode); newProperty.setPropertyJson(newPropertyJson); - newProperty.setPropertyNode(newPropertyNode); - newProperty.setAssocPropertyNode(newAssocPropertyNode); newProperty.setAssocPropertyJson(newAssocPropertyJson); newProperty.setView(true); return newProperty; } - private void deletePropertyElements(List properties, NodeChangeInfo info) { + private void deletePropertyElements(String projectId, String refId, List properties, NodeChangeInfo info) { Set assocToDelete = new HashSet<>(); for (PropertyData oldProperty: properties) { - Node oldPropertyNode = oldProperty.getPropertyNode(); ElementJson oldPropertyJson = oldProperty.getPropertyJson(); - nodePostHelper.processElementDeleted(oldPropertyJson, oldPropertyNode, info); + getNodePersistence().prepareDeletes(info, List.of(oldPropertyJson)); String assocId = (String)oldPropertyJson.get(CameoConstants.ASSOCIATIONID); if (assocId == null) { continue; @@ -215,20 +221,42 @@ private void deletePropertyElements(List properties, NodeChangeInf assocToDelete.add(assocId); } Set assocPropToDelete = new HashSet<>(); - NodeGetInfo assocInfo = nodeGetHelper.processGetJson(buildRequest(assocToDelete).getElements(), null); + NodeGetInfo assocInfo = getNodePersistence().findAll(projectId, refId, null, buildRequest(assocToDelete).getElements()); for (ElementJson assocJson: assocInfo.getActiveElementMap().values()) { - Node assocNode = assocInfo.getExistingNodeMap().get(assocJson.getId()); - nodePostHelper.processElementDeleted(assocJson, assocNode, info); + getNodePersistence().prepareDeletes(info, List.of(assocJson)); List ownedEndIds = (List)assocJson.get(CameoConstants.OWNEDENDIDS); if (ownedEndIds == null) { continue; } assocPropToDelete.addAll(ownedEndIds); } - NodeGetInfo assocPropInfo = nodeGetHelper.processGetJson(buildRequest(assocPropToDelete).getElements(), null); - for (ElementJson assocPropJson: assocPropInfo.getActiveElementMap().values()) { - Node assocPropNode = assocPropInfo.getExistingNodeMap().get(assocPropJson.getId()); - nodePostHelper.processElementDeleted(assocPropJson, assocPropNode, info); + NodeGetInfo assocPropInfo = getNodePersistence().findAll(projectId, refId, null, buildRequest(assocPropToDelete).getElements()); + getNodePersistence().prepareDeletes(info, assocPropInfo.getActiveElementMap().values()); + } + + //find first element of type in types following e's relkey (assuming relkey's value is an element id) + private Optional getFirstRelationshipOfType(String projectId, String refId, String commitId, + ElementJson e, List types, String relkey) { + //only for latest graph + String nextId = (String)e.get(relkey); + if (nextId == null || nextId.isEmpty()) { + return Optional.empty(); + } + + NodeGetInfo getInfo = nodePersistence.findById(projectId, refId, commitId, nextId); + Optional next = Optional.of(getInfo.getActiveElementMap().get(nextId)); + + while (next.isPresent()) { + if (types.contains(cameoHelper.getNodeType(next.get()).getValue())) { + return next; + } + nextId = (String)next.get().get(relkey); + if (nextId == null || nextId.isEmpty()) { + return Optional.empty(); + } + getInfo = nodePersistence.findById(projectId, refId, commitId, nextId); + next = Optional.of(getInfo.getActiveElementMap().get(nextId)); } + return Optional.empty(); } } diff --git a/core/core.gradle b/core/core.gradle index 868a6ebb9..dc672e804 100644 --- a/core/core.gradle +++ b/core/core.gradle @@ -1,7 +1,13 @@ dependencies { - api project(':data') + api project(':json') + api commonDependencies.'jackson-annotations' + api commonDependencies.'jackson-databind' + api commonDependencies.'jackson-datatype-hibernate5' + api commonDependencies.'jackson-datatype-jsr310' + + api commonDependencies.'spring-data-commons' api commonDependencies.'spring-security-config' api commonDependencies.'spring-webmvc' api commonDependencies.'spring-tx' @@ -10,4 +16,10 @@ dependencies { implementation commonDependencies.'servlet-api' testImplementation commonDependencies.'spring-boot-starter-test' +} + +tasks { + processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } } \ No newline at end of file diff --git a/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdateResponseBuilder.java b/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdateResponseBuilder.java index f9043b7b9..f9a143d8a 100644 --- a/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdateResponseBuilder.java +++ b/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdateResponseBuilder.java @@ -1,18 +1,12 @@ package org.openmbee.mms.core.builders; import org.openmbee.mms.core.objects.PermissionUpdateResponse; -import org.openmbee.mms.data.domains.global.BranchGroupPerm; -import org.openmbee.mms.data.domains.global.BranchUserPerm; -import org.openmbee.mms.data.domains.global.OrgGroupPerm; -import org.openmbee.mms.data.domains.global.OrgUserPerm; -import org.openmbee.mms.data.domains.global.ProjectGroupPerm; -import org.openmbee.mms.data.domains.global.ProjectUserPerm; import java.util.*; public class PermissionUpdateResponseBuilder { - private class PermissionUpdateWrapper { + protected static class PermissionUpdateWrapper { private PermissionUpdateResponse.PermissionUpdate permissionUpdate; @@ -50,93 +44,6 @@ public PermissionUpdateResponseBuilder insert(PermissionUpdateResponse updateUse return this; } - public void insertPermissionUpdates_OrgUserPerm(PermissionUpdateResponse.Action action, Collection perms) { - perms.forEach(v -> insertPermissionUpdate(action, v)); - } - - public void insertPermissionUpdate(PermissionUpdateResponse.Action action, OrgUserPerm v) { - if(v == null) - return; - - PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( - action, v.getUser().getUsername(), v.getRole().getName(), v.getOrganization().getOrganizationId(), v.getOrganization().getOrganizationName(), - null, null, null, false); - doInsert(update); - } - - public void insertPermissionUpdates_OrgGroupPerm(PermissionUpdateResponse.Action action, Collection perms) { - perms.forEach(v -> insertPermissionUpdate(action, v)); - } - - public void insertPermissionUpdate(PermissionUpdateResponse.Action action, OrgGroupPerm v) { - if(v == null) - return; - - PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( - action, v.getGroup().getName(), v.getRole().getName(), v.getOrganization().getOrganizationId(), v.getOrganization().getOrganizationName(), - null, null, null, false); - doInsert(update); - } - - public void insertPermissionUpdates_ProjectUserPerm(PermissionUpdateResponse.Action action, Collection perms) { - perms.forEach(v -> insertPermissionUpdate(action, v)); - } - - public void insertPermissionUpdate(PermissionUpdateResponse.Action action, ProjectUserPerm v) { - if(v == null) - return; - - PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( - action, v.getUser().getUsername(), v.getRole().getName(), v.getProject().getOrganization().getOrganizationId(), - v.getProject().getOrganization().getOrganizationName(), v.getProject().getProjectId(), v.getProject().getProjectName(), - null, v.isInherited()); - doInsert(update); - } - - public void insertPermissionUpdates_ProjectGroupPerm(PermissionUpdateResponse.Action action, Collection perms) { - perms.forEach(v -> insertPermissionUpdate(action, v)); - } - - public void insertPermissionUpdate(PermissionUpdateResponse.Action action, ProjectGroupPerm v) { - if(v == null) - return; - - PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( - action, v.getGroup().getName(), v.getRole().getName(), v.getProject().getOrganization().getOrganizationId(), - v.getProject().getOrganization().getOrganizationName(), v.getProject().getProjectId(), v.getProject().getProjectName(), - null, v.isInherited()); - doInsert(update); - } - - public void insertPermissionUpdates_BranchUserPerm(PermissionUpdateResponse.Action action, Collection perms) { - perms.forEach(v -> insertPermissionUpdate(action, v)); - } - - public void insertPermissionUpdate(PermissionUpdateResponse.Action action, BranchUserPerm v) { - if(v == null) - return; - - PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( - action, v.getUser().getUsername(), v.getRole().getName(), v.getBranch().getProject().getOrganization().getOrganizationId(), - v.getBranch().getProject().getOrganization().getOrganizationName(), v.getBranch().getProject().getProjectId(), - v.getBranch().getProject().getProjectName(), v.getBranch().getBranchId(), v.isInherited()); - doInsert(update); - } - - public void insertPermissionUpdates_BranchGroupPerm(PermissionUpdateResponse.Action action, Collection perms) { - perms.forEach(v -> insertPermissionUpdate(action, v)); - } - - public void insertPermissionUpdate(PermissionUpdateResponse.Action action, BranchGroupPerm v) { - if(v == null) - return; - - PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( - action, v.getGroup().getName(), v.getRole().getName(), v.getBranch().getProject().getOrganization().getOrganizationId(), - v.getBranch().getProject().getOrganization().getOrganizationName(), v.getBranch().getProject().getProjectId(), - v.getBranch().getProject().getProjectName(),v.getBranch().getBranchId(), v.isInherited()); - doInsert(update); - } public PermissionUpdateResponse getPermissionUpdateResponse() { PermissionUpdateResponse response = new PermissionUpdateResponse(); @@ -147,7 +54,7 @@ public PermissionUpdateResponse getPermissionUpdateResponse() { return response; } - private void doInsert(PermissionUpdateResponse.PermissionUpdate update) { + protected void doInsert(PermissionUpdateResponse.PermissionUpdate update) { PermissionUpdateWrapper wrapped = new PermissionUpdateWrapper(update); if(update.getAction() == PermissionUpdateResponse.Action.ADD) { if(! removed.remove(wrapped)) { @@ -156,5 +63,6 @@ private void doInsert(PermissionUpdateResponse.PermissionUpdate update) { } else if(! added.remove(wrapped)){ removed.add(wrapped); } + } } diff --git a/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdatesResponseBuilder.java b/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdatesResponseBuilder.java index e6640d13a..4ed15275a 100644 --- a/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdatesResponseBuilder.java +++ b/core/src/main/java/org/openmbee/mms/core/builders/PermissionUpdatesResponseBuilder.java @@ -5,8 +5,8 @@ public class PermissionUpdatesResponseBuilder { - private Boolean inherit; - private Boolean isPublic; + protected Boolean inherit; + protected Boolean isPublic; private PermissionUpdateResponseBuilder usersBuilder = new PermissionUpdateResponseBuilder(); private PermissionUpdateResponseBuilder groupsBuilder = new PermissionUpdateResponseBuilder(); @@ -56,12 +56,12 @@ public PermissionUpdateResponseBuilder getGroups() { } private Boolean or(Boolean a, Boolean b) { - if(a == b) { - return a; - } - if(a == null) { + if (a == null) { return b; } + if (b == null) { + return a; + } return a || b; } } diff --git a/core/src/main/java/org/openmbee/mms/core/config/Constants.java b/core/src/main/java/org/openmbee/mms/core/config/Constants.java index ee6b30275..c70762353 100644 --- a/core/src/main/java/org/openmbee/mms/core/config/Constants.java +++ b/core/src/main/java/org/openmbee/mms/core/config/Constants.java @@ -11,25 +11,40 @@ public class Constants { public static final String ELEMENT_KEY = "elements"; public static final String COMMIT_KEY = "commits"; public static final String WEBHOOK_KEY = "webhooks"; + public static final String BRANCH_TYPE = "Branch"; + public static final String ELEMENT_TYPE = "element"; + public static final String REF_ID = "refId"; + public static final String ID_KEY = "id"; public static final String REJECTED = "rejected"; public static final String MESSAGES = "messages"; public static final String CODE = "code"; + public static final String TRUE= "true"; + public static final String FALSE = "false"; + public static final String LIMIT = "limit"; public static final String MASTER_BRANCH = "master"; public static final Pattern BRANCH_ID_VALID_PATTERN = Pattern.compile("^[\\w-]+$"); - public static final Map RPmap = new LinkedHashMap<>(); + public static final Map> RPmap = new LinkedHashMap<>(); public static final List aPriv; public static final List rPriv; public static final List wPriv; + + public static final String ADMIN = "ADMIN"; + public static final String READER = "READER"; + public static final String WRITER = "WRITER"; + + public static final String NOT_FOUND = "Not Found"; + public static final String ELEMENT_DELETE = "Element Already Deleted"; + static { aPriv = Arrays.asList("ORG_READ", "ORG_EDIT", "ORG_UPDATE_PERMISSIONS", "ORG_READ_PERMISSIONS", "ORG_CREATE_PROJECT", "ORG_DELETE", "PROJECT_READ", "PROJECT_EDIT", "PROJECT_READ_COMMITS", "PROJECT_CREATE_BRANCH", "PROJECT_DELETE", "PROJECT_UPDATE_PERMISSIONS", "PROJECT_READ_PERMISSIONS", "PROJECT_CREATE_WEBHOOKS", "BRANCH_READ", "BRANCH_EDIT_CONTENT", "BRANCH_DELETE", "BRANCH_UPDATE_PERMISSIONS", "BRANCH_READ_PERMISSIONS"); rPriv = Arrays.asList("ORG_READ", "ORG_READ_PERMISSIONS", "PROJECT_READ", "PROJECT_READ_COMMITS", "PROJECT_READ_PERMISSIONS", "BRANCH_READ", "BRANCH_READ_PERMISSIONS"); wPriv = Arrays.asList("ORG_READ", "ORG_EDIT", "ORG_READ_PERMISSIONS", "ORG_CREATE_PROJECT", "PROJECT_READ", "PROJECT_EDIT", "PROJECT_READ_COMMITS", "PROJECT_CREATE_BRANCH", "PROJECT_READ_PERMISSIONS", "PROJECT_CREATE_WEBHOOKS", "BRANCH_READ", "BRANCH_EDIT_CONTENT", "BRANCH_READ_PERMISSIONS"); - RPmap.put("ADMIN", aPriv); - RPmap.put("READER", rPriv); - RPmap.put("WRITER", wPriv); + RPmap.put(ADMIN, aPriv); + RPmap.put(READER, rPriv); + RPmap.put(WRITER, wPriv); } } diff --git a/core/src/main/java/org/openmbee/mms/core/config/ContextHolder.java b/core/src/main/java/org/openmbee/mms/core/config/ContextHolder.java index 2dd8c62b0..a08a93a9e 100644 --- a/core/src/main/java/org/openmbee/mms/core/config/ContextHolder.java +++ b/core/src/main/java/org/openmbee/mms/core/config/ContextHolder.java @@ -12,7 +12,7 @@ public static ContextObject getContext() { } public static void setContext(String projectId) { - contextHolder.set(new ContextObject(projectId)); + setContext(projectId, getContext().getProjectId().equals(projectId) ? getContext().getBranchId() : Constants.MASTER_BRANCH); } public static void setContext(String projectId, String refId) { diff --git a/core/src/main/java/org/openmbee/mms/core/config/Formats.java b/core/src/main/java/org/openmbee/mms/core/config/Formats.java index 4212b96a1..9666837b3 100644 --- a/core/src/main/java/org/openmbee/mms/core/config/Formats.java +++ b/core/src/main/java/org/openmbee/mms/core/config/Formats.java @@ -1,14 +1,15 @@ package org.openmbee.mms.core.config; -import java.text.SimpleDateFormat; import java.time.ZoneId; import java.time.format.DateTimeFormatter; public class Formats { - public static String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; - public static SimpleDateFormat SDF = new SimpleDateFormat(DATE_FORMAT); - public static DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT).withZone( + public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT).withZone( ZoneId.systemDefault()); + private Formats() { + throw new IllegalStateException("Formats"); + } } diff --git a/core/src/main/java/org/openmbee/mms/core/dao/BranchPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/BranchPersistence.java new file mode 100644 index 000000000..351381438 --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/dao/BranchPersistence.java @@ -0,0 +1,21 @@ +package org.openmbee.mms.core.dao; + +import org.openmbee.mms.json.RefJson; + +import java.util.List; +import java.util.Optional; + +public interface BranchPersistence { + + RefJson save(RefJson refJson); + + RefJson update(RefJson refJson); + + List findAll(String projectId); + + Optional findById(String projectId, String refId); + + Optional deleteById(String projectId, String refId); + + boolean inheritsPermissions(String projectId, String branchId); +} diff --git a/core/src/main/java/org/openmbee/mms/core/dao/CommitPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/CommitPersistence.java new file mode 100644 index 000000000..ee10a8c8d --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/dao/CommitPersistence.java @@ -0,0 +1,30 @@ +package org.openmbee.mms.core.dao; + +import org.openmbee.mms.json.CommitJson; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public interface CommitPersistence { + + CommitJson save(CommitJson commitJson, Instant now); + + CommitJson update(CommitJson commitJson); + + Optional findById(String projectId, String commitId); + + List findAllById(String projectId, Set commitIds); + + List findAllByProjectId(String projectId); + + Optional findLatestByProjectAndRef(String projectId, String refId); + + List findByProjectAndRefAndTimestampAndLimit(String projectId, String refId, Instant timestamp, int limit); + + List elementHistory(String projectId, String refId, String elementId); + + Optional deleteById(String projectId, String commitId); + +} diff --git a/core/src/main/java/org/openmbee/mms/core/dao/GroupPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/GroupPersistence.java new file mode 100644 index 000000000..b7622f251 --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/dao/GroupPersistence.java @@ -0,0 +1,14 @@ +package org.openmbee.mms.core.dao; + +import org.openmbee.mms.json.GroupJson; + +import java.util.Collection; +import java.util.Optional; + +public interface GroupPersistence { + + GroupJson save(GroupJson groupJson); + void delete(GroupJson groupJson); + Optional findByName(String name); + Collection findAll(); +} diff --git a/core/src/main/java/org/openmbee/mms/core/dao/NodePersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/NodePersistence.java new file mode 100644 index 000000000..e06c7414c --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/dao/NodePersistence.java @@ -0,0 +1,35 @@ +package org.openmbee.mms.core.dao; + +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.json.RefJson; + +import java.io.OutputStream; +import java.util.Collection; +import java.util.List; + +public interface NodePersistence { + + + NodeChangeInfo prepareChange(CommitJson commitJson, boolean overwrite, boolean preserveTimestamps); + + NodeChangeInfo prepareAddsUpdates(NodeChangeInfo nodeChangeInfo, Collection elements); + + NodeChangeInfo prepareDeletes(NodeChangeInfo nodeChangeInfo, Collection jsons); + + NodeChangeInfo commitChanges(NodeChangeInfo nodeChangeInfo); + + NodeGetInfo findById(String projectId, String refId, String commitId, String elementId); + + List findAllByNodeType(String projectId, String refId, String commitId, int nodeType); + + NodeGetInfo findAll(String projectId, String refId, String commitId, List elements); + + List findAll(String projectId, String refId, String commitId); + + void streamAllAtCommit(String projectId, String refId, String commitId, OutputStream outputStream, String separator); + + void branchElements(RefJson parentBranch, CommitJson parentCommit, RefJson targetBranch); +} diff --git a/core/src/main/java/org/openmbee/mms/core/dao/OrgPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/OrgPersistence.java new file mode 100644 index 000000000..c9dd84799 --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/dao/OrgPersistence.java @@ -0,0 +1,19 @@ +package org.openmbee.mms.core.dao; + +import org.openmbee.mms.json.OrgJson; + +import java.util.Collection; +import java.util.Optional; + +public interface OrgPersistence { + + OrgJson save(OrgJson orgJson); + + Optional findById(String orgId); + + Collection findAll(); + + OrgJson deleteById(String orgId); + + boolean hasPublicPermissions(String orgId); +} diff --git a/core/src/main/java/org/openmbee/mms/core/dao/ProjectPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/ProjectPersistence.java new file mode 100644 index 000000000..3bf83cc6e --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/dao/ProjectPersistence.java @@ -0,0 +1,31 @@ +package org.openmbee.mms.core.dao; + +import org.openmbee.mms.json.ProjectJson; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public interface ProjectPersistence { + + ProjectJson save(ProjectJson projectJson); + + ProjectJson update(ProjectJson projectJson); + + Optional findById(String projectId); + + List findAllById(Set projectIds); + + List findAll(); + + Collection findAllByOrgId(String orgId); + + void softDelete(String projectId); + + void hardDelete(String projectId); + + boolean inheritsPermissions(String projectId); + + boolean hasPublicPermissions(String projectId); +} diff --git a/core/src/main/java/org/openmbee/mms/core/dao/UserGroupsPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/UserGroupsPersistence.java new file mode 100644 index 000000000..f8cf58b16 --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/dao/UserGroupsPersistence.java @@ -0,0 +1,15 @@ +package org.openmbee.mms.core.dao; + +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.UserJson; + +import java.util.Collection; + +public interface UserGroupsPersistence { + + boolean addUserToGroup(String groupName, String username); + boolean removeUserFromGroup(String groupName, String username); + Collection findUsersInGroup(String groupName); + Collection findGroupsAssignedToUser(String username); + +} diff --git a/core/src/main/java/org/openmbee/mms/core/dao/UserPersistence.java b/core/src/main/java/org/openmbee/mms/core/dao/UserPersistence.java new file mode 100644 index 000000000..9876f5438 --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/dao/UserPersistence.java @@ -0,0 +1,14 @@ +package org.openmbee.mms.core.dao; + +import org.openmbee.mms.json.UserJson; + +import java.util.Collection; +import java.util.Optional; + +public interface UserPersistence { + + UserJson save(UserJson user); + Optional findByUsername(String username); + Collection findAll(); + +} diff --git a/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegate.java b/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegate.java index e6dd91f2c..88fb35a37 100644 --- a/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegate.java +++ b/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegate.java @@ -9,9 +9,11 @@ public interface PermissionsDelegate { boolean hasPermission(String user, Set groups, String privilege); + boolean hasGroupPermissions(String group, String privilege); void initializePermissions(String creator); void initializePermissions(String creator, boolean inherit); boolean setInherit(boolean isInherit); + PermissionResponse getInherit(); void setPublic(boolean isPublic); PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest req); PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest req); diff --git a/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegateFactory.java b/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegateFactory.java index 794f61efc..99b53daea 100644 --- a/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegateFactory.java +++ b/core/src/main/java/org/openmbee/mms/core/delegation/PermissionsDelegateFactory.java @@ -1,14 +1,14 @@ package org.openmbee.mms.core.delegation; -import org.openmbee.mms.data.domains.global.Branch; -import org.openmbee.mms.data.domains.global.Organization; -import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; public interface PermissionsDelegateFactory { - PermissionsDelegate getPermissionsDelegate(Project project); + PermissionsDelegate getPermissionsDelegate(ProjectJson project); - PermissionsDelegate getPermissionsDelegate(Organization organization); + PermissionsDelegate getPermissionsDelegate(OrgJson organization); - PermissionsDelegate getPermissionsDelegate(Branch branch); + PermissionsDelegate getPermissionsDelegate(RefJson branch); } diff --git a/core/src/main/java/org/openmbee/mms/core/objects/PermissionResponse.java b/core/src/main/java/org/openmbee/mms/core/objects/PermissionResponse.java index 341e26be7..becf438e8 100644 --- a/core/src/main/java/org/openmbee/mms/core/objects/PermissionResponse.java +++ b/core/src/main/java/org/openmbee/mms/core/objects/PermissionResponse.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class PermissionResponse { @@ -44,6 +45,19 @@ public boolean isInherited() { public void setInherited(boolean inherited) { this.inherited = inherited; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Permission that = (Permission) o; + return inherited == that.inherited && name.equals(that.name) && role.equals(that.role); + } + + @Override + public int hashCode() { + return Objects.hash(name, role, inherited); + } } public List getPermissions() { diff --git a/core/src/main/java/org/openmbee/mms/core/security/MethodSecurityService.java b/core/src/main/java/org/openmbee/mms/core/security/MethodSecurityService.java index 5266f9d58..bbab05f2a 100644 --- a/core/src/main/java/org/openmbee/mms/core/security/MethodSecurityService.java +++ b/core/src/main/java/org/openmbee/mms/core/security/MethodSecurityService.java @@ -4,16 +4,16 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import org.openmbee.mms.core.dao.BranchPersistence; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.dao.BranchDAO; -import org.openmbee.mms.core.dao.OrgDAO; -import org.openmbee.mms.core.dao.ProjectDAO; +import org.openmbee.mms.core.dao.OrgPersistence; import org.openmbee.mms.core.services.PermissionService; import org.openmbee.mms.core.utils.AuthenticationUtils; -import org.openmbee.mms.data.domains.global.Organization; -import org.openmbee.mms.data.domains.global.Project; -import org.openmbee.mms.data.domains.scoped.Branch; +import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; @@ -23,9 +23,9 @@ public class MethodSecurityService { private PermissionService permissionService; - private ProjectDAO projectRepository; - private BranchDAO branchRepository; - private OrgDAO orgRepository; + private ProjectPersistence projectPersistence; + private BranchPersistence branchPersistence; + private OrgPersistence orgPersistence; @Autowired public void setPermissionService(PermissionService permissionService) { @@ -33,21 +33,20 @@ public void setPermissionService(PermissionService permissionService) { } @Autowired - public void setOrgRepository(OrgDAO orgRepository) { - this.orgRepository = orgRepository; + public void setOrgPersistence(OrgPersistence orgPersistence) { + this.orgPersistence = orgPersistence; } @Autowired - public void setProjectRepository(ProjectDAO projectRepository) { - this.projectRepository = projectRepository; + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; } @Autowired - public void setBranchRepository(BranchDAO branchRepository) { - this.branchRepository = branchRepository; + public void setBranchPersistence(BranchPersistence branchPersistence) { + this.branchPersistence = branchPersistence; } - public boolean hasOrgPrivilege(Authentication authentication, String orgId, String privilege, boolean allowAnonIfPublic) { CompletableFuture permissionsFuture = CompletableFuture.supplyAsync(() -> { @@ -106,13 +105,13 @@ public boolean hasBranchPrivilege(Authentication authentication, String projectI } private boolean orgExists(String orgId) { - Optional o = orgRepository.findByOrganizationId(orgId); + Optional o = orgPersistence.findById(orgId); return o.isPresent(); } private boolean projectExists(String projectId) { ContextHolder.getContext().setProjectId(projectId); - Optional p = projectRepository.findByProjectId(projectId); + Optional p = projectPersistence.findById(projectId); return p.isPresent(); } @@ -120,16 +119,16 @@ private boolean branchExists(String projectId, String branchId){ if(! projectExists(projectId)) { return false; } - Optional branchesOption = branchRepository.findByBranchId(branchId); + Optional branchesOption = branchPersistence.findById(projectId, branchId); return branchesOption.isPresent(); } private boolean completeFutures(CompletableFuture permissionsFuture, CompletableFuture existsFuture, String context) { try { - if (!permissionsFuture.join()) { + if (!isBooleanFutureTrue(permissionsFuture)){ return false; } - if (!existsFuture.join()) { + if (!isBooleanFutureTrue(existsFuture)){ throw new NotFoundException(context + " not found"); } return true; @@ -140,4 +139,12 @@ private boolean completeFutures(CompletableFuture permissionsFuture, Co return false; } } + + private boolean isBooleanFutureTrue(CompletableFuture future) { + if(future != null) { + Boolean joinResult = future.join(); + return joinResult != null && joinResult; + } + return false; + } } diff --git a/core/src/main/java/org/openmbee/mms/core/services/BranchService.java b/core/src/main/java/org/openmbee/mms/core/services/BranchService.java index bc61f413d..6348ec6f1 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/BranchService.java +++ b/core/src/main/java/org/openmbee/mms/core/services/BranchService.java @@ -10,7 +10,8 @@ public interface BranchService { RefsResponse getBranch(String projectId, String id); RefJson createBranch(String projectId, RefJson branch); - RefJson createBranchfromCommit(String projectId, RefJson branch, NodeService nodeService); + + RefJson updateBranch(String projectId, RefJson branch); RefsResponse deleteBranch(String projectId, String id); } diff --git a/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java b/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java index 30bdabd5a..c53ad3dab 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java +++ b/core/src/main/java/org/openmbee/mms/core/services/DefaultPermissionService.java @@ -1,27 +1,26 @@ package org.openmbee.mms.core.services; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import org.openmbee.mms.core.builders.PermissionUpdatesResponseBuilder; import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.config.Constants; -import org.openmbee.mms.core.dao.BranchGDAO; -import org.openmbee.mms.core.dao.OrgDAO; -import org.openmbee.mms.core.dao.ProjectDAO; +import org.openmbee.mms.core.dao.BranchPersistence; +import org.openmbee.mms.core.dao.OrgPersistence; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.delegation.PermissionsDelegate; -import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.objects.PermissionResponse; import org.openmbee.mms.core.objects.PermissionUpdateRequest; import org.openmbee.mms.core.objects.PermissionUpdateResponse; import org.openmbee.mms.core.objects.PermissionUpdatesResponse; -import org.openmbee.mms.data.domains.global.Branch; -import org.openmbee.mms.data.domains.global.Organization; -import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.core.utils.PermissionsDelegateUtil; +import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,44 +28,44 @@ @Service("defaultPermissionService") public class DefaultPermissionService implements PermissionService { - private BranchGDAO branchRepo; - private ProjectDAO projectRepo; - private OrgDAO orgRepo; - private List permissionsDelegateFactories; + private static final Logger logger = LoggerFactory.getLogger(DefaultPermissionService.class); + + private BranchPersistence branchPersistence; + private ProjectPersistence projectPersistence; + private OrgPersistence orgPersistence; + private PermissionsDelegateUtil permissionsDelegateUtil; @Autowired - public void setPermissionsDelegateFactories(List permissionsDelegateFactories) { - this.permissionsDelegateFactories = permissionsDelegateFactories; + public void setPermissionsDelegateUtil(PermissionsDelegateUtil permissionsDelegateUtil) { + this.permissionsDelegateUtil = permissionsDelegateUtil; } @Autowired - public void setBranchRepo(BranchGDAO branchRepo) { - this.branchRepo = branchRepo; + public void setBranchPersistence(BranchPersistence branchPersistence) { + this.branchPersistence = branchPersistence; } @Autowired - public void setProjectRepo(ProjectDAO projectRepo) { - this.projectRepo = projectRepo; + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; } @Autowired - public void setOrgRepo(OrgDAO orgRepo) { - this.orgRepo = orgRepo; + public void setOrgPersistence(OrgPersistence orgPersistence) { + this.orgPersistence = orgPersistence; } @Override - @Transactional public void initOrgPerms(String orgId, String creator) { - Organization organization = getOrganization(orgId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(organization); + OrgJson organization = getOrganization(orgId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(organization); permissionsDelegate.initializePermissions(creator); } @Override - @Transactional public void initProjectPerms(String projectId, boolean inherit, String creator) { - Project project = getProject(projectId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + ProjectJson project = getProject(projectId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); permissionsDelegate.initializePermissions(creator, inherit); recalculateInheritedPerms(project); @@ -74,54 +73,58 @@ public void initProjectPerms(String projectId, boolean inherit, String creator) } @Override - @Transactional public void initBranchPerms(String projectId, String branchId, boolean inherit, String creator) { - Branch branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.CREATE); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(branch); + RefJson branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); + if(branch == null) { + logger.error("Error initiating branch permissions " + projectId + " / " + branchId); + throw new InternalErrorException("Could not initiate branch permissions"); + } + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(branch); permissionsDelegate.initializePermissions(creator, inherit); recalculateInheritedPerms(branch); } @Override - @Transactional public PermissionUpdatesResponse updateOrgUserPerms(PermissionUpdateRequest req, String orgId) { - Organization organization = getOrganization(orgId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(organization); + OrgJson organization = getOrganization(orgId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(organization); PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); responseBuilder.getUsers().insert(permissionsDelegate.updateUserPermissions(req)); - for (Project proj: organization.getProjects()) { - responseBuilder.insert(recalculateInheritedPerms(proj)); + Collection projects = projectPersistence.findAllByOrgId(orgId); + + for (ProjectJson project : projects) { + responseBuilder.insert(recalculateInheritedPerms(project)); } return responseBuilder.getPermissionUpdatesReponse(); } @Override - @Transactional public PermissionUpdatesResponse updateOrgGroupPerms(PermissionUpdateRequest req, String orgId) { - Organization organization = getOrganization(orgId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(organization); + OrgJson organization = getOrganization(orgId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(organization); PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); responseBuilder.getGroups().insert(permissionsDelegate.updateGroupPermissions(req)); - for (Project proj : organization.getProjects()) { - responseBuilder.insert(recalculateInheritedPerms(proj)); + Collection projects = projectPersistence.findAllByOrgId(orgId); + for (ProjectJson project : projects) { + responseBuilder.insert(recalculateInheritedPerms(project)); } return responseBuilder.getPermissionUpdatesReponse(); } @Override - @Transactional public PermissionUpdatesResponse updateProjectUserPerms(PermissionUpdateRequest req, String projectId) { - Project project = getProject(projectId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + ProjectJson project = getProject(projectId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); responseBuilder.getUsers().insert(permissionsDelegate.updateUserPermissions(req)); - for (Branch b : project.getBranches()) { + Collection branches = branchPersistence.findAll(projectId); + for (RefJson b : branches) { responseBuilder.insert(recalculateInheritedPerms(b)); } @@ -129,14 +132,14 @@ public PermissionUpdatesResponse updateProjectUserPerms(PermissionUpdateRequest } @Override - @Transactional public PermissionUpdatesResponse updateProjectGroupPerms(PermissionUpdateRequest req, String projectId) { - Project project = getProject(projectId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + ProjectJson project = getProject(projectId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); responseBuilder.getGroups().insert(permissionsDelegate.updateGroupPermissions(req)); - for (Branch b : project.getBranches()) { + Collection branches = branchPersistence.findAll(projectId); + for (RefJson b : branches) { responseBuilder.insert(recalculateInheritedPerms(b)); } @@ -144,28 +147,28 @@ public PermissionUpdatesResponse updateProjectGroupPerms(PermissionUpdateRequest } @Override - @Transactional public PermissionUpdateResponse updateBranchUserPerms(PermissionUpdateRequest req, String projectId, String branchId) { - Branch branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(branch); + RefJson branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); + if(branch == null) { + throw new NotFoundException("Branch not found"); + } + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(branch); return permissionsDelegate.updateUserPermissions(req); } @Override - @Transactional public PermissionUpdateResponse updateBranchGroupPerms(PermissionUpdateRequest req, String projectId, String branchId) { - Branch branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(branch); + RefJson branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(branch); return permissionsDelegate.updateGroupPermissions(req); } @Override - @Transactional public PermissionUpdatesResponse setProjectInherit(boolean isInherit, String projectId) { PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); - responseBuilder.setInherit(true); - Project project = getProject(projectId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + responseBuilder.setInherit(isInherit); + ProjectJson project = getProject(projectId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); if (permissionsDelegate.setInherit(isInherit)) { responseBuilder.insert(recalculateInheritedPerms(project)); } @@ -173,12 +176,11 @@ public PermissionUpdatesResponse setProjectInherit(boolean isInherit, String pro } @Override - @Transactional public PermissionUpdatesResponse setBranchInherit(boolean isInherit, String projectId, String branchId) { PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); - responseBuilder.setInherit(true); - Branch branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(branch); + responseBuilder.setInherit(isInherit); + RefJson branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(branch); if (permissionsDelegate.setInherit(isInherit)) { responseBuilder.insert(recalculateInheritedPerms(branch)); } @@ -186,169 +188,141 @@ public PermissionUpdatesResponse setBranchInherit(boolean isInherit, String proj } @Override - @Transactional public boolean setOrgPublic(boolean isPublic, String orgId) { - Organization organization = getOrganization(orgId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(organization); + OrgJson organization = getOrganization(orgId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(organization); permissionsDelegate.setPublic(isPublic); return true; } @Override - @Transactional public boolean setProjectPublic(boolean isPublic, String projectId) { - Project project = getProject(projectId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + ProjectJson project = getProject(projectId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); permissionsDelegate.setPublic(isPublic); return true; } @Override - @Transactional public boolean hasOrgPrivilege(String privilege, String user, Set groups, String orgId) { if (groups.contains(AuthorizationConstants.MMSADMIN)) return true; - Organization organization = getOrganization(orgId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(organization); + OrgJson organization = getOrganization(orgId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(organization); return permissionsDelegate.hasPermission(user, groups, privilege); } @Override - @Transactional public boolean hasProjectPrivilege(String privilege, String user, Set groups, String projectId) { if (groups.contains(AuthorizationConstants.MMSADMIN)) return true; - Project project = getProject(projectId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + ProjectJson project = getProject(projectId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); return permissionsDelegate.hasPermission(user, groups, privilege); } @Override - @Transactional public boolean hasBranchPrivilege(String privilege, String user, Set groups, String projectId, String branchId) { if (groups.contains(AuthorizationConstants.MMSADMIN)) return true; - Branch branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(branch); + RefJson branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.THROW); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(branch); return permissionsDelegate.hasPermission(user, groups, privilege); } @Override - @Transactional public boolean isProjectInherit(String projectId) { - Optional project = projectRepo.findByProjectId(projectId); - if (!project.isPresent()) { - throw new NotFoundException("project " + projectId + " not found"); - } - return project.get().isInherit(); + return projectPersistence.inheritsPermissions(projectId); } @Override - @Transactional public boolean isBranchInherit(String projectId, String branchId) { - Optional branch = branchRepo.findByProject_ProjectIdAndBranchId(projectId, branchId); - return branch.map(Branch::isInherit).orElse(false); + return branchPersistence.inheritsPermissions(projectId, branchId); } @Override - @Transactional public boolean isOrgPublic(String orgId) { - Optional organization = orgRepo.findByOrganizationId(orgId); - if (!organization.isPresent()) { - throw new NotFoundException("org " + orgId + " not found"); - } - return organization.get().isPublic(); + return orgPersistence.hasPublicPermissions(orgId); } @Override - @Transactional public boolean isProjectPublic(String projectId) { - Optional project = projectRepo.findByProjectId(projectId); - if (!project.isPresent()) { - throw new NotFoundException("project " + projectId + " not found"); - } - return project.get().isPublic(); + return projectPersistence.hasPublicPermissions(projectId); } @Override - @Transactional public PermissionResponse getOrgGroupRoles(String orgId) { - Organization organization = getOrganization(orgId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(organization); + OrgJson organization = getOrganization(orgId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(organization); return permissionsDelegate.getGroupRoles(); } @Override - @Transactional public PermissionResponse getOrgUserRoles(String orgId) { - Organization organization = getOrganization(orgId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(organization); + OrgJson organization = getOrganization(orgId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(organization); return permissionsDelegate.getUserRoles(); } @Override - @Transactional public PermissionResponse getProjectGroupRoles(String projectId) { - Project project = getProject(projectId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + ProjectJson project = getProject(projectId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); return permissionsDelegate.getGroupRoles(); } @Override - @Transactional public PermissionResponse getProjectUserRoles(String projectId) { - Project project = getProject(projectId); - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + ProjectJson project = getProject(projectId); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); return permissionsDelegate.getUserRoles(); } @Override - @Transactional public PermissionResponse getBranchGroupRoles(String projectId, String branchId) { - Branch branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.IGNORE); + RefJson branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.IGNORE); if (branch == null) { return PermissionResponse.getDefaultResponse(); } - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(branch); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(branch); return permissionsDelegate.getGroupRoles(); } @Override - @Transactional public PermissionResponse getBranchUserRoles(String projectId, String branchId) { - Branch branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.IGNORE); + RefJson branch = getBranch(projectId, branchId, BRANCH_NOTFOUND_BEHAVIOR.IGNORE); if (branch == null) { return PermissionResponse.getDefaultResponse(); } - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(branch); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(branch); return permissionsDelegate.getUserRoles(); } - private PermissionUpdatesResponse recalculateInheritedPerms(Project project) { + private PermissionUpdatesResponse recalculateInheritedPerms(ProjectJson project) { - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(project); + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(project); PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); responseBuilder.insert(permissionsDelegate.recalculateInheritedPerms()); - if (project.getBranches() != null) { - for (Branch branch : project.getBranches()) { - responseBuilder.insert(recalculateInheritedPerms(branch)); - } + Collection branches = branchPersistence.findAll(project.getProjectId()); + for (RefJson branch : branches) { + responseBuilder.insert(recalculateInheritedPerms(branch)); } + return responseBuilder.getPermissionUpdatesReponse(); } - private PermissionUpdatesResponse recalculateInheritedPerms(Branch branch) { - PermissionsDelegate permissionsDelegate = getPermissionsDelegate(branch); + private PermissionUpdatesResponse recalculateInheritedPerms(RefJson branch) { + PermissionsDelegate permissionsDelegate = permissionsDelegateUtil.getPermissionsDelegate(branch); return permissionsDelegate.recalculateInheritedPerms(); } - private Organization getOrganization(String orgId) { - Optional org = orgRepo.findByOrganizationId(orgId); + private OrgJson getOrganization(String orgId) { + Optional org = orgPersistence.findById(orgId); if (!org.isPresent()) { throw new NotFoundException("Organization " + orgId + " not found"); @@ -357,10 +331,10 @@ private Organization getOrganization(String orgId) { return org.get(); } - private Project getProject(String projectId) { - Optional proj = projectRepo.findByProjectId(projectId); + private ProjectJson getProject(String projectId) { + Optional proj = projectPersistence.findById(projectId); - if (!proj.isPresent()) { + if (proj.isEmpty()) { throw new NotFoundException("Project " + projectId + " not found"); } return proj.get(); @@ -368,16 +342,19 @@ private Project getProject(String projectId) { private enum BRANCH_NOTFOUND_BEHAVIOR {THROW, CREATE, IGNORE} - private Branch getBranch(String projectId, String branchId, BRANCH_NOTFOUND_BEHAVIOR mode) { - Optional branch = branchRepo.findByProject_ProjectIdAndBranchId(projectId, branchId); - if(!branch.isPresent()) { - switch(mode){ + private RefJson getBranch(String projectId, String branchId, BRANCH_NOTFOUND_BEHAVIOR mode) { + Optional branch = branchPersistence.findById(projectId, branchId); + if (branch.isEmpty()) { + switch (mode) { case THROW: - throw new NotFoundException("Branch " + projectId + " " + branchId + " not found"); + throw new NotFoundException("Branch " + projectId + " " + branchId + " not found"); + /* branch should never be created here case CREATE: - Branch b = new Branch(getProject(projectId), branchId, false); - branchRepo.save(b); - return b; + RefJson b = new RefJson(); + b.setProjectId(projectId); + b.setRefId(branchId); + return branchPersistence.save(b); + */ default: //do nothing break; @@ -385,44 +362,4 @@ private Branch getBranch(String projectId, String branchId, BRANCH_NOTFOUND_BEHA } return branch.orElse(null); } - - private PermissionsDelegate getPermissionsDelegate(final Organization organization) { - Optional permissionsDelegate = permissionsDelegateFactories.stream() - .map(v -> v.getPermissionsDelegate(organization)).filter(Objects::nonNull).findFirst(); - - if (permissionsDelegate.isPresent()) { - return permissionsDelegate.get(); - } - - throw new InternalErrorException( - "No valid permissions scheme found for organization " + organization.getOrganizationId() - + " (" + organization.getOrganizationName() + ")"); - } - - private PermissionsDelegate getPermissionsDelegate(final Project project) { - Optional permissionsDelegate = permissionsDelegateFactories.stream() - .map(v -> v.getPermissionsDelegate(project)).filter(Objects::nonNull).findFirst(); - - if(permissionsDelegate.isPresent()) { - return permissionsDelegate.get(); - } - - throw new InternalErrorException( - "No valid permissions scheme found for project " + project.getProjectId() - + " (" + project.getProjectName() + ")"); - } - - private PermissionsDelegate getPermissionsDelegate(final Branch branch) { - Optional permissionsDelegate = permissionsDelegateFactories.stream() - .map(v -> v.getPermissionsDelegate(branch)).filter(Objects::nonNull).findFirst(); - - if(permissionsDelegate.isPresent()) { - return permissionsDelegate.get(); - } - - throw new InternalErrorException( - "No valid permissions scheme found for branch " + branch.getBranchId() - + " of project " + branch.getProject().getProjectId() - + " (" + branch.getProject().getProjectName() + ")"); - } } diff --git a/core/src/main/java/org/openmbee/mms/core/services/GenericServiceFactory.java b/core/src/main/java/org/openmbee/mms/core/services/GenericServiceFactory.java new file mode 100644 index 000000000..bc7fdf344 --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/services/GenericServiceFactory.java @@ -0,0 +1,61 @@ +package org.openmbee.mms.core.services; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class GenericServiceFactory implements ApplicationContextAware { + + private ApplicationContext context; + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public void setApplicationContext(ApplicationContext context) { + this.context = context; + } + + public T getService(Class clazz) { + try { + Map beans = context.getBeansOfType(clazz); + if(beans.size() > 0) { + return beans.entrySet().iterator().next().getValue(); + } + } catch (BeansException e) { + logger.error("Error getting service for Class : "+ clazz.getName(), e.getMessage()); + } + return null; + } + + public T getServiceForSchema(Class clazz, String schema) { + if (clazz == null && schema == null) { + return null; + } + + //Try to find a matching service + try { + Map beans = context.getBeansOfType(clazz); + List> matches = beans.entrySet().stream() + .filter(v -> v.getKey().startsWith(schema)).collect(Collectors.toList()); + if(matches.size() >= 1) { + return matches.get(0).getValue(); + } + } catch (BeansException e) { + if (clazz != null) { + logger.error("Error getting service for Class : " + clazz.getName()); + } else { + logger.error("Error getting service for Class : class was null"); + } + } + return null; + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/openmbee/mms/core/services/HierarchicalNodeService.java b/core/src/main/java/org/openmbee/mms/core/services/HierarchicalNodeService.java new file mode 100644 index 000000000..c8e50d5bb --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/services/HierarchicalNodeService.java @@ -0,0 +1,11 @@ +package org.openmbee.mms.core.services; + +import org.openmbee.mms.json.MountJson; +import org.springframework.data.util.Pair; + +import java.util.List; + +public interface HierarchicalNodeService extends NodeService { + + MountJson getProjectUsages(String projectId, String refId, String commitId, List> saw, boolean restrictOnPermissions); +} diff --git a/core/src/main/java/org/openmbee/mms/core/services/NodeChangeInfo.java b/core/src/main/java/org/openmbee/mms/core/services/NodeChangeInfo.java index 31a11e217..2c13f013e 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/NodeChangeInfo.java +++ b/core/src/main/java/org/openmbee/mms/core/services/NodeChangeInfo.java @@ -1,76 +1,29 @@ package org.openmbee.mms.core.services; -import java.time.Instant; -import java.util.Map; -import java.util.Set; -import org.openmbee.mms.data.domains.scoped.Node; -import org.openmbee.mms.json.CommitJson; import org.openmbee.mms.json.ElementJson; -public class NodeChangeInfo extends NodeGetInfo { - - Map toSaveNodeMap; - - Map updatedMap; - - Map deletedMap; - - Set oldDocIds; - - CommitJson commitJson; - - Instant now; - - public Instant getNow() { - return now; - } +import java.time.Instant; +import java.util.Map; - public void setNow(Instant now) { - this.now = now; - } +public interface NodeChangeInfo extends NodeGetInfo { - public Map getToSaveNodeMap() { - return toSaveNodeMap; - } + Instant getInstant(); - public NodeChangeInfo setToSaveNodeMap(Map toSaveNodeMap) { - this.toSaveNodeMap = toSaveNodeMap; - return this; - } + void setInstant(Instant instant); - public Map getUpdatedMap() { - return updatedMap; - } + Map getUpdatedMap(); - public NodeChangeInfo setUpdatedMap(Map updatedMap) { - this.updatedMap = updatedMap; - return this; - } + NodeChangeInfo setUpdatedMap(Map updatedMap); - public Map getDeletedMap() { - return deletedMap; - } + Map getDeletedMap(); - public NodeChangeInfo setDeletedMap(Map deletedMap) { - this.deletedMap = deletedMap; - return this; - } + NodeChangeInfo setDeletedMap(Map deletedMap); - public Set getOldDocIds() { - return oldDocIds; - } + boolean getPreserveTimestamps(); - public NodeChangeInfo setOldDocIds(Set oldDocIds) { - this.oldDocIds = oldDocIds; - return this; - } + NodeChangeInfo setPreserveTimestamps(boolean preserveTimestamps); - public CommitJson getCommitJson() { - return commitJson; - } + boolean getOverwrite(); - public NodeChangeInfo setCommitJson(CommitJson commitJson) { - this.commitJson = commitJson; - return this; - } + NodeChangeInfo setOverwrite(boolean overwrite); } diff --git a/core/src/main/java/org/openmbee/mms/core/services/NodeChangeInfoImpl.java b/core/src/main/java/org/openmbee/mms/core/services/NodeChangeInfoImpl.java new file mode 100644 index 000000000..2883701fc --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/services/NodeChangeInfoImpl.java @@ -0,0 +1,78 @@ +package org.openmbee.mms.core.services; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +import org.openmbee.mms.json.ElementJson; + +public class NodeChangeInfoImpl extends NodeGetInfoImpl implements NodeChangeInfo { + + private Map updatedMap; + + private Map deletedMap; + + private Instant instant; + + private boolean overwrite; + + private boolean preserveTimestamps; + + @Override + public Instant getInstant() { + return instant; + } + + @Override + public void setInstant(Instant instant) { + this.instant = instant; + } + + @Override + public Map getUpdatedMap() { + if(this.updatedMap == null){ + this.updatedMap = new HashMap(); + } + return updatedMap; + } + + @Override + public NodeChangeInfo setUpdatedMap(Map updatedMap) { + this.updatedMap = updatedMap; + return this; + } + + @Override + public Map getDeletedMap() { + return deletedMap; + } + + @Override + public NodeChangeInfo setDeletedMap(Map deletedMap) { + this.deletedMap = deletedMap; + return this; + } + + @Override + public boolean getOverwrite() { + return overwrite; + } + + @Override + public NodeChangeInfo setOverwrite(boolean overwrite) { + this.overwrite = overwrite; + return this; + } + + @Override + public boolean getPreserveTimestamps() { + return preserveTimestamps; + } + + @Override + public NodeChangeInfo setPreserveTimestamps(boolean preserveTimestamps) { + this.preserveTimestamps = preserveTimestamps; + return this; + } + +} diff --git a/core/src/main/java/org/openmbee/mms/core/services/NodeGetInfo.java b/core/src/main/java/org/openmbee/mms/core/services/NodeGetInfo.java index a718e7c81..cc4978f00 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/NodeGetInfo.java +++ b/core/src/main/java/org/openmbee/mms/core/services/NodeGetInfo.java @@ -1,100 +1,36 @@ package org.openmbee.mms.core.services; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; import org.openmbee.mms.core.objects.Rejection; -import org.openmbee.mms.data.domains.scoped.Node; +import org.openmbee.mms.json.CommitJson; import org.openmbee.mms.json.ElementJson; -public class NodeGetInfo { - - Set reqIndexIds; - - Map reqElementMap; - - Map existingNodeMap; - - // string is node id - // map of element json objects - Map existingElementMap; - - Map activeElementMap; - - Map rejected; - - String commitId; - - - public Set getReqIndexIds() { - return reqIndexIds; - } - - public NodeGetInfo setReqIndexIds(Set reqIndexIds) { - this.reqIndexIds = reqIndexIds; - return this; - } +import java.util.Map; - public Map getReqElementMap() { - return reqElementMap; - } +public interface NodeGetInfo { - public NodeGetInfo setReqElementMap(Map reqElementMap) { - this.reqElementMap = reqElementMap; - return this; - } + CommitJson getCommitJson(); - public Map getExistingNodeMap() { - return existingNodeMap; - } + NodeGetInfo setCommitJson(CommitJson commitJson); - public NodeGetInfo setExistingNodeMap(Map existingNodeMap) { - this.existingNodeMap = existingNodeMap; - return this; - } + Map getReqElementMap(); - public Map getActiveElementMap() { - return activeElementMap; - } + NodeGetInfo setReqElementMap(Map reqElementMap); - public NodeGetInfo setActiveElementMap( - Map activeElementMap) { - this.activeElementMap = activeElementMap; - return this; - } + Map getActiveElementMap(); - public Map getExistingElementMap() { - return existingElementMap; - } + NodeGetInfo setActiveElementMap(Map activeElementMap); - public NodeGetInfo setExistingElementMap( - Map existingElementMap) { - this.existingElementMap = existingElementMap; - return this; - } + Map getExistingElementMap(); - public Map getRejected() { - return rejected; - } + NodeGetInfo setExistingElementMap(Map existingElementMap); - public NodeGetInfo setRejected(Map rejected) { - this.rejected = rejected; - return this; - } + Map getRejected(); - public void addRejection(String id, Rejection rejection) { - if (this.rejected == null) { - this.rejected = new HashMap<>(); - } - this.rejected.put(id, rejection); - } + NodeGetInfo setRejected(Map rejected); - public void setCommitId(String commitId) { - this.commitId = commitId; - } + void addRejection(String id, Rejection rejection); - public String getCommitId() { - return this.commitId; - } + void setRefId(String refId); + String getRefId(); } diff --git a/core/src/main/java/org/openmbee/mms/core/services/NodeGetInfoImpl.java b/core/src/main/java/org/openmbee/mms/core/services/NodeGetInfoImpl.java new file mode 100644 index 000000000..8e7284793 --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/services/NodeGetInfoImpl.java @@ -0,0 +1,100 @@ +package org.openmbee.mms.core.services; + +import java.util.HashMap; +import java.util.Map; +import org.openmbee.mms.core.objects.Rejection; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.ElementJson; + +public class NodeGetInfoImpl implements NodeGetInfo { + + private Map reqElementMap; + + // string is node id + // map of element json objects + private Map existingElementMap; + + private Map activeElementMap; + + private Map rejected; + + private CommitJson commitJson; + + private String refId; + + @Override + public Map getReqElementMap() { + return reqElementMap; + } + + @Override + public NodeGetInfo setReqElementMap(Map reqElementMap) { + this.reqElementMap = reqElementMap; + return this; + } + + @Override + public Map getActiveElementMap() { + return activeElementMap; + } + + @Override + public NodeGetInfo setActiveElementMap( + Map activeElementMap) { + this.activeElementMap = activeElementMap; + return this; + } + + @Override + public Map getExistingElementMap() { + return existingElementMap; + } + + @Override + public NodeGetInfo setExistingElementMap( + Map existingElementMap) { + this.existingElementMap = existingElementMap; + return this; + } + + @Override + public Map getRejected() { + return rejected; + } + + @Override + public NodeGetInfo setRejected(Map rejected) { + this.rejected = rejected; + return this; + } + + @Override + public void addRejection(String id, Rejection rejection) { + if (this.rejected == null) { + this.rejected = new HashMap<>(); + } + this.rejected.put(id, rejection); + } + + @Override + public CommitJson getCommitJson() { + return commitJson; + } + + @Override + public NodeGetInfo setCommitJson(CommitJson commitJson) { + this.commitJson = commitJson; + return this; + } + + + @Override + public void setRefId(String refId) { + this.refId = refId; + } + + @Override + public String getRefId() { + return this.refId; + } +} diff --git a/core/src/main/java/org/openmbee/mms/core/services/NodeService.java b/core/src/main/java/org/openmbee/mms/core/services/NodeService.java index adaae9665..d87fa3764 100644 --- a/core/src/main/java/org/openmbee/mms/core/services/NodeService.java +++ b/core/src/main/java/org/openmbee/mms/core/services/NodeService.java @@ -7,8 +7,6 @@ import org.openmbee.mms.core.objects.ElementsCommitResponse; import org.openmbee.mms.core.objects.ElementsRequest; import org.openmbee.mms.core.objects.ElementsResponse; -import org.openmbee.mms.data.domains.scoped.Node; -import org.openmbee.mms.json.ElementJson; public interface NodeService { @@ -21,12 +19,6 @@ public interface NodeService { ElementsCommitResponse createOrUpdate(String projectId, String refId, ElementsRequest req, Map params, String user); - void extraProcessPostedElement(ElementJson element, Node node, NodeChangeInfo info); - - void extraProcessDeletedElement(ElementJson element, Node node, NodeChangeInfo info); - - void extraProcessGotElement(ElementJson element, Node node, NodeGetInfo info); - ElementsCommitResponse delete(String projectId, String refId, String id, String user); ElementsCommitResponse delete(String projectId, String refId, ElementsRequest req, String user); diff --git a/core/src/main/java/org/openmbee/mms/core/utils/ElementUtils.java b/core/src/main/java/org/openmbee/mms/core/utils/ElementUtils.java new file mode 100644 index 000000000..39084f70d --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/utils/ElementUtils.java @@ -0,0 +1,7 @@ +package org.openmbee.mms.core.utils; + +import org.openmbee.mms.json.ElementJson; + +public interface ElementUtils { + Enum getNodeType(ElementJson e); +} \ No newline at end of file diff --git a/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java b/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java new file mode 100644 index 000000000..c0378b3e8 --- /dev/null +++ b/core/src/main/java/org/openmbee/mms/core/utils/PermissionsDelegateUtil.java @@ -0,0 +1,70 @@ +package org.openmbee.mms.core.utils; + +import org.openmbee.mms.core.delegation.PermissionsDelegate; +import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.services.DefaultPermissionService; +import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@Component +public class PermissionsDelegateUtil { + private static final Logger logger = LoggerFactory.getLogger(PermissionsDelegateUtil.class); + + private List permissionsDelegateFactories; + + @Autowired + public void setPermissionsDelegateFactories(List permissionsDelegateFactories) { + this.permissionsDelegateFactories = permissionsDelegateFactories; + } + + public PermissionsDelegate getPermissionsDelegate(final OrgJson organization) { + Optional permissionsDelegate = permissionsDelegateFactories.stream() + .map(v -> v.getPermissionsDelegate(organization)).filter(Objects::nonNull).findFirst(); + + if (permissionsDelegate.isPresent()) { + return permissionsDelegate.get(); + } + + throw new InternalErrorException( + "No valid permissions scheme found for organization " + organization.getId() + + " (" + organization.getName() + ")"); + } + + public PermissionsDelegate getPermissionsDelegate(final ProjectJson project) { + Optional permissionsDelegate = permissionsDelegateFactories.stream() + .map(v -> v.getPermissionsDelegate(project)).filter(Objects::nonNull).findFirst(); + + if(permissionsDelegate.isPresent()) { + return permissionsDelegate.get(); + } + + throw new InternalErrorException( + "No valid permissions scheme found for project " + project.getProjectId() + + " (" + project.getName() + ")"); + } + + public PermissionsDelegate getPermissionsDelegate(final RefJson branch) { + Optional permissionsDelegate = permissionsDelegateFactories.stream() + .map(v -> v.getPermissionsDelegate(branch)).filter(Objects::nonNull).findFirst(); + + if(permissionsDelegate.isPresent()) { + return permissionsDelegate.get(); + } + + throw new InternalErrorException( + "No valid permissions scheme found for branch " + + (branch.getRefId() == null ? "?" : branch.getRefId()) + + " of project " + + (branch.getProjectId() == null ? "?" : branch.getProjectId())); + } +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/CrudConstants.java b/crud/src/main/java/org/openmbee/mms/crud/CrudConstants.java new file mode 100644 index 000000000..642c8351a --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/CrudConstants.java @@ -0,0 +1,16 @@ +package org.openmbee.mms.crud; + +public class CrudConstants { + public static final String COMMIT = "Commit"; + public static final String COMMIT_SC = "commit"; + public static final String COMMITID = "commitId"; + public static final String DEFAULT = "default"; + public static final String EMPTY = "Empty"; + public static final String NODE = "Node"; + public static final String OVERWRITE = "overwrite"; + public static final String PRESERVETIMESTAMPS = "preserveTimestamps"; + public static final String PROJECT = "Project"; + public static final String ORG = "Org"; + public static final String CREATED = "created"; + public static final String CREATING = "creating"; +} \ No newline at end of file diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/BaseController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/BaseController.java index e8171935d..bbf0abb53 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/BaseController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/BaseController.java @@ -3,7 +3,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.openmbee.mms.core.dao.ProjectDAO; +import org.openmbee.mms.core.dao.BranchPersistence; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.ConflictException; import org.openmbee.mms.core.exceptions.DeletedException; @@ -18,7 +19,8 @@ import org.openmbee.mms.core.services.NodeService; import org.openmbee.mms.core.services.PermissionService; import org.openmbee.mms.crud.services.ServiceFactory; -import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -35,7 +37,9 @@ public abstract class BaseController { protected ServiceFactory serviceFactory; - protected ProjectDAO projectRepository; + protected ProjectPersistence projectPersistence; + + protected BranchPersistence branchPersistence; protected PermissionService permissionService; @@ -47,8 +51,13 @@ public void setServiceFactory(ServiceFactory serviceFactory) { } @Autowired - public void setProjectRepository(ProjectDAO projectRepository) { - this.projectRepository = projectRepository; + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; + } + + @Autowired + public void setBranchPersistence(BranchPersistence branchPersistence) { + this.branchPersistence = branchPersistence; } @Autowired @@ -70,8 +79,16 @@ public Map convertToMap(Object obj) { return om.convertValue(obj, new TypeReference>() {}); } + protected void findBranch(String projectId, String id){ + getProjectType(projectId); + Optional branchesOption = branchPersistence.findById(projectId, id); + if (!branchesOption.isPresent()) { + throw new NotFoundException("branch not found"); + } + } + protected String getProjectType(String projectId) { - Optional p = projectRepository.findByProjectId(projectId); + Optional p = projectPersistence.findById(projectId); if (p.isPresent()) { return p.get().getProjectType(); } diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java index 598821731..d69dcf0df 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/branches/BranchesController.java @@ -1,6 +1,8 @@ package org.openmbee.mms.crud.controllers.branches; import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.Optional; import java.util.UUID; import org.openmbee.mms.core.config.Privileges; import org.openmbee.mms.core.exceptions.BadRequestException; @@ -15,7 +17,6 @@ import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; @@ -41,6 +42,8 @@ public RefsResponse getAllRefs( @PathVariable String projectId, Authentication auth) { + getProjectType(projectId); + RefsResponse res = branchService.getBranches(projectId); if (!permissionService.isProjectPublic(projectId)) { List filtered = new ArrayList<>(); @@ -70,9 +73,8 @@ public RefsResponse getRef( } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - @Transactional @PreAuthorize("@mss.hasProjectPrivilege(authentication, #projectId, 'PROJECT_CREATE_BRANCH', false)") - public RefsResponse createRefs( + public RefsResponse createRefs( //this can also handle update @PathVariable String projectId, @RequestBody RefsRequest projectsPost, Authentication auth) { @@ -90,15 +92,24 @@ public RefsResponse createRefs( response.addRejection(new Rejection(branch, 400, "Branch id is invalid.")); continue; } - + Optional existingOptional = branchPersistence.findById(projectId, branch.getId()); + if (existingOptional.isPresent()) { + //Branch exists, should merge the json, but cannot change parent ref or commit + //if branch exists it will get resurrected if it was deleted + RefJson existing = existingOptional.get(); + if (branch.getParentRefId() != null && !branch.getParentRefId().equals(existing.getParentRefId()) || + branch.getParentCommitId() != null && !branch.getParentCommitId().equals(existing.getParentCommitId())) { + response.addRejection(new Rejection(branch, 400, "Cannot change existing branch's origin")); + continue; + } + branch.merge(existingOptional.get()); + RefJson res = branchService.updateBranch(projectId, branch); + response.getRefs().add(res); + continue; + } RefJson res; branch.setCreator(auth.getName()); - if (branch.getParentCommitId() == null || branch.getParentCommitId().isEmpty()) { - res = branchService.createBranch(projectId, branch); - } else { - res = branchService.createBranchfromCommit(projectId, branch, getNodeService(projectId)); - } - + res = branchService.createBranch(projectId, branch); permissionService.initBranchPerms(projectId, branch.getId(), true, auth.getName()); response.getRefs().add(res); } catch (MMSException e) { @@ -112,7 +123,6 @@ public RefsResponse createRefs( } @DeleteMapping("/{refId}") - @Transactional @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_DELETE', false)") public RefsResponse deleteRef( @PathVariable String projectId, 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 fef7654c1..2a141bfd5 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 @@ -19,6 +19,7 @@ import org.openmbee.mms.core.objects.ElementsRequest; import org.openmbee.mms.core.objects.ElementsResponse; import org.openmbee.mms.core.services.CommitService; +import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.controllers.BaseController; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.services.NodeService; @@ -75,11 +76,11 @@ public void setCommitService(@Qualifier("defaultCommitService")CommitService com @Content(mediaType = "application/x-ndjson") }) public ResponseEntity getAllElements( - @PathVariable String projectId, - @PathVariable String refId, - @RequestParam(required = false) String commitId, - @RequestParam(required = false) Map params, - @Parameter(hidden = true) @RequestHeader(value = "Accept", defaultValue = "application/json") String accept) { + @PathVariable String projectId, + @PathVariable String refId, + @RequestParam(required = false) String commitId, + @RequestParam(required = false) Map params, + @Parameter(hidden = true) @RequestHeader(value = "Accept", defaultValue = "application/json") String accept) { NodeService nodeService = getNodeService(projectId); if (commitId != null && !commitId.isEmpty()) { @@ -94,11 +95,11 @@ public ResponseEntity getAllElements( @GetMapping(value = "/{elementId}", produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_READ', true)") public ElementsResponse getElement( - @PathVariable String projectId, - @PathVariable String refId, - @PathVariable String elementId, - @RequestParam(required = false) String commitId, - @RequestParam(required = false) Map params) { + @PathVariable String projectId, + @PathVariable String refId, + @PathVariable String elementId, + @RequestParam(required = false) String commitId, + @RequestParam(required = false) Map params) { NodeService nodeService = getNodeService(projectId); ElementsResponse res = nodeService.read(projectId, refId, elementId, params); @@ -109,12 +110,12 @@ public ElementsResponse getElement( @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_EDIT_CONTENT', false)") public ElementsCommitResponse createOrUpdateElements( - @PathVariable String projectId, - @PathVariable String refId, - @RequestBody ElementsRequest req, - @RequestParam(required = false) String overwrite, - @RequestParam(required = false) Map params, - Authentication auth) { + @PathVariable String projectId, + @PathVariable String refId, + @RequestBody ElementsRequest req, + @RequestParam(required = false) String overwrite, + @RequestParam(required = false) Map params, + Authentication auth) { embeddedHookService.hook(new ElementUpdateHook(ElementUpdateHook.Action.ADD_UPDATE, projectId, refId, req.getElements(), params, auth)); @@ -124,19 +125,19 @@ public ElementsCommitResponse createOrUpdateElements( NodeService nodeService = getNodeService(projectId); return nodeService.createOrUpdate(projectId, refId, req, params, auth.getName()); } - throw new BadRequestException(response.addMessage("Empty")); + throw new BadRequestException(response.addMessage(CrudConstants.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) { + @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); @@ -179,27 +180,27 @@ public ResponseEntity createOrUpdateElementsStream( @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_READ', true)") public ElementsResponse getElements( - @PathVariable String projectId, - @PathVariable String refId, - @RequestBody ElementsRequest req, - @RequestParam(required = false) String commitId, - @RequestParam(required = false) Map params) { + @PathVariable String projectId, + @PathVariable String refId, + @RequestBody ElementsRequest req, + @RequestParam(required = false) String commitId, + @RequestParam(required = false) Map params) { ElementsResponse response = new ElementsResponse(); if (!req.getElements().isEmpty()) { NodeService nodeService = getNodeService(projectId); return nodeService.read(projectId, refId, req, params); } - throw new BadRequestException(response.addMessage("Empty")); + throw new BadRequestException(response.addMessage(CrudConstants.EMPTY)); } @DeleteMapping(value = "/{elementId}") @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_EDIT_CONTENT', false)") public ElementsCommitResponse deleteElement( - @PathVariable String projectId, - @PathVariable String refId, - @PathVariable String elementId, - Authentication auth) { + @PathVariable String projectId, + @PathVariable String refId, + @PathVariable String elementId, + Authentication auth) { ElementsCommitResponse res = getNodeService(projectId).delete(projectId, refId, elementId, auth.getName()); handleSingleResponse(res); @@ -209,10 +210,10 @@ public ElementsCommitResponse deleteElement( @DeleteMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_EDIT_CONTENT', false)") public ElementsResponse deleteElements( - @PathVariable String projectId, - @PathVariable String refId, - @RequestBody ElementsRequest req, - Authentication auth) { + @PathVariable String projectId, + @PathVariable String refId, + @RequestBody ElementsRequest req, + Authentication auth) { return getNodeService(projectId).delete(projectId, refId, req, auth.getName()); } diff --git a/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java b/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java index c6b4c9d0f..05b408a6c 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java +++ b/crud/src/main/java/org/openmbee/mms/crud/controllers/orgs/OrgsController.java @@ -1,15 +1,20 @@ package org.openmbee.mms.crud.controllers.orgs; import io.swagger.v3.oas.annotations.tags.Tag; + +import java.time.Instant; +import java.util.Collection; import java.util.List; import java.util.UUID; import java.util.Optional; + +import org.openmbee.mms.core.config.Formats; import org.openmbee.mms.core.config.Privileges; -import org.openmbee.mms.core.dao.OrgDAO; +import org.openmbee.mms.core.dao.OrgPersistence; import org.openmbee.mms.core.objects.OrganizationsRequest; import org.openmbee.mms.core.objects.OrganizationsResponse; import org.openmbee.mms.core.objects.Rejection; -import org.openmbee.mms.data.domains.global.Organization; +import org.openmbee.mms.crud.CrudConstants; import org.openmbee.mms.crud.controllers.BaseController; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.NotFoundException; @@ -18,7 +23,6 @@ import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -32,48 +36,41 @@ @Tag(name = "Orgs") public class OrgsController extends BaseController { - OrgDAO organizationRepository; + OrgPersistence organizationRepository; @Autowired - public OrgsController(OrgDAO organizationRepository) { + public OrgsController(OrgPersistence organizationRepository) { this.organizationRepository = organizationRepository; } @GetMapping - @Transactional public OrganizationsResponse getAllOrgs( Authentication auth) { OrganizationsResponse response = new OrganizationsResponse(); - List allOrgs = organizationRepository.findAll(); - for (Organization org : allOrgs) { - if (mss.hasOrgPrivilege(auth, org.getOrganizationId(), Privileges.ORG_READ.name(), true)) { - OrgJson orgJson = new OrgJson(); - orgJson.merge(convertToMap(org)); - response.getOrgs().add(orgJson); + Collection allOrgs = organizationRepository.findAll(); + for (OrgJson org : allOrgs) { + if (mss.hasOrgPrivilege(auth, org.getId(), Privileges.ORG_READ.name(), true)) { + response.getOrgs().add(org); } } return response; } @GetMapping(value = "/{orgId}") - @Transactional @PreAuthorize("@mss.hasOrgPrivilege(authentication, #orgId, 'ORG_READ', true)") public OrganizationsResponse getOrg( @PathVariable String orgId) { OrganizationsResponse response = new OrganizationsResponse(); - Optional orgOption = organizationRepository.findByOrganizationId(orgId); + Optional orgOption = organizationRepository.findById(orgId); if (!orgOption.isPresent()) { throw new NotFoundException(response.addMessage("Organization not found.")); } - OrgJson orgJson = new OrgJson(); - orgJson.merge(convertToMap(orgOption.get())); - response.getOrgs().add(orgJson); + response.getOrgs().add(orgOption.get()); return response; } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - @Transactional @PreAuthorize("isAuthenticated()") public OrganizationsResponse createOrUpdateOrgs( @RequestBody OrganizationsRequest orgPost, @@ -89,25 +86,32 @@ public OrganizationsResponse createOrUpdateOrgs( org.setId(UUID.randomUUID().toString()); } - Organization o = organizationRepository.findByOrganizationId(org.getId()) - .orElse(new Organization()); + OrgJson o = organizationRepository.findById(org.getId()).orElse(new OrgJson()); boolean newOrg = true; if (o.getId() != null) { - if (!mss.hasOrgPrivilege(auth, o.getOrganizationId(), Privileges.ORG_EDIT.name(), false)) { + if (!mss.hasOrgPrivilege(auth, o.getId(), Privileges.ORG_EDIT.name(), false)) { response.addRejection(new Rejection(org, 403, "No permission to update org")); continue; } newOrg = false; + org.merge(o); + } else { + if (org.getCreated() == null || org.getCreated().isEmpty()) { + org.setCreated(Formats.FORMATTER.format(Instant.now())); + } + if (org.getType() == null || org.getType().isEmpty()) { + org.setType(CrudConstants.ORG); + } + if (org.getCreator() == null || org.getCreator().isEmpty()) { + org.setCreator(auth.getName()); + } } - o.setOrganizationId(org.getId()); - o.setOrganizationName(org.getName()); - logger.info("Saving organization: {}", o.getOrganizationId()); - Organization saved = organizationRepository.save(o); + logger.info("Saving organization: {}", org.getId()); + OrgJson saved = organizationRepository.save(org); if (newOrg) { permissionService.initOrgPerms(org.getId(), auth.getName()); } - org.merge(convertToMap(saved)); - response.getOrgs().add(org); + response.getOrgs().add(saved); } if (orgPost.getOrgs().size() == 1) { handleSingleResponse(response); @@ -116,21 +120,19 @@ public OrganizationsResponse createOrUpdateOrgs( } @DeleteMapping(value = "/{orgId}") - @Transactional @PreAuthorize("@mss.hasOrgPrivilege(authentication, #orgId, 'ORG_DELETE', false)") - public OrganizationsResponse deleteOrg( - @PathVariable String orgId) { + public OrganizationsResponse deleteOrg(@PathVariable String orgId) { OrganizationsResponse response = new OrganizationsResponse(); - Optional orgOption = organizationRepository.findByOrganizationId(orgId); + Optional orgOption = organizationRepository.findById(orgId); if (!orgOption.isPresent()) { throw new NotFoundException(response.addMessage("Organization not found.")); } - Organization org = orgOption.get(); - if (!org.getProjects().isEmpty()) { + if (!projectPersistence.findAllByOrgId(orgId).isEmpty()) { throw new BadRequestException(response.addMessage("Organization is not empty")); } - organizationRepository.delete(org); + OrgJson deleted = organizationRepository.deleteById(orgId); + response.setOrgs(List.of(deleted)); return response; } } 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 8dc62b6a9..7e9a2ab92 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,42 +1,28 @@ package org.openmbee.mms.crud.controllers.projects; import io.swagger.v3.oas.annotations.tags.Tag; - -import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.config.Privileges; -import org.openmbee.mms.core.config.ProjectSchemas; -import org.openmbee.mms.core.dao.ProjectDAO; -import org.openmbee.mms.core.dao.ProjectIndex; +import org.openmbee.mms.core.config.*; +import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.core.exceptions.DeletedException; import org.openmbee.mms.core.exceptions.MMSException; +import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.objects.ProjectsRequest; import org.openmbee.mms.core.objects.ProjectsResponse; -import org.openmbee.mms.core.exceptions.DeletedException; import org.openmbee.mms.core.objects.Rejection; -import org.openmbee.mms.data.domains.global.Project; -import org.openmbee.mms.crud.controllers.BaseController; -import org.openmbee.mms.core.exceptions.BadRequestException; -import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.services.ProjectService; +import org.openmbee.mms.crud.CrudConstants; +import org.openmbee.mms.crud.controllers.BaseController; +import org.openmbee.mms.crud.services.ProjectDeleteService; import org.openmbee.mms.json.ProjectJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -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; +import org.springframework.web.bind.annotation.*; + +import java.time.Instant; +import java.util.*; + @RestController @RequestMapping("/projects") @@ -45,32 +31,34 @@ public class ProjectsController extends BaseController { private static final String PROJECT_ID_VALID_PATTERN = "^[\\w-]+$"; - ProjectDAO projectRepository; - ProjectIndex projectIndex; - ProjectSchemas schemas; + private ProjectDeleteService projectDeleteService; + private ProjectSchemas projectSchemas; + @Autowired + public void setProjectDeleteService(ProjectDeleteService projectDeleteService) { + this.projectDeleteService = projectDeleteService; + } @Autowired - public ProjectsController(ProjectDAO projectRepository, ProjectIndex projectIndex, ProjectSchemas schemas) { - this.projectRepository = projectRepository; - this.projectIndex = projectIndex; - this.schemas = schemas; + public void setProjectSchemas(ProjectSchemas projectSchemas) { + this.projectSchemas = projectSchemas; } @GetMapping - public ProjectsResponse getAllProjects(Authentication auth, - @RequestParam(required = false) String orgId) { + public ProjectsResponse getAllProjects(Authentication auth, @RequestParam(required = false) String orgId) { + ProjectsResponse response = new ProjectsResponse(); - 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()); - if(proj.getDocId() != null && !proj.isDeleted()) { - Optional projectJsonOption = projectIndex.findById(proj.getDocId()); - projectJsonOption.ifPresentOrElse(json -> response.getProjects().add(json), ()-> { - logger.error("Project json not found for id: {}", proj.getProjectId()); - }); + Collection allProjects = + orgId == null ? projectPersistence.findAll() : projectPersistence.findAllByOrgId(orgId); + for (ProjectJson projectJson : allProjects) { + try { + if (mss.hasProjectPrivilege(auth, projectJson.getProjectId(), Privileges.PROJECT_READ.name(), true) + && projectJson.getDocId() != null + && !Constants.TRUE.equals(projectJson.getIsDeleted())) { + response.getProjects().add(projectJson); } + } catch(NotFoundException ex) { + logger.error("Project {} was not found when getting all projects.", projectJson.getProjectId()); } } return response; @@ -83,22 +71,18 @@ public ProjectsResponse getProject( ContextHolder.setContext(projectId); ProjectsResponse response = new ProjectsResponse(); - Optional projectOption = projectRepository.findByProjectId(projectId); - if (!projectOption.isPresent()) { + Optional projectOption = projectPersistence.findById(projectId); + if (projectOption.isEmpty()) { throw new NotFoundException(response.addMessage("Project not found")); } - Optional projectJsonOption = projectIndex.findById(projectOption.get().getDocId()); - projectJsonOption.ifPresentOrElse(json -> response.getProjects().add(json), ()-> { - throw new NotFoundException(response.addMessage("Project JSON not found")); - }); - if (projectOption.get().isDeleted()) { + response.getProjects().add(projectOption.get()); + if (Constants.TRUE.equals(projectOption.get().getIsDeleted())) { throw new DeletedException(response); } return response; } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - @Transactional @PreAuthorize("isAuthenticated()") public ProjectsResponse createOrUpdateProjects( @RequestBody ProjectsRequest projectsPost, @@ -113,59 +97,81 @@ public ProjectsResponse createOrUpdateProjects( try { if (json.getProjectId() == null || json.getProjectId().isEmpty()) { json.setId(UUID.randomUUID().toString()); - } - if (!isProjectIdValid(json.getProjectId())) { + } else if (!isProjectIdValid(json.getProjectId())) { response.addRejection(new Rejection(json, 400, "Project id is invalid.")); continue; } - if ((json.getProjectType() != null) && (!((schemas.getSchemas()) - .containsKey(json.getProjectType())))) { - response.addRejection(new Rejection(json, 400, "Project schema is unknown.")); - continue; + + Optional existingOptional = projectPersistence.findById(json.getProjectId()); + if (existingOptional.isPresent()) { + //Project exists, should merge the json, but not if schema is different + if (json.getProjectType() != null && !json.getProjectType().equals(existingOptional.get().getProjectType())) { + response.addRejection(new Rejection(json, 400, "Cannot change existing project schema")); + continue; + } + json.merge(existingOptional.get()); + } else { + //New Project + if (json.getCreated() == null || json.getCreated().isEmpty()) { + json.setCreated(Formats.FORMATTER.format(Instant.now())); + } + if (json.getType() == null || json.getType().isEmpty()) { + json.setType(CrudConstants.PROJECT); + } + if ((json.getProjectType() != null) && (!((projectSchemas.getSchemas()) + .containsKey(json.getProjectType())))) { + response.addRejection(new Rejection(json, 400, "Project schema is unknown.")); + continue; + } + //This needs to be used for create and update + if (json.getCreator() == null || json.getCreator().isEmpty()) { + json.setCreator(auth.getName()); + } } + ProjectService ps = getProjectService(json); - if (!ps.exists(json.getProjectId())) { + String projectId = json.getProjectId(); + if (!ps.exists(projectId)) { if (!mss.hasOrgPrivilege(auth, json.getOrgId(), Privileges.ORG_CREATE_PROJECT.name(), false)) { response.addRejection(new Rejection(json, 403, "No permission to create project under org")); continue; } - if (json.getCreator() == null || json.getCreator().isEmpty()) { - json.setCreator(auth.getName()); - } response.getProjects().add(ps.create(json)); - permissionService.initProjectPerms(json.getProjectId(), true, auth.getName()); + permissionService.initProjectPerms(projectId, true, auth.getName()); } else { if (!mss.hasProjectPrivilege(auth, json.getProjectId(), Privileges.PROJECT_EDIT.name(), false)) { response.addRejection(new Rejection(json, 403, "No permission to change project")); continue; } - boolean updateInheritedPerms = false; + boolean updatePermissions = false; if (json.getOrgId() != null && !json.getOrgId().isEmpty()) { - Project proj = projectRepository.findByProjectId(json.getProjectId()).get(); - String existingOrg = proj.getOrgId(); - if (!json.getOrgId().equals(existingOrg)) { - if (!mss.hasProjectPrivilege(auth, json.getProjectId(), Privileges.PROJECT_DELETE.name(), false) || - !mss.hasOrgPrivilege(auth, json.getOrgId(), Privileges.ORG_CREATE_PROJECT.name(), false)) { - response.addRejection( - new Rejection(json, 403, "No permission to move project org")); - continue; - } - if (proj.isInherit()) { - updateInheritedPerms = true; + Optional projectJsonOption = projectPersistence.findById(json.getProjectId()); + if (projectJsonOption.isPresent()) { + ProjectJson projectJson = projectJsonOption.get(); + String existingOrg = projectJson.getOrgId(); + if (!json.getOrgId().equals(existingOrg)) { + if (!mss.hasProjectPrivilege(auth, json.getProjectId(), Privileges.PROJECT_DELETE.name(), false) || + !mss.hasOrgPrivilege(auth, json.getOrgId(), Privileges.ORG_CREATE_PROJECT.name(), false)) { + response.addRejection( + new Rejection(json, 403, "No permission to move project org")); + continue; + } + if (projectPersistence.inheritsPermissions(projectJson.getProjectId())) { + updatePermissions = true; + } } } } response.getProjects().add(ps.update(json)); - if (updateInheritedPerms) { + if (updatePermissions) { permissionService.setProjectInherit(false, json.getProjectId()); permissionService.setProjectInherit(true, json.getProjectId()); } } } catch (MMSException ex) { response.addRejection(new Rejection(json, ex.getCode().value(), ex.getMessageObject().toString())); - continue; } } if (projectsPost.getProjects().size() == 1) { @@ -176,31 +182,15 @@ public ProjectsResponse createOrUpdateProjects( @DeleteMapping(value = "/{projectId}") - @Transactional @PreAuthorize("@mss.hasProjectPrivilege(authentication, #projectId, 'PROJECT_DELETE', false)") public ProjectsResponse deleteProject( @PathVariable String projectId, - @RequestParam(required = false, defaultValue = "false") boolean hard) { + @RequestParam(required = false, defaultValue = Constants.FALSE) boolean hard) { - ProjectsResponse response = new ProjectsResponse(); - Optional projectOption = projectRepository.findByProjectId(projectId); - if (!projectOption.isPresent()) { - throw new NotFoundException(response.addMessage("Project not found")); - } - Project project = projectOption.get(); - project.setDeleted(true); - ProjectJson projectJson = new ProjectJson(); - projectJson.merge(convertToMap(project)); - List res = new ArrayList<>(); - res.add(projectJson); - if (hard) { - projectRepository.delete(project); - projectIndex.delete(projectId); - } else { - projectRepository.save(project); - // TODO soft delete for index? + if(!isProjectIdValid(projectId)) { + throw new BadRequestException("Invalid project id"); } - return response.setProjects(res); + return projectDeleteService.deleteProject(projectId, hard); } private ProjectService getProjectService(ProjectJson json) { @@ -209,7 +199,7 @@ private ProjectService getProjectService(ProjectJson json) { try { type = this.getProjectType(json.getProjectId()); } catch (NotFoundException e) { - type = "default"; + type = CrudConstants.DEFAULT; } json.setProjectType(type); } diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/CommitDomain.java b/crud/src/main/java/org/openmbee/mms/crud/domain/CommitDomain.java new file mode 100644 index 000000000..8ad7e9b56 --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/CommitDomain.java @@ -0,0 +1,22 @@ +package org.openmbee.mms.crud.domain; + +import org.openmbee.mms.core.config.Formats; +import org.openmbee.mms.crud.CrudConstants; +import org.openmbee.mms.json.CommitJson; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.ArrayList; + +@Component +public class CommitDomain { + + public void initCommitJson(CommitJson cmjs, Instant now) { + cmjs.setCreated(Formats.FORMATTER.format(now)); + cmjs.setAdded(new ArrayList<>()); + cmjs.setDeleted(new ArrayList<>()); + cmjs.setUpdated(new ArrayList<>()); + cmjs.setType(CrudConstants.COMMIT); + } + +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java b/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java new file mode 100644 index 000000000..7174db827 --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/DefaultNodeUpdateFilter.java @@ -0,0 +1,68 @@ +package org.openmbee.mms.crud.domain; + +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.config.Formats; +import org.openmbee.mms.core.objects.Rejection; +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.json.BaseJson; +import org.openmbee.mms.json.ElementJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + +@Component +public class DefaultNodeUpdateFilter implements NodeUpdateFilter { + private static final Logger logger = LoggerFactory.getLogger(DefaultNodeUpdateFilter.class); + + @Override + public boolean filterUpdate(NodeChangeInfo info, ElementJson updated, ElementJson existing) { + if (!info.getOverwrite()) { + if (Constants.TRUE.equals(existing.getIsDeleted()) || isUpdated(updated, existing, info)) { + return diffUpdateJson(updated, existing, info); + } else { + return false; + } + } else { + updated.setCreator(existing.getCreator()); + updated.setCreated(existing.getCreated()); + } + return true; + } + + protected boolean isUpdated(BaseJson element, Map existing, NodeChangeInfo info) { + + if (element.isPartialOf(existing)) { + info.addRejection(element.getId(), new Rejection(element, 304, "Is Equivalent")); + return false; + } + return true; + } + + protected boolean diffUpdateJson(BaseJson element, Map existing, NodeChangeInfo info) { + + String jsonModified = element.getModified(); + Object existingModified = existing.get(BaseJson.MODIFIED); + if (jsonModified != null && !jsonModified.isEmpty()) { + try { + SimpleDateFormat dateFormat = new SimpleDateFormat(Formats.DATE_FORMAT); + Date jsonModDate = dateFormat.parse(jsonModified); + Date existingModDate = dateFormat.parse(existingModified.toString()); + if (jsonModDate.before(existingModDate)) { + info.addRejection(element.getId(), new Rejection(element, 409, "Conflict Detected")); + return false; + } + } catch (ParseException e) { + logger.info("date parse exception: {} {}", jsonModified, existingModified); + } + } + element.merge(existing); + element.remove(ElementJson.IS_DELETED); + return true; + } + +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/ElementDomain.java b/crud/src/main/java/org/openmbee/mms/crud/domain/ElementDomain.java new file mode 100644 index 000000000..766ce627c --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/ElementDomain.java @@ -0,0 +1,12 @@ +package org.openmbee.mms.crud.domain; + +import org.openmbee.mms.core.utils.ElementUtils; +import org.openmbee.mms.json.ElementJson; + +public interface ElementDomain { + + public abstract ElementUtils getElementUtils(String projectId); + + public abstract Integer getNodeType(String projectId, ElementJson element); + +} \ No newline at end of file diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/JsonDomain.java b/crud/src/main/java/org/openmbee/mms/crud/domain/JsonDomain.java new file mode 100644 index 000000000..d5d9f51b4 --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/JsonDomain.java @@ -0,0 +1,36 @@ +package org.openmbee.mms.crud.domain; + +import org.openmbee.mms.json.ElementJson; + +import java.util.*; + +public class JsonDomain { + + public static List filter(List ids, List orig) { + Map map = convertJsonToMap(orig); + List ret = new ArrayList<>(); + for (String id: ids) { + if (map.containsKey(id)) { + ret.add(map.get(id)); + } + } + return ret; + } + public static Map convertJsonToMap(Collection elements) { + if (elements == null || elements.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + + for (ElementJson elem : elements) { + if (elem == null) { + continue; + } + if (elem.getId() != null && !elem.getId().isEmpty()) { + result.put(elem.getId(), elem); + } + } + return result; + } + +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/NodeChangeDomain.java b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeChangeDomain.java new file mode 100644 index 000000000..f67329e26 --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeChangeDomain.java @@ -0,0 +1,114 @@ +package org.openmbee.mms.crud.domain; + +import org.openmbee.mms.core.config.Formats; +import org.openmbee.mms.core.objects.Rejection; +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.core.services.NodeChangeInfoImpl; +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.json.BaseJson; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.ElementJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Node; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.*; + +public abstract class NodeChangeDomain extends JsonDomain { + private static final Logger logger = LoggerFactory.getLogger(NodeChangeDomain.class); + + private NodeGetDomain nodeGetDomain; + private CommitDomain commitDomain; + + private List nodeUpdateFilters; + + public NodeChangeDomain(NodeGetDomain nodeGetDomain, CommitDomain commitDomain, List nodeUpdateFilters) { + this.nodeGetDomain = nodeGetDomain; + this.commitDomain = commitDomain; + this.nodeUpdateFilters = nodeUpdateFilters; + } + + public NodeChangeInfo initInfo(CommitJson commitJson, boolean overwrite, boolean preserveTimestamps) { + + NodeChangeInfo info = (NodeChangeInfo) nodeGetDomain.initInfo(commitJson, this::createNodeChangeInfo); + + Instant now = Instant.now(); + if (commitJson != null) { + commitDomain.initCommitJson(commitJson, now); + } + + info.setUpdatedMap(new HashMap<>()); + info.setDeletedMap(new HashMap<>()); + info.setInstant(now); + info.setOverwrite(overwrite); + info.setPreserveTimestamps(preserveTimestamps); + + return info; + } + + + public void processElementAdded(NodeChangeInfo info, ElementJson element) { + CommitJson commitJson = info.getCommitJson(); + processElementAddedOrUpdated(info, element); + + element.setCreator(commitJson.getCreator()); //Only set on creation of new element + element.setCreated(commitJson.getCreated()); + } + + public boolean processElementUpdated(NodeChangeInfo info, ElementJson element, ElementJson existing) { + if (nodeUpdateFilters.stream().anyMatch(f -> !f.filterUpdate(info, element, existing))) { + return false; + } + processElementAddedOrUpdated(info, element); + return true; + } + + protected void processElementAddedOrUpdated(NodeChangeInfo info, ElementJson element) { + CommitJson commitJson = info.getCommitJson(); + element.setProjectId(commitJson.getProjectId()); + element.setRefId(commitJson.getRefId()); + List inRefIds = new ArrayList<>(); + inRefIds.add(commitJson.getRefId()); + element.setInRefIds(inRefIds); + element.setCommitId(commitJson.getId()); + + if(!info.getPreserveTimestamps()) { + element.setModified(commitJson.getCreated()); + element.setModifier(commitJson.getCreator()); + } + + info.getUpdatedMap().put(element.getId(), element); + } + + public void processElementDeleted(NodeChangeInfo info, ElementJson element) { + } + + public NodeChangeInfo processDeleteJson(NodeChangeInfo info, Collection elements) { + for (ElementJson element : elements) { + ElementJson request = info.getReqElementMap().get(element.getId()); + request.putAll(element); + processElementDeleted(info, request); + info.getDeletedMap().put(element.getId(), request); + } + return info; + } + + protected NodeChangeInfo createNodeChangeInfo() { + return new NodeChangeInfoImpl(); + } + + protected void rejectNotFound(NodeGetInfo info, String elementId) { + info.addRejection(elementId, new Rejection(elementId, 404, "Not Found")); + } + + + // create new elastic id for all element json, update modified time, modifier (use dummy for now), set _projectId, _refId, _inRefIds + public abstract NodeChangeInfo processPostJson(NodeChangeInfo nodeChangeInfo, Collection elements); + + + + public abstract void primeNodeChangeInfo(NodeChangeInfo nodeChangeInfo, Collection transactedElements); +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/NodeGetDomain.java b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeGetDomain.java new file mode 100644 index 000000000..8e338732d --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeGetDomain.java @@ -0,0 +1,54 @@ +package org.openmbee.mms.crud.domain; + +import org.openmbee.mms.core.objects.Rejection; +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.core.services.NodeGetInfoImpl; +import org.openmbee.mms.core.services.NodeService; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.ElementJson; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +public class NodeGetDomain extends JsonDomain { + + public NodeGetInfo initInfo(CommitJson commitJson) { + return initInfo(commitJson, this::createNodeGetInfo); + } + + public NodeGetInfo initInfo(CommitJson commitJson, NodeGetInfoFactory nodeGetInfoFactory) { + Map existingElementMap = new HashMap<>(); + Map elementMapForRequests = new HashMap<>(); + + NodeGetInfo info = nodeGetInfoFactory.get(); + + info.setCommitJson(commitJson); + info.setExistingElementMap(existingElementMap); + info.setReqElementMap(elementMapForRequests); + info.setRejected(new HashMap<>()); + info.setActiveElementMap(new HashMap<>()); + + return info; + } + + protected void rejectNotFound(NodeGetInfo info, String elementId) { + info.addRejection(elementId, new Rejection(elementId, 404, "Not Found")); + } + + protected void rejectDeleted(NodeGetInfo info, String nodeId, ElementJson indexElement) { + info.addRejection(nodeId, new Rejection(indexElement, 410, "Element deleted")); + } + + public NodeGetInfo createNodeGetInfo() { + return new NodeGetInfoImpl(); + } + + public void addExistingElements(NodeGetInfo info, List elements) { + elements.forEach(e -> { + info.getExistingElementMap().put(e.getId(), e); + }); + } +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/NodeGetInfoFactory.java b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeGetInfoFactory.java new file mode 100644 index 000000000..c764abdc5 --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeGetInfoFactory.java @@ -0,0 +1,7 @@ +package org.openmbee.mms.crud.domain; + +import org.openmbee.mms.core.services.NodeGetInfo; + +public interface NodeGetInfoFactory { + NodeGetInfo get(); +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/domain/NodeUpdateFilter.java b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeUpdateFilter.java new file mode 100644 index 000000000..0a7095c35 --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/domain/NodeUpdateFilter.java @@ -0,0 +1,8 @@ +package org.openmbee.mms.crud.domain; + +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.json.ElementJson; + +public interface NodeUpdateFilter { + boolean filterUpdate(NodeChangeInfo nodeChangeInfo, ElementJson updated, ElementJson existing); +} 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 0945aa916..75471913c 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 @@ -3,7 +3,6 @@ import java.util.Collection; import org.openmbee.mms.core.config.Constants; -import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.core.config.Formats; import org.openmbee.mms.core.dao.*; import org.openmbee.mms.core.exceptions.BadRequestException; @@ -14,11 +13,8 @@ import org.openmbee.mms.core.objects.RefsResponse; import org.openmbee.mms.core.services.BranchService; import org.openmbee.mms.core.services.EventService; -import org.openmbee.mms.core.services.NodeService; -import org.openmbee.mms.data.domains.scoped.Branch; -import org.openmbee.mms.data.domains.scoped.Commit; -import org.openmbee.mms.data.domains.scoped.Node; -import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.crud.CrudConstants; +import org.openmbee.mms.json.CommitJson; import org.openmbee.mms.json.RefJson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,50 +28,27 @@ public class DefaultBranchService implements BranchService { protected final Logger logger = LoggerFactory.getLogger(getClass()); - private BranchDAO branchRepository; + private BranchPersistence branchPersistence; - private BranchIndexDAO branchIndex; + private CommitPersistence commitPersistence; - private CommitDAO commitRepository; - - private NodeDAO nodeRepository; - - private NodeIndexDAO nodeIndex; + private NodePersistence nodePersistence; protected Collection eventPublisher; - protected NodeGetHelper nodeGetHelper; - - - - @Autowired - public void setNodeGetHelper(NodeGetHelper nodeGetHelper) { - this.nodeGetHelper = nodeGetHelper; - } - - - @Autowired - public void setBranchRepository(BranchDAO branchRepository) { - this.branchRepository = branchRepository; - } @Autowired - public void setCommitRepository(CommitDAO commitRepository) { - this.commitRepository = commitRepository; + public void setBranchPersistence(BranchPersistence branchPersistence) { + this.branchPersistence = branchPersistence; } @Autowired - public void setNodeRepository(NodeDAO nodeRepository) { - this.nodeRepository = nodeRepository; + public void setCommitPersistence(CommitPersistence commitPersistence) { + this.commitPersistence = commitPersistence; } @Autowired - public void setNodeIndex(NodeIndexDAO nodeIndex) { - this.nodeIndex = nodeIndex; - } - - @Autowired - public void setBranchIndex(BranchIndexDAO branchIndex) { - this.branchIndex = branchIndex; + public void setNodePersistence(NodePersistence nodePersistence) { + this.nodePersistence = nodePersistence; } @Autowired @@ -84,194 +57,113 @@ public void setEventPublisher(Collection eventPublisher) { } public RefsResponse getBranches(String projectId) { - ContextHolder.setContext(projectId); RefsResponse branchesResponse = new RefsResponse(); - List branches = this.branchRepository.findAll(); - Set docIds = new HashSet<>(); - branches.forEach(branch -> { - docIds.add(branch.getDocId()); - }); - branchesResponse.setRefs(branchIndex.findAllById(docIds)); + branchesResponse.setRefs(branchPersistence.findAll(projectId)); return branchesResponse; } public RefsResponse getBranch(String projectId, String id) { - ContextHolder.setContext(projectId); - RefsResponse branchesResponse = new RefsResponse(); - Optional branchesOption = this.branchRepository.findByBranchId(id); - if (!branchesOption.isPresent()) { - throw new NotFoundException(branchesResponse); - } - Branch b = branchesOption.get(); - List refs = new ArrayList<>(); - Optional refOption = branchIndex.findById(b.getDocId()); - if (!refOption.isPresent()) { - logger.error("DefaultBranchService: JSON Not found for {} with docId: {}", - b.getBranchId(), b.getDocId()); - throw new NotFoundException(branchesResponse); + RefsResponse response = new RefsResponse(); + Optional refJsonOptional = branchPersistence.findById(projectId, id); + if (refJsonOptional.isEmpty()) { + throw new NotFoundException(response.addMessage("Branch not found")); } - refs.add(refOption.get()); - branchesResponse.setRefs(refs); - if (b.isDeleted()) { - throw new DeletedException(branchesResponse); + response.getRefs().add(refJsonOptional.get()); + if (refJsonOptional.get().isDeleted()) { + throw new DeletedException(response); } - return branchesResponse; + return response; } public RefJson createBranch(String projectId, RefJson branch) { - Instant now = Instant.now(); - ContextHolder.setContext(projectId); - - Optional branchesOption = this.branchRepository.findByBranchId(branch.getId()); - Branch b = branchesOption.orElseGet(Branch::new); - if (b.isDeleted()) { - throw new BadRequestException("Bad Request Error: Branch was previously deleted."); - } + logger.info("Saving branch: {}", branch.getId()); - b.setBranchId(branch.getId()); - b.setBranchName(branch.getName()); - b.setDescription(branch.getDescription()); - b.setTag(branch.isTag()); - b.setTimestamp(now); + Instant now = Instant.now(); branch.setCreated(Formats.FORMATTER.format(now)); branch.setDeleted(false); branch.setProjectId(projectId); - boolean fromCommit = branch.getParentCommitId() == null ? false : true; - branch.setStatus(fromCommit ? "creating" : "created"); - if (branch.getDocId() == null || branch.getDocId().isEmpty()) { - String docId = branchIndex.createDocId(branch); - branch.setDocId(docId); - b.setDocId(docId); - } - logger.info("Saving branch: {}", branch.getId()); + branch.setStatus(CrudConstants.CREATING); - if (branch.getParentRefId() != null) { - b.setParentRefId(branch.getParentRefId()); - } else { - branch.setParentRefId(Constants.MASTER_BRANCH); - b.setParentRefId(Constants.MASTER_BRANCH); + //Ensure that the type is of Branch + if (branch.getType() == null || branch.getType().isEmpty()) { + branch.setType(Constants.BRANCH_TYPE); } - Optional refOption = branchRepository.findByBranchId(b.getParentRefId()); - if (refOption.isPresent()) { - if(branch.getParentCommitId() != null){ - Optional commitRequestId = commitRepository.findByCommitId(branch.getParentCommitId()); - commitRequestId.ifPresentOrElse(commit -> { - Optional parentCommit = commitRepository.findByRefAndTimestamp(refOption.get(), commit.getTimestamp()); - parentCommit.ifPresent(parent -> { - b.setParentCommit(parent.getId()); - branch.setParentCommitId(parent.getCommitId()); - }); - }, - () -> { throw new BadRequestException(new RefsResponse().addMessage("parentCommitId not found " + now.toString())); - }); - } else { - Optional parentCommit = commitRepository.findLatestByRef(refOption.get()); - parentCommit.ifPresent(parent -> { - b.setParentCommit(parent.getId()); - branch.setParentCommitId(parent.getCommitId()); //commit id is same as its docId - }); + //Find parent branch + String parentRefId; + Optional parentCommit = null; + if (branch.getParentCommitId() != null && !branch.getParentCommitId().isEmpty()) { + parentCommit = commitPersistence.findById(projectId, branch.getParentCommitId()); + if (!parentCommit.isPresent()) { + throw new BadRequestException("Parent commit cannot be determined or found"); } + parentRefId = parentCommit.get().getRefId(); + branch.setParentRefId(parentRefId); + } else if (branch.getParentRefId() != null && !branch.getParentRefId().isEmpty()) { + parentRefId = branch.getParentRefId(); + } else { + parentRefId = Constants.MASTER_BRANCH; + branch.setParentRefId(parentRefId); + } + Optional parentRefOption = branchPersistence.findById(projectId, parentRefId); + if (!parentRefOption.isPresent()) { + throw new BadRequestException("Parent branch cannot be determined"); } - if (b.getParentCommit() == null) { - throw new BadRequestException("Parent branch or commit cannot be determined"); - //creating more branches are not allowed until there's at least 1 commit, same as git + //Find parent commit + // AND the commit federated are expecting the branches to be in PG + if (parentCommit == null || !parentCommit.isPresent()) { + parentCommit = commitPersistence.findLatestByProjectAndRef(projectId, parentRefId); + parentCommit.ifPresent(parent -> + branch.setParentCommitId(parent.getId()) + ); + } + if (!parentCommit.isPresent()) { + throw new BadRequestException("Parent commit cannot be determined or found"); } + //Do branch creation try { - branchIndex.update(branch); - branchRepository.save(b); - if(!fromCommit) { - Set docIds = new HashSet<>(); - for (Node n: nodeRepository.findAllByDeleted(false)) { - docIds.add(n.getDocId()); - } - - try { nodeIndex.addToRef(docIds); } catch(Exception e) {} - } - eventPublisher.forEach((pub) -> pub.publish( - EventObject.create(projectId, branch.getId(), "branch_created", branch))); - return branch; + RefJson committedBranch = branchPersistence.save(branch); + nodePersistence.branchElements(parentRefOption.get(), parentCommit.get(), committedBranch); + committedBranch.setStatus(CrudConstants.CREATED); + branchPersistence.update(committedBranch); + //TODO transaction commit + eventPublisher.forEach(pub -> pub.publish( + EventObject.create(projectId, committedBranch.getId(), "branch_created", committedBranch))); + return committedBranch; } catch (Exception e) { + //TODO transaction rollback logger.error("Couldn't create branch: {}", branch.getId(), e); - //TODO should clean up any created tables/rows? throw new InternalErrorException(e); } } - public RefJson createBranchfromCommit(String projectId, RefJson parentCommitIdRef, NodeService nodeService) { - Instant now = Instant.now(); - - if(parentCommitIdRef.getParentCommitId().isEmpty()){ - throw new BadRequestException(new RefsResponse().addMessage("parentCommitId not provided " + now.toString())); - } - - ContextHolder.setContext(projectId); - Optional parentCommit = commitRepository.findByCommitId(parentCommitIdRef.getParentCommitId()); - - // Get Commit object - String parentCommitID = parentCommit.map(Commit::getCommitId).orElseThrow(() -> - new BadRequestException(new RefsResponse().addMessage("parentCommitId not found " + now.toString()))); - - // Get Commit parentRef, the branch will be created from this - String parentRef = parentCommit.map(Commit::getBranchId).orElseThrow(() -> - new BadRequestException(new RefsResponse().addMessage("Ref from parentCommitId not found" + now.toString()))); - - parentCommitIdRef.setParentRefId(parentRef); - ContextHolder.setContext(projectId, parentRef); - - RefJson branchFromCommit = this.createBranch(projectId, parentCommitIdRef); - ContextHolder.setContext(projectId, branchFromCommit.getId()); - // Get current nodes from database - List nodes = nodeRepository.findAll(); - // Get elements from index - Collection result = nodeGetHelper.processGetJsonFromNodes(nodes, parentCommitID, nodeService) - .getActiveElementMap().values(); - - Map nodeCommitData = new HashMap<>(); - for (ElementJson element : result) { - nodeCommitData.put(element.getId(), element); - } - - // Update database table to match index - Set docIds = new HashSet<>(); - for (Node node : nodes) { - if(nodeCommitData.containsKey(node.getNodeId())){ - node.setDocId(nodeCommitData.get(node.getNodeId()).getDocId()); - node.setLastCommit(nodeCommitData.get(node.getNodeId()).getCommitId()); - node.setDeleted(false); - docIds.add(node.getDocId()); - } else { - node.setDeleted(true); + @Override + public RefJson updateBranch(String projectId, RefJson branch) { + if (projectId != null && branch != null) { + Optional refJson = branchPersistence.findById(projectId, branch.getId()); + if (!refJson.isPresent()) { + throw new BadRequestException("Branch: " + branch.getId() + " Not found for project: " + projectId); + } + if (refJson.get().isDeleted()) { + //un-delete action + branch.setDeleted(false); } } - nodeRepository.updateAll(nodes); - branchFromCommit.setStatus("created"); - branchIndex.update(branchFromCommit); - try { nodeIndex.addToRef(docIds); } catch(Exception e) {} - - return branchFromCommit; + return branchPersistence.update(branch); } public RefsResponse deleteBranch(String projectId, String id) { - ContextHolder.setContext(projectId); RefsResponse branchesResponse = new RefsResponse(); - if ("master".equals(id)) { + if (Constants.MASTER_BRANCH.equals(id)) { throw new BadRequestException(branchesResponse.addMessage("Cannot delete master")); } - Optional branch = this.branchRepository.findByBranchId(id); + Optional branch = branchPersistence.deleteById(projectId, id); if (!branch.isPresent()) { throw new NotFoundException(branchesResponse); } - Branch b = branch.get(); - b.setDeleted(true); - branchRepository.save(b); - List refs = new ArrayList<>(); - RefJson refJson = new RefJson().setDocId(b.getDocId()).setDeleted(true) - .setProjectId(projectId).setId(id); - refs.add(branchIndex.update(refJson)); - branchesResponse.setRefs(refs); + branchesResponse.setRefs(branch.map(List::of).orElseGet(List::of)); return branchesResponse; } } 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 6479e146b..ec3224910 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 @@ -1,55 +1,39 @@ package org.openmbee.mms.crud.services; import java.text.ParseException; +import java.text.SimpleDateFormat; import java.time.Instant; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; -import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.core.config.Formats; import org.openmbee.mms.core.exceptions.BadRequestException; -import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.exceptions.NotFoundException; -import org.openmbee.mms.core.dao.BranchDAO; +import org.openmbee.mms.core.dao.*; import org.openmbee.mms.core.services.CommitService; -import org.openmbee.mms.data.domains.scoped.Branch; import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.RefJson; import org.openmbee.mms.core.objects.CommitsRequest; import org.openmbee.mms.core.objects.CommitsResponse; -import org.openmbee.mms.data.domains.scoped.Commit; -import org.openmbee.mms.core.dao.CommitDAO; -import org.openmbee.mms.core.dao.CommitIndexDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("defaultCommitService") public class DefaultCommitService implements CommitService { - protected CommitDAO commitRepository; - private CommitIndexDAO commitIndex; - private BranchDAO branchRepository; + protected CommitPersistence commitPersistence; + protected BranchPersistence branchPersistence; @Autowired - public void setCommitDao(CommitDAO commitDao) { - this.commitRepository = commitDao; + public void setCommitPersistence(CommitPersistence commitPersistence) { + this.commitPersistence = commitPersistence; } @Autowired - public void setBranchRepository(BranchDAO branchRepository) { - this.branchRepository = branchRepository; - } - - @Autowired - public void setCommitElasticDao(CommitIndexDAO commitElasticRepository) { - this.commitIndex = commitElasticRepository; + public void setBranchPersistence(BranchPersistence branchPersistence) { + this.branchPersistence = branchPersistence; } @Override public CommitsResponse getRefCommits(String projectId, String refId, Map params) { - ContextHolder.setContext(projectId, refId); int limit = 0; Instant timestamp = null; if (params.containsKey("limit")) { @@ -57,14 +41,14 @@ public CommitsResponse getRefCommits(String projectId, String refId, Map ref = branchRepository.findByBranchId(refId); + Optional ref = branchPersistence.findById(projectId, refId); List resJson = new ArrayList<>(); CommitsResponse res = new CommitsResponse(); @@ -72,17 +56,9 @@ public CommitsResponse getRefCommits(String projectId, String refId, Map { - List commits = commitRepository.findByRefAndTimestampAndLimit(branch, finalTimestamp, finalLimit); - for (Commit c : commits) { - CommitJson json = new CommitJson(); - json.setCreated(Formats.FORMATTER.format(c.getTimestamp())); - json.setCreator(c.getCreator()); - json.setId(c.getCommitId()); - json.setComment(c.getComment()); - json.setRefId(c.getBranchId()); - json.setProjectId(projectId); - resJson.add(json); - } + //branch for case that finalTimestamp is null AND/OR finalLimit is 0 + List commits = commitPersistence.findByProjectAndRefAndTimestampAndLimit(projectId, refId, finalTimestamp, finalLimit); + resJson.addAll(commits); }, () -> { throw new NotFoundException("Branch not found"); }); @@ -93,56 +69,46 @@ public CommitsResponse getRefCommits(String projectId, String refId, Map commit = commitIndex.findById(commitId); - if (commit.isPresent()) { - res.getCommits().add(commit.get()); - } else { + Optional commit = commitPersistence.findById(projectId, commitId); + if (!commit.isPresent()) { throw new NotFoundException("Commit not found"); } + res.getCommits().add(commit.get()); return res; } @Override public CommitsResponse getElementCommits(String projectId, String refId, String elementId, Map params) { - ContextHolder.setContext(projectId); CommitsResponse res = new CommitsResponse(); - Optional ref = branchRepository.findByBranchId(refId); + Optional ref = branchPersistence.findById(projectId, refId); if (!ref.isPresent()) { throw new NotFoundException("Branch not found"); } - List refCommits = commitRepository.findByRefAndTimestampAndLimit(ref.get(), null, 0); - Set commitIds = new HashSet<>(); - for (Commit commit: refCommits) { - commitIds.add(commit.getCommitId()); - } - res.getCommits().addAll(commitIndex.elementHistory(elementId, commitIds)); + res.getCommits().addAll(commitPersistence.elementHistory(projectId, refId, elementId)); return res; } @Override public CommitsResponse getCommits(String projectId, CommitsRequest req) { - ContextHolder.setContext(projectId); Set ids = new HashSet<>(); for (CommitJson j : req.getCommits()) { ids.add(j.getId()); } CommitsResponse res = new CommitsResponse(); - try { - res.getCommits().addAll(commitIndex.findAllById(ids)); - } catch (Exception e) { - e.printStackTrace(); - throw new InternalErrorException(e); + List commitJsonList = commitPersistence.findAllById(projectId, ids); + if (commitJsonList.isEmpty()) { + throw new NotFoundException("Commit not found"); } + + res.getCommits().addAll(commitJsonList); return res; } @Override public boolean isProjectNew(String projectId) { - ContextHolder.setContext(projectId); - List commits = commitRepository.findAll(); + // if project is not new, there must be at least 1 commit to master + List commits = commitPersistence.findByProjectAndRefAndTimestampAndLimit(projectId, "master", null, 1); return commits == null || commits.isEmpty(); } } 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 6beb8ae60..671581132 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 @@ -1,96 +1,54 @@ package org.openmbee.mms.crud.services; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.*; - -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; +import org.openmbee.mms.core.dao.*; import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.core.exceptions.ConflictException; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.objects.ElementsCommitResponse; +import org.openmbee.mms.core.objects.ElementsRequest; +import org.openmbee.mms.core.objects.ElementsResponse; import org.openmbee.mms.core.objects.EventObject; import org.openmbee.mms.core.services.EventService; import org.openmbee.mms.core.services.NodeChangeInfo; import org.openmbee.mms.core.services.NodeGetInfo; import org.openmbee.mms.core.services.NodeService; -import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.objects.ElementsRequest; -import org.openmbee.mms.core.objects.ElementsResponse; -import org.openmbee.mms.data.domains.scoped.Commit; -import org.openmbee.mms.data.domains.scoped.CommitType; -import org.openmbee.mms.data.domains.scoped.Node; -import org.openmbee.mms.core.dao.CommitDAO; -import org.openmbee.mms.core.dao.CommitIndexDAO; -import org.openmbee.mms.core.dao.NodeDAO; -import org.openmbee.mms.core.dao.NodeIndexDAO; +import org.openmbee.mms.crud.CrudConstants; +import org.openmbee.mms.json.BaseJson; import org.openmbee.mms.json.CommitJson; import org.openmbee.mms.json.ElementJson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -@Service("defaultNodeService") -public class DefaultNodeService implements NodeService { +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; - @Value("${mms.stream.batch.size:100000}") - protected int streamLimit; - protected final ObjectMapper objectMapper = new ObjectMapper(); +@Service("defaultNodeService") +public class DefaultNodeService implements NodeService { protected final Logger logger = LoggerFactory.getLogger(getClass()); - protected NodeDAO nodeRepository; - protected CommitDAO commitRepository; - protected NodeIndexDAO nodeIndex; - //to save to this use base json classes - protected CommitIndexDAO commitIndex; - protected NodeGetHelper nodeGetHelper; - protected NodePostHelper nodePostHelper; - protected NodeDeleteHelper nodeDeleteHelper; + protected CommitPersistence commitPersistence; + protected NodePersistence nodePersistence; protected Collection eventPublisher; @Autowired - public void setNodeRepository(NodeDAO nodeRepository) { - this.nodeRepository = nodeRepository; + public void setCommitPersistence(CommitPersistence commitPersistence) { + this.commitPersistence = commitPersistence; } @Autowired - public void setCommitRepository(CommitDAO commitRepository) { - this.commitRepository = commitRepository; + public void setNodePersistence(NodePersistence nodePersistence) { + this.nodePersistence = nodePersistence; } - @Autowired - public void setNodeIndex(NodeIndexDAO nodeIndex) { - this.nodeIndex = nodeIndex; - } - - @Autowired - public void setCommitIndex(CommitIndexDAO commitIndex) { - this.commitIndex = commitIndex; - } - - @Autowired - public void setNodePostHelper(NodePostHelper nodePostHelper) { - this.nodePostHelper = nodePostHelper; - } - - @Autowired - public void setNodeDeleteHelper(NodeDeleteHelper nodeDeleteHelper) { - this.nodeDeleteHelper = nodeDeleteHelper; - } - - @Autowired - public void setNodeGetHelper(NodeGetHelper nodeGetHelper) { - this.nodeGetHelper = nodeGetHelper; + public NodePersistence getNodePersistence() { + return nodePersistence; } @Autowired @@ -99,53 +57,37 @@ public void setEventPublisher(Collection eventPublisher) { } @Override - public void readAsStream(String projectId, String refId, - Map params, OutputStream stream, String accept) throws IOException { + public void readAsStream(String projectId, String refId, Map params, OutputStream stream, + String accept) throws IOException { + + String commitId = params.getOrDefault(CrudConstants.COMMITID, null); - String commitId = params.getOrDefault("commitId", null); - ContextHolder.setContext(projectId, refId); - List nodes; - String resCommitId; if (commitId != null && !commitId.isEmpty()) { - if (!commitRepository.findByCommitId(commitId).isPresent()) { + if (!commitPersistence.findById(projectId, commitId).isPresent()) { throw new BadRequestException("commit id is invalid"); } - nodes = nodeRepository.findAll(); - resCommitId = commitId; } else { - nodes = nodeRepository.findAllByDeleted(false); - resCommitId = nodeGetHelper.getLatestRefCommitId(); + Optional commitJson = commitPersistence.findLatestByProjectAndRef(projectId, refId); + if (!commitJson.isPresent()) { + throw new InternalErrorException("Could not find latest commit for project and ref"); + } + commitId = commitJson.get().getId(); } + String separator = "\n"; if (!"application/x-ndjson".equals(accept)) { - String intro = "{\"commitId\":" + (resCommitId == null ? "null" : "\"" + resCommitId + "\"") + ",\"elements\":["; + String intro = "{\"commitId\":\"" + commitId + "\",\"elements\":["; stream.write(intro.getBytes(StandardCharsets.UTF_8)); separator = ","; } - final String sep = separator; - AtomicInteger counter = new AtomicInteger(); - batches(nodes, streamLimit).forEach(ns -> { - try { - if (counter.get() == 0) { - counter.getAndIncrement(); - } else { - stream.write(sep.getBytes(StandardCharsets.UTF_8)); - } - Collection result = nodeGetHelper.processGetJsonFromNodes(ns, commitId, this) - .getActiveElementMap().values(); - stream.write(result.stream().map(this::toJson).collect(Collectors.joining(sep)) - .getBytes(StandardCharsets.UTF_8)); - } catch (IOException ioe) { - logger.error("Error writing to stream", ioe); - throw new InternalErrorException("Error writing to stream."); - } - }); + + nodePersistence.streamAllAtCommit(projectId, refId, commitId, stream, separator); + if (!"application/x-ndjson".equals(accept)) { stream.write("]}".getBytes(StandardCharsets.UTF_8)); } else { stream.write("\n".getBytes(StandardCharsets.UTF_8)); } - stream.close(); } @Override @@ -154,132 +96,122 @@ public ElementsResponse read(String projectId, String refId, String id, if (id != null && !id.isEmpty()) { logger.debug("ElementId given: {}", id); - ElementsRequest req = buildRequest(id); return read(projectId, refId, req, params); - - } else { - // If no id is provided, return all - logger.debug("No ElementId given"); - ContextHolder.setContext(projectId, refId); - - ElementsResponse response = new ElementsResponse(); - String commitId = params.getOrDefault("commitId", null); - response.getElements().addAll(nodeGetHelper.processGetAll(commitId, this)); - if (commitId != null) { - response.setCommitId(commitId); - } else { - response.setCommitId(nodeGetHelper.getLatestRefCommitId()); + } + String commitId = params.getOrDefault(CrudConstants.COMMITID, null); + if (commitId == null) { + Optional commitJson = commitPersistence.findLatestByProjectAndRef(projectId, refId); + if (!commitJson.isPresent()) { + throw new InternalErrorException("Could not find latest commit for project and ref"); } - return response; + commitId = commitJson.get().getId(); } + // If no id is provided, return all + ElementsResponse response = new ElementsResponse(); + logger.debug("No ElementId given"); + + List nodes = nodePersistence.findAll(projectId, refId, commitId); + response.getElements().addAll(nodes); + response.getElements().forEach(v -> v.setRefId(refId)); + response.setCommitId(commitId); + return response; } @Override public ElementsResponse read(String projectId, String refId, ElementsRequest req, Map params) { - String commitId = params.getOrDefault("commitId", null); - ContextHolder.setContext(projectId, refId); + String commitId = params.getOrDefault(CrudConstants.COMMITID, null); + if (commitId == null) { + Optional commitJson = commitPersistence.findLatestByProjectAndRef(projectId, refId); + if (!commitJson.isPresent()) { + throw new InternalErrorException("Could not find latest commit for project and ref"); + } + commitId = commitJson.get().getId(); + } - NodeGetInfo info = nodeGetHelper.processGetJson(req.getElements(), commitId, this); + NodeGetInfo info = nodePersistence.findAll(projectId, refId, commitId, req.getElements()); ElementsResponse response = new ElementsResponse(); response.getElements().addAll(info.getActiveElementMap().values()); + response.getElements().forEach(v -> v.setRefId(refId)); response.setRejected(new ArrayList<>(info.getRejected().values())); - response.setCommitId(info.getCommitId()); + response.setCommitId(commitId); return response; } @Override public ElementsCommitResponse createOrUpdate(String projectId, String refId, ElementsRequest req, - Map params, String user) { + Map params, String user) { - ContextHolder.setContext(projectId, refId); - boolean overwriteJson = Boolean.parseBoolean(params.get("overwrite")); - nodePostHelper.setPreserveTimestamps(Boolean.parseBoolean(params.get("preserveTimestamps"))); - String commitId = params.get("commitId"); + boolean overwriteJson = Boolean.parseBoolean(params.get(CrudConstants.OVERWRITE)); + boolean preserveTimestamps = Boolean.parseBoolean(params.get(CrudConstants.PRESERVETIMESTAMPS)); + String commitId = params.getOrDefault(CrudConstants.COMMITID, null); String lastCommitId = req.getLastCommitId(); - NodeChangeInfo info = nodePostHelper - .processPostJson(req.getElements(), overwriteJson, - createCommit(user, refId, projectId, req, commitId), this, lastCommitId); - - if (req.getDeletes() != null && !req.getDeletes().isEmpty()) { - NodeChangeInfo delete = nodeDeleteHelper.processDeleteJson(req.getDeletes(), createCommit(user, refId, projectId, req, info.getCommitJson().getCommitId()), this); - info.getCommitJson().getDeleted().addAll(delete.getCommitJson().getDeleted()); - info.getDeletedMap().putAll(delete.getDeletedMap()); - info.getToSaveNodeMap().putAll(delete.getToSaveNodeMap()); - info.getOldDocIds().addAll(delete.getOldDocIds()); - info.getRejected().putAll(delete.getRejected()); + if (lastCommitId != null && !lastCommitId.isEmpty()) { + Optional latestCommit = commitPersistence.findLatestByProjectAndRef(projectId, refId); + if (latestCommit.isEmpty() || !lastCommitId.equals(latestCommit.get().getId())) { + throw new ConflictException("Given commitId " + lastCommitId + " is not the latest"); + } } - commitChanges(info); + NodeChangeInfo changes = nodePersistence.prepareChange(createCommit(user, refId, projectId, commitId, req), + overwriteJson, preserveTimestamps); + changes = nodePersistence.prepareAddsUpdates(changes, req.getElements()); + + for (ElementJson element : changes.getUpdatedMap().values()) { + extraProcessPostedElement(changes, element); + } + if (req.getDeletes() != null) { + changes = nodePersistence.prepareDeletes(changes, req.getDeletes()); + } + + changes = nodePersistence.commitChanges(changes); + CommitJson commitJson = changes.getCommitJson(); + eventPublisher.forEach(pub -> pub.publish( + EventObject.create(commitJson.getProjectId(), commitJson.getRefId(), CrudConstants.COMMIT_SC, commitJson))); ElementsCommitResponse response = new ElementsCommitResponse(); - response.getElements().addAll(info.getUpdatedMap().values()); - response.setRejected(new ArrayList<>(info.getRejected().values())); - response.setDeleted(new ArrayList<>(info.getDeletedMap().values())); - if(!info.getUpdatedMap().isEmpty() || !info.getDeletedMap().isEmpty()) { - response.setCommitId(info.getCommitJson().getId()); + response.getElements().addAll(changes.getUpdatedMap().values()); + response.setDeleted(new ArrayList<>(changes.getDeletedMap().values())); + response.setRejected(new ArrayList<>(changes.getRejected().values())); + if(!changes.getUpdatedMap().isEmpty() || !changes.getDeletedMap().isEmpty()) { + response.setCommitId(changes.getCommitJson().getId()); } return response; } - public void commitChanges(NodeChangeInfo info) { - Map nodes = info.getToSaveNodeMap(); - Map json = info.getUpdatedMap(); - CommitJson cmjs = info.getCommitJson(); - Instant now = info.getNow(); - if (!nodes.isEmpty()) { - try { - if (json != null && !json.isEmpty()) { - this.nodeIndex.indexAll(json.values()); - } - try { this.nodeIndex.removeFromRef(info.getOldDocIds()); } catch(Exception e) {} - this.commitIndex.index(cmjs); - - 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.nodeRepository.saveAll(new ArrayList<>(nodes.values())); - } catch (Exception e) { - logger.error("Error in commitChanges: ", e); - throw new InternalErrorException("Error committing changes: " + e.getMessage()); - } - eventPublisher.forEach((pub) -> pub.publish( - EventObject.create(cmjs.getProjectId(), cmjs.getRefId(), "commit", cmjs))); + + //Info is used in one of the child classes + public void extraProcessPostedElement(NodeChangeInfo info, ElementJson element) { + if (element.getType() == null || element.getType().isEmpty()) { + element.setType(CrudConstants.NODE); } } @Override - public void extraProcessPostedElement(ElementJson element, Node node, NodeChangeInfo info) { + public ElementsCommitResponse delete(String projectId, String refId, String id, String user) { + ElementsRequest req = buildRequest(id); + return delete(projectId, refId, req, user); } @Override - public void extraProcessDeletedElement(ElementJson element, Node node, NodeChangeInfo info) { - } + public ElementsCommitResponse delete(String projectId, String refId, ElementsRequest req, String user) { + NodeChangeInfo changes = nodePersistence.prepareChange(createCommit(user, refId, projectId, null, req), + false, false); + changes = nodePersistence.prepareDeletes(changes, req.getElements()); - @Override - public void extraProcessGotElement(ElementJson element, Node node, NodeGetInfo info) { - } - @Override - public ElementsCommitResponse delete(String projectId, String refId, String id, String user) { - ElementsRequest req = buildRequest(id); - return delete(projectId, refId, req, user); + changes = nodePersistence.commitChanges(changes); + ElementsCommitResponse response = new ElementsCommitResponse(); + response.getElements().addAll(changes.getDeletedMap().values()); + response.setRejected(new ArrayList<>(changes.getRejected().values())); + if(!changes.getDeletedMap().isEmpty()) { + response.setCommitId(changes.getCommitJson().getId()); + } + return response; } protected ElementsRequest buildRequest(String id) { @@ -300,52 +232,21 @@ protected ElementsRequest buildRequest(Collection ids) { return req; } - @Override - public ElementsCommitResponse delete(String projectId, String refId, ElementsRequest req, String user) { - ContextHolder.setContext(projectId, refId); - - NodeChangeInfo info = nodeDeleteHelper - .processDeleteJson(req.getElements(), createCommit(user, refId, projectId, req, null), - this); - ElementsCommitResponse response = new ElementsCommitResponse(); - - commitChanges(info); - - response.getElements().addAll(info.getDeletedMap().values()); - response.setRejected(new ArrayList<>(info.getRejected().values())); - if(!info.getDeletedMap().isEmpty()) { - response.setCommitId(info.getCommitJson().getId()); - } - return response; + protected ElementsRequest buildRequestFromJsons(Collection jsons) { + return buildRequest(jsons.stream().map(BaseJson::getId).collect(Collectors.toList())); } - private CommitJson createCommit(String creator, String refId, String projectId, - ElementsRequest req, String commitId) { + private CommitJson createCommit(String creator, String refId, String projectId, String commitId, ElementsRequest req) { 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()) { + if (commitId != null) { cmjs.setId(commitId); + cmjs.setDocId(commitId); } - return cmjs; } - - protected static Stream> batches(List source, int length) { - return IntStream.iterate(0, i -> i < source.size(), i -> i + length) - .mapToObj(i -> source.subList(i, Math.min(i + length, source.size()))); - } - - protected String toJson(ElementJson elementJson) { - try { - return objectMapper.writeValueAsString(elementJson); - } catch (JsonProcessingException e) { - logger.error("Error in toJson: ", e); - } - return ""; - } } diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java index 2c633a97b..bfbca9148 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/DefaultProjectService.java @@ -1,70 +1,48 @@ package org.openmbee.mms.crud.services; -import java.util.Collection; - import org.openmbee.mms.core.config.Constants; -import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.config.Formats; -import org.openmbee.mms.core.dao.BranchDAO; -import org.openmbee.mms.core.dao.BranchIndexDAO; -import org.openmbee.mms.core.dao.OrgDAO; -import org.openmbee.mms.core.dao.ProjectDAO; -import org.openmbee.mms.core.dao.ProjectIndex; +import org.openmbee.mms.core.dao.BranchPersistence; +import org.openmbee.mms.core.dao.OrgPersistence; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.objects.EventObject; import org.openmbee.mms.core.objects.ProjectsResponse; import org.openmbee.mms.core.services.EventService; import org.openmbee.mms.core.services.ProjectService; -import org.openmbee.mms.data.domains.global.Organization; -import org.openmbee.mms.data.domains.global.Project; -import org.openmbee.mms.data.domains.scoped.Branch; -import org.openmbee.mms.json.ProjectJson; -import org.openmbee.mms.json.RefJson; -import org.openmbee.mms.json.RefType; +import org.openmbee.mms.json.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; -import java.time.Instant; +import java.util.Collection; import java.util.Optional; -import java.util.UUID; @Service("defaultProjectService") +@Primary public class DefaultProjectService implements ProjectService { protected final Logger logger = LoggerFactory.getLogger(getClass()); - protected ProjectDAO projectRepository; - protected OrgDAO orgRepository; - protected ProjectIndex projectIndex; - protected BranchDAO branchRepository; - protected BranchIndexDAO branchIndex; + protected ProjectPersistence projectPersistence; + protected OrgPersistence orgPersistence; + protected BranchPersistence branchPersistence; protected Collection eventPublisher; @Autowired - public void setProjectRepository(ProjectDAO projectRepository) { - this.projectRepository = projectRepository; - } - - @Autowired - public void setOrganizationRepository(OrgDAO orgRepository) { - this.orgRepository = orgRepository; + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; } @Autowired - public void setProjectIndex(ProjectIndex projectIndex) { - this.projectIndex = projectIndex; + public void setOrgPersistence(OrgPersistence orgDAO) { + this.orgPersistence = orgDAO; } @Autowired - public void setBranchRepository(BranchDAO branchRepository) { - this.branchRepository = branchRepository; - } - - @Autowired - public void setBranchIndex(BranchIndexDAO branchIndex) { - this.branchIndex = branchIndex; + public void setBranchPersistence(BranchPersistence branchPersistence) { + this.branchPersistence = branchPersistence; } @Autowired @@ -77,83 +55,42 @@ public ProjectJson create(ProjectJson project) { throw new BadRequestException("Organization ID not provided"); } - Optional org = orgRepository.findByOrganizationId(project.getOrgId()); - if (!org.isPresent() || org.get().getOrganizationId().isEmpty()) { + Optional org = orgPersistence.findById(project.getOrgId()); + if (org.isEmpty() || org.get().getId().isEmpty()) { throw new BadRequestException("Organization not found"); } - Project proj = new Project(); - proj.setProjectId(project.getId()); - proj.setProjectName(project.getName()); - proj.setOrganization(org.get()); - proj.setProjectType(project.getProjectType()); - - String uuid = UUID.randomUUID().toString(); - proj.setDocId(uuid); - project.setDocId(uuid); - project.setCreated(Formats.FORMATTER.format(Instant.now())); - project.setType("Project"); - try { - projectRepository.save(proj); - projectIndex.create(project); - - Optional masterBranch = branchRepository.findByBranchId(Constants.MASTER_BRANCH); - if (masterBranch.isPresent()) { - String docId = UUID.randomUUID().toString(); - Branch master = masterBranch.get(); - master.setDocId(docId); - master.setParentCommit(0L); + //TODO Transaction start + ProjectJson savedProjectJson = projectPersistence.save(project); - branchRepository.save(master); + //create and save master branch. We're combining operations with the branch unifiedDAO. + branchPersistence.save(createMasterRefJson(savedProjectJson)); + //TODO Transaction commit - RefJson branchJson = createRefJson(project, docId); - branchIndex.index(branchJson); - } - eventPublisher.forEach((pub) -> pub.publish( - EventObject.create(project.getId(), "master", "project_created", project))); - return project; + eventPublisher.forEach(pub -> pub.publish( + EventObject.create(savedProjectJson.getId(), Constants.MASTER_BRANCH, "project_created", savedProjectJson))); + return savedProjectJson; } catch (Exception e) { logger.error("Couldn't create project: {}", project.getProjectId(), e); + //Need to clean up in case of partial creation + projectPersistence.hardDelete(project.getProjectId()); + //TODO Transaction rollback (could include project delete in rollback) } throw new InternalErrorException("Could not create project"); } - public RefJson createRefJson(ProjectJson project, String docId){ - RefJson branchJson = new RefJson(); - branchJson.setId(Constants.MASTER_BRANCH); - branchJson.setName(Constants.MASTER_BRANCH); - branchJson.setParentRefId(null); - branchJson.setDocId(docId); - branchJson.setRefType(RefType.Branch); - branchJson.setCreated(project.getCreated()); - branchJson.setProjectId(project.getId()); - branchJson.setCreator(project.getCreator()); - branchJson.setDeleted(false); - return branchJson; - } - public ProjectJson update(ProjectJson project) { - Optional projOption = projectRepository.findByProjectId(project.getProjectId()); - if (projOption.isPresent()) { - ContextHolder.setContext(project.getProjectId()); - Project proj = projOption.get(); - if (project.getName() != null && !project.getName().isEmpty()) { - proj.setProjectName(project.getName()); + if (project.getOrgId() != null && !project.getOrgId().isEmpty()) { + Optional org = orgPersistence.findById(project.getOrgId()); + if (org.isPresent() && !org.get().getId().isEmpty()) { + project.setOrgId(org.get().getId()); + } else { + throw new BadRequestException("Invalid organization"); } - if (project.getOrgId() != null && !project.getOrgId().isEmpty()) { - Optional org = orgRepository.findByOrganizationId(project.getOrgId()); - if (org.isPresent() && !org.get().getOrganizationId().isEmpty()) { - proj.setOrganization(org.get()); - } else { - throw new BadRequestException("Invalid organization"); - } - } - project.setDocId(proj.getDocId()); - projectRepository.save(proj); - return projectIndex.update(project); } - throw new InternalErrorException("Could not update project"); + + return projectPersistence.update(project); } public ProjectsResponse read(String projectId) { @@ -161,7 +98,21 @@ public ProjectsResponse read(String projectId) { } public boolean exists(String projectId) { - Optional project = this.projectRepository.findByProjectId(projectId); + Optional project = this.projectPersistence.findById(projectId); return project.isPresent() && project.get().getProjectId().equals(projectId); } + + + public RefJson createMasterRefJson(ProjectJson project) { + RefJson branchJson = new RefJson(); + branchJson.setId(Constants.MASTER_BRANCH); + branchJson.setName(Constants.MASTER_BRANCH); + branchJson.setParentRefId(null); + branchJson.setRefType(RefType.Branch); + branchJson.setCreated(project.getCreated()); + branchJson.setProjectId(project.getId()); + branchJson.setCreator(project.getCreator()); + branchJson.setDeleted(false); + return branchJson; + } } diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/ElementUtilsFactory.java b/crud/src/main/java/org/openmbee/mms/crud/services/ElementUtilsFactory.java new file mode 100644 index 000000000..39973c2ab --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/services/ElementUtilsFactory.java @@ -0,0 +1,28 @@ +package org.openmbee.mms.crud.services; + +import org.openmbee.mms.core.utils.ElementUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class ElementUtilsFactory implements ApplicationContextAware { + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext context) { + this.context = context; + } + + public ElementUtils getElementUtil(String type) { + try { + ElementUtils eu = context.getBean(type + "Helper", ElementUtils.class); + if (eu != null) { + return eu; + } + } catch (BeansException e) { + } + return null; + } +} \ No newline at end of file diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/NodeDeleteHelper.java b/crud/src/main/java/org/openmbee/mms/crud/services/NodeDeleteHelper.java deleted file mode 100644 index 98caddfc7..000000000 --- a/crud/src/main/java/org/openmbee/mms/crud/services/NodeDeleteHelper.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.openmbee.mms.crud.services; - -import java.util.List; -import java.util.Map; - -import org.openmbee.mms.core.objects.Rejection; -import org.openmbee.mms.core.services.NodeChangeInfo; -import org.openmbee.mms.core.services.NodeService; -import org.openmbee.mms.data.domains.scoped.Node; -import org.openmbee.mms.json.CommitJson; -import org.openmbee.mms.json.ElementJson; -import org.springframework.stereotype.Service; - -@Service -public class NodeDeleteHelper extends NodeOperation { - - public NodeChangeInfo processDeleteJson(List elements, CommitJson cmjs, NodeService service) { - NodeChangeInfo info = initInfo(elements, cmjs); - - for (String nodeId : info.getReqElementMap().keySet()) { - if (!existingNodeContainsNodeId(info, nodeId)) { - continue; - } - Node node = info.getExistingNodeMap().get(nodeId); - Map indexElement = info.getExistingElementMap().get(nodeId); - if (node.isDeleted()) { - info.addRejection(nodeId, new Rejection(indexElement, 304, "Already deleted")); - continue; - } - if (indexElement == null) { - logger.warn("node db and index mismatch on element delete: nodeId: " + nodeId + - ", docId not found: " + info.getExistingNodeMap().get(nodeId).getDocId()); - indexElement = Map.of("id", nodeId); - } - ElementJson request = info.getReqElementMap().get(nodeId); - request.putAll(indexElement); - processElementDeleted(request, node, info); - service.extraProcessDeletedElement(request, node, info); - info.getDeletedMap().put(nodeId, request); - } - return info; - } -} 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 deleted file mode 100644 index 00e14f78f..000000000 --- a/crud/src/main/java/org/openmbee/mms/crud/services/NodeGetHelper.java +++ /dev/null @@ -1,243 +0,0 @@ -package org.openmbee.mms.crud.services; - -import java.time.Instant; -import java.util.*; - -import java.util.stream.Collectors; - -import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.objects.Rejection; -import org.openmbee.mms.core.services.NodeGetInfo; -import org.openmbee.mms.core.exceptions.BadRequestException; -import org.openmbee.mms.core.services.NodeService; -import org.openmbee.mms.data.domains.scoped.Branch; -import org.openmbee.mms.data.domains.scoped.Commit; -import org.openmbee.mms.data.domains.scoped.Node; -import org.openmbee.mms.core.dao.CommitIndexDAO; -import org.openmbee.mms.json.CommitJson; -import org.openmbee.mms.json.ElementJson; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import static org.openmbee.mms.core.config.ContextHolder.getContext; - -@Service -public class NodeGetHelper extends NodeOperation { - - protected CommitIndexDAO commitIndex; - - @Autowired - public void setCommitIndex(CommitIndexDAO commitIndex) { - this.commitIndex = commitIndex; - } - - public NodeGetInfo processGetJsonFromNodes(List nodes, NodeService service) { - NodeGetInfo info = initInfoFromNodes(nodes, null); - return processLatest(info, service); - } - - public NodeGetInfo processGetJson(List elements, NodeService service) { - NodeGetInfo info = initInfo(elements, null); - return processLatest(info, service); - } - - private NodeGetInfo processLatest(NodeGetInfo info, NodeService service) { - for (String nodeId : info.getReqElementMap().keySet()) { - if (!existingNodeContainsNodeId(info, nodeId)) { - continue; - } - ElementJson indexElement = info.getExistingElementMap().get(nodeId); - if (info.getExistingNodeMap().get(nodeId).isDeleted()) { - rejectDeleted(info, nodeId, indexElement == null ? new ElementJson().setId(nodeId) : indexElement); - continue; - } - if (indexElement == null) { - logger.warn("node db and index mismatch on element get: nodeId: " + nodeId + - ", docId not found: " + info.getExistingNodeMap().get(nodeId).getDocId()); - rejectNotFound(info, nodeId); - continue; - } - if (service != null) { - service.extraProcessGotElement(indexElement, info.getExistingNodeMap().get(nodeId), info); - } - info.getActiveElementMap().put(nodeId, indexElement); - } - info.setCommitId(getLatestRefCommitId()); - return info; - } - - public NodeGetInfo processGetJsonFromNodes(List nodes, String commitId, NodeService service) { - if (commitId == null || commitId.isEmpty()) { - return processGetJsonFromNodes(nodes, service); - } - NodeGetInfo info = initInfoFromNodes(nodes, null); - return processCommit(info, commitId, service); - } - - public NodeGetInfo processGetJson(List elements, String commitId, NodeService service) { - if (commitId == null || commitId.isEmpty()) { - return processGetJson(elements, service); - } - NodeGetInfo info = initInfo(elements, null); //gets all current nodes - return processCommit(info, commitId, service); - } - - private NodeGetInfo processCommit(NodeGetInfo info, String commitId, NodeService service) { - Optional commit = commitRepository.findByCommitId(commitId); - if (!commit.isPresent() ) { - throw new BadRequestException("commitId is invalid"); - } - info.setCommitId(commitId); - Instant time = commit.get().getTimestamp(); //time of commit - List refCommitIds = null; //get it later if needed - for (String nodeId : info.getReqElementMap().keySet()) { - if (!existingNodeContainsNodeId(info, nodeId)) { // nodeId not found - continue; - } - ElementJson indexElement = info.getExistingElementMap().get(nodeId); - if (indexElement == null) { - Node n = info.getExistingNodeMap().get(nodeId); - logger.warn("node db and index mismatch on element commit get: nodeId: " + nodeId + - ", docId not found: " + n.getDocId()); - Optional last = commitRepository.findByCommitId(n.getLastCommit()); - Optional first = commitRepository.findByCommitId(n.getInitialCommit()); - if (!last.isPresent() || !first.isPresent()) { - rejectNotFound(info, nodeId); - continue; - } - indexElement = new ElementJson().setId(nodeId).setDocId(n.getDocId()); - indexElement.setModified(formatter.format(last.get().getTimestamp())); - indexElement.setModifier(last.get().getCreator()); - indexElement.setCommitId(last.get().getCommitId()); - indexElement.setCreator(first.get().getCreator()); - indexElement.setCreated(formatter.format(first.get().getTimestamp())); - } - Instant modified = Instant.from(formatter.parse(indexElement.getModified())); - Instant created = Instant.from(formatter.parse(indexElement.getCreated())); - indexElement.setRefId(ContextHolder.getContext().getBranchId()); - if (commitId.equals(indexElement.getCommitId())) { //exact match - info.getActiveElementMap().put(nodeId, indexElement); - } else if (created.isAfter(time)) { // element created after commit - rejectNotFound(info, nodeId); - } else if (modified.isAfter(time)) { // latest element is after commit - Optional tryExact = nodeIndex.getByCommitId(commitId, nodeId); - if (tryExact.isPresent()) { - tryExact.get().setRefId(ContextHolder.getContext().getBranchId()); - info.getActiveElementMap().put(nodeId, tryExact.get()); - continue; // found exact match at commit - } - if (refCommitIds == null) { // need list of commitIds of current ref to filter on - refCommitIds = getRefCommitIds(time); - } - Optional e = nodeIndex.getElementLessThanOrEqualTimestamp(nodeId, - formatter.format(time), refCommitIds); - if (e.isPresent()) { // found version of element at commit time - Instant realModified = Instant.from(formatter.parse(e.get().getModified())); - if (elementDeleted(nodeId, commitId, time, realModified, refCommitIds)) { - rejectDeleted(info, nodeId, e.get()); - } else { - e.get().setRefId(ContextHolder.getContext().getBranchId()); - info.getActiveElementMap().put(nodeId, e.get()); - } - } else { - rejectNotFound(info, nodeId); // element not found at commit time - } - } else if (info.getExistingNodeMap().get(nodeId).isDeleted()) { // latest element is before commit, but deleted - if (refCommitIds == null) { // need list of commitIds of current ref to filter on - refCommitIds = getRefCommitIds(time); - } - if (elementDeleted(nodeId, commitId, time, modified, refCommitIds)) { - rejectDeleted(info, nodeId, indexElement); - } else { - info.getActiveElementMap().put(nodeId, indexElement); - } - } else { // latest element version is version at commit, not deleted - info.getActiveElementMap().put(nodeId, indexElement); - } - } - return info; - } - - private boolean elementDeleted(String nodeId, String commitId, Instant time, Instant modified, List refCommitIds) { - List commits = commitIndex.elementDeletedHistory(nodeId, refCommitIds); - for (CommitJson c: commits) { - Instant deletedTime = Instant.from(formatter.parse(c.getCreated())); - if ((deletedTime.isBefore(time) || c.getId().equals(commitId)) && deletedTime.isAfter(modified)) { - //there's a delete between element last modified time and requested commit time - //or element is deleted at commit - return true; - } - } - return false; - } - - public NodeGetInfo processGetJson(List elements, Instant time, NodeService service) { - Optional ref = branchRepository.findByBranchId(getContext().getBranchId()); - if (ref.isPresent()) { - Optional c = commitRepository.findLatestByRef(ref.get()); - if (c.isPresent()) { - return processGetJson(elements, c.get().getCommitId(), service); - } else { - throw new BadRequestException("invalid time"); - } - } - return null; - } - - public List processGetAll() { - Set indexIds = new HashSet<>(); - List existingNodes = nodeRepository.findAllByDeleted(false); - for (Node node : existingNodes) { - indexIds.add(node.getDocId()); - } - List els = nodeIndex.findAllById(indexIds); - String ref = ContextHolder.getContext().getBranchId(); - for (ElementJson el: els) { - el.setRefId(ref); - } - return els; - } - - public List processGetAll(String commitId, NodeService service) { - if (commitId == null || commitId.isEmpty()) { - return processGetAll(); - } else { - List nodes = nodeRepository.findAll(); - List el = nodes.stream().map( - node -> new ElementJson().setId(node.getNodeId())).collect(Collectors.toList()); - NodeGetInfo info = processGetJson(el, commitId, service); - return new ArrayList<>(info.getActiveElementMap().values()); - } - } - - public List processGetAll(Instant time, NodeService service) { - List result = new ArrayList<>(); - Optional ref = branchRepository.findByBranchId(getContext().getBranchId()); - if (ref.isPresent()) { - Optional c = commitRepository.findByRefAndTimestamp(ref.get(), time); - if (c.isPresent()) { - result.addAll(processGetAll(c.get().getCommitId(), service)); - } else { - throw new BadRequestException("invalid time"); - } - } - return result; - } - - protected void rejectDeleted(NodeGetInfo info, String nodeId, ElementJson indexElement) { - info.addRejection(nodeId, new Rejection(indexElement, 410, "Element deleted")); - } - - protected List getRefCommitIds(Instant time) { - List commitIds = new ArrayList<>(); - - Optional ref = branchRepository.findByBranchId(getContext().getBranchId()); - ref.ifPresent(current -> { - List refCommits = commitRepository.findByRefAndTimestampAndLimit(current, time, 0); - for (Commit c : refCommits) { - commitIds.add(c.getCommitId()); - } - }); - return commitIds; - } -} \ No newline at end of file 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 deleted file mode 100644 index d6d465a4e..000000000 --- a/crud/src/main/java/org/openmbee/mms/crud/services/NodeOperation.java +++ /dev/null @@ -1,289 +0,0 @@ -package org.openmbee.mms.crud.services; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; - -import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.objects.Rejection; -import org.openmbee.mms.core.services.NodeChangeInfo; -import org.openmbee.mms.core.services.NodeGetInfo; -import org.openmbee.mms.core.dao.BranchDAO; -import org.openmbee.mms.core.dao.CommitDAO; -import org.openmbee.mms.data.domains.scoped.Branch; -import org.openmbee.mms.data.domains.scoped.Commit; -import org.openmbee.mms.data.domains.scoped.Node; -import org.openmbee.mms.core.dao.NodeDAO; -import org.openmbee.mms.core.dao.NodeIndexDAO; -import org.openmbee.mms.json.CommitJson; -import org.openmbee.mms.json.ElementVersion; -import org.openmbee.mms.json.ElementJson; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class NodeOperation { - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - protected NodeDAO nodeRepository; - protected NodeIndexDAO nodeIndex; - protected CommitDAO commitRepository; - protected BranchDAO branchRepository; - - protected DateTimeFormatter formatter = DateTimeFormatter - .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").withZone( - ZoneId.systemDefault()); - - private boolean preserveTimestamps = false; - - - @Autowired - public void setNodeRepository(NodeDAO nodeRepository) { - this.nodeRepository = nodeRepository; - } - - @Autowired - public void setNodeIndex(NodeIndexDAO nodeIndex) { - this.nodeIndex = nodeIndex; - } - - @Autowired - public void setCommitRepository(CommitDAO commitRepository) { - this.commitRepository = commitRepository; - } - - @Autowired - public void setBranchRepository(BranchDAO branchRepository) { - this.branchRepository = branchRepository; - } - - public void initCommitJson(CommitJson cmjs, Instant now) { - 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<>()); - cmjs.setUpdated(new ArrayList<>()); - cmjs.setType("Commit"); - } - - public NodeChangeInfo initInfoFromNodes(List existingNodes, CommitJson cmjs) { - Set indexIds = new HashSet<>(); - Map existingNodeMap = new HashMap<>(); - Map reqElementMap = new HashMap<>(); - for (Node node : existingNodes) { - indexIds.add(node.getDocId()); - existingNodeMap.put(node.getNodeId(), node); - reqElementMap.put(node.getNodeId(), new ElementJson().setId(node.getNodeId())); - } - // bulk read existing elements in elastic - List existingElements = nodeIndex.findAllById(indexIds); - // set the _refId of the element json to be the ref it's 'found/requested' in, - String ref = ContextHolder.getContext().getBranchId(); - for (ElementJson e: existingElements) { - e.setRefId(ref); - } - Map existingElementMap = convertJsonToMap(existingElements); - - Instant now = Instant.now(); - if (cmjs != null) { - initCommitJson(cmjs, now); - } - - NodeChangeInfo info = new NodeChangeInfo(); - info.setCommitJson(cmjs); - info.setUpdatedMap(new HashMap<>()); - info.setDeletedMap(new HashMap<>()); - info.setExistingElementMap(existingElementMap); - info.setExistingNodeMap(existingNodeMap); - info.setReqElementMap(reqElementMap); - info.setReqIndexIds(indexIds); - info.setToSaveNodeMap(new HashMap<>()); - info.setRejected(new HashMap<>()); - info.setNow(now); - info.setOldDocIds(new HashSet<>()); - info.setActiveElementMap(new HashMap<>()); - return info; - } - - public NodeChangeInfo initInfo(List elements, CommitJson cmjs) { - Map reqElementMap = convertJsonToMap(elements); - List existingNodes = nodeRepository.findAllByNodeIds(reqElementMap.keySet()); - NodeChangeInfo info = initInfoFromNodes(existingNodes, cmjs); - info.setReqElementMap(reqElementMap); - return info; - } - - public void processElementAdded(ElementJson e, Node n, NodeChangeInfo info) { - CommitJson cmjs = info.getCommitJson(); - processElementAddedOrUpdated(e, n, info); - - e.setCreator(cmjs.getCreator()); //Only set on creation of new element - e.setCreated(cmjs.getCreated()); - - ElementVersion newObj = new ElementVersion() - .setDocId(e.getDocId()) - .setId(e.getId()) - .setType("Element"); - cmjs.getAdded().add(newObj); - - n.setNodeId(e.getId()); - n.setInitialCommit(e.getCommitId()); - } - - public void processElementUpdated(ElementJson e, Node n, NodeChangeInfo info) { - String previousDocId = n.getDocId(); - processElementAddedOrUpdated(e, n, info); - - info.getOldDocIds().add(previousDocId); - ElementVersion newObj= new ElementVersion() - .setPreviousDocId(previousDocId) - .setDocId(e.getDocId()) - .setId(e.getId()) - .setType("Element"); - info.getCommitJson().getUpdated().add(newObj); - } - - private void processElementAddedOrUpdated(ElementJson e, Node n, NodeChangeInfo info) { - CommitJson cmjs = info.getCommitJson(); - e.setProjectId(cmjs.getProjectId()); - e.setRefId(cmjs.getRefId()); - List inRefIds = new ArrayList<>(); - inRefIds.add(cmjs.getRefId()); - e.setInRefIds(inRefIds); - String docId = UUID.randomUUID().toString(); - e.setDocId(docId); - e.setCommitId(cmjs.getId()); - - if(!preserveTimestamps) { - e.setModified(cmjs.getCreated()); - e.setModifier(cmjs.getCreator()); - } - - n.setDocId(e.getDocId()); - n.setLastCommit(cmjs.getId()); - n.setDeleted(false); - n.setNodeType(0); - - info.getToSaveNodeMap().put(e.getId(), n); - info.getUpdatedMap().put(e.getId(), e); - } - - public void processElementDeleted(ElementJson e, Node n, NodeChangeInfo info) { - ElementVersion newObj = new ElementVersion() - .setPreviousDocId(n.getDocId()) - .setId(e.getId()) - .setType("Element"); - info.getCommitJson().getDeleted().add(newObj); - info.getOldDocIds().add(n.getDocId()); - info.getToSaveNodeMap().put(n.getNodeId(), n); - n.setDeleted(true); - } - - public boolean existingNodeContainsNodeId(NodeGetInfo info, String nodeId) { - if (!info.getExistingNodeMap().containsKey(nodeId)) { - rejectNotFound(info, nodeId); - return false; - } - return true; - } - - protected void rejectNotFound(NodeGetInfo info, String nodeId) { - info.addRejection(nodeId, new Rejection(nodeId, 404, "Not Found")); - } - - public static Map convertJsonToMap(List elements) { - Map result = new HashMap<>(); - for (ElementJson elem : elements) { - if (elem == null) { - continue; - } - if (elem.getId() != null && !elem.getId().equals("")) { - result.put(elem.getId(), elem); - } - } - return result; - } - - public static Map convertNodesToMap(List nodes) { - Map result = new HashMap<>(); - for (Node node : nodes) { - if (node == null) { - continue; - } - if (node.getNodeId() != null && !node.getNodeId().equals("")) { - result.put(node.getNodeId(), node); - } - } - return result; - } - - public static List sort(List ids, List orig) { - Map map = convertJsonToMap(orig); - List ret = new ArrayList<>(); - for (String id: ids) { - if (map.containsKey(id)) { - ret.add(map.get(id)); - } - } - return ret; - } - - //find first element of type in types following e's relkey (assuming relkey's value is an element id) - public Optional getFirstRelationshipOfType(ElementJson e, List types, String relkey) { - //only for latest graph - String nextId = (String)e.get(relkey); - if (nextId == null || nextId.isEmpty()) { - return Optional.empty(); - } - Optional nextNode = nodeRepository.findByNodeId(nextId); - while (nextNode.isPresent() && !nextNode.get().isDeleted()) { - Optional nextJson = nodeIndex.findById(nextNode.get().getDocId()); - if (!nextJson.isPresent()) { - return Optional.empty(); - } - if (types.contains(nextNode.get().getNodeType())) { - return nextJson; - } - nextId = (String)nextJson.get().get(relkey); - if (nextId == null || nextId.isEmpty()) { - return Optional.empty(); - } - nextNode = nodeRepository.findByNodeId(nextId); - } - return Optional.empty(); - } - - public boolean isPreserveTimestamps() { - return preserveTimestamps; - } - - public void setPreserveTimestamps(boolean preserveTimestamps) { - this.preserveTimestamps = preserveTimestamps; - } - - public String getLatestRefCommitId() { - Optional branch = branchRepository.findByBranchId(ContextHolder.getContext().getBranchId()); - Optional commit = commitRepository.findLatestByRef(branch.get()); - if (commit.isPresent()) { - return commit.get().getCommitId(); - } else { - return null; - } - } -} diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/NodePostHelper.java b/crud/src/main/java/org/openmbee/mms/crud/services/NodePostHelper.java deleted file mode 100644 index e0b9d27a5..000000000 --- a/crud/src/main/java/org/openmbee/mms/crud/services/NodePostHelper.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.openmbee.mms.crud.services; - -import java.text.ParseException; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import java.util.Optional; -import java.util.UUID; -import org.openmbee.mms.core.config.Formats; -import org.openmbee.mms.core.exceptions.ConflictException; -import org.openmbee.mms.core.objects.Rejection; -import org.openmbee.mms.core.services.NodeChangeInfo; -import org.openmbee.mms.core.services.NodeService; -import org.openmbee.mms.data.domains.scoped.Commit; -import org.openmbee.mms.json.BaseJson; -import org.openmbee.mms.json.CommitJson; -import org.openmbee.mms.json.ElementJson; -import org.openmbee.mms.data.domains.scoped.Node; -import org.springframework.stereotype.Service; - -@Service -public class NodePostHelper extends NodeOperation { - - public boolean isUpdated(BaseJson element, Map existing, - NodeChangeInfo info) { - - if (element.isPartialOf(existing)) { - info.addRejection(element.getId(), new Rejection(element, 304, "Is Equivalent")); - return false; - } - return true; - } - - public boolean diffUpdateJson(BaseJson element, Map existing, - NodeChangeInfo info) { - - String jsonModified = element.getModified(); - Object existingModified = existing.get(BaseJson.MODIFIED); - if (jsonModified != null && !jsonModified.isEmpty() && existingModified != null) { - try { - Date jsonModDate = Formats.SDF.parse(jsonModified); - Date existingModDate = Formats.SDF.parse(existingModified.toString()); - if (jsonModDate.before(existingModDate)) { - info.addRejection(element.getId(), new Rejection(element, 409, "Conflict Detected")); - return false; - } - } catch (ParseException e) { - logger.info("date parse exception:" + jsonModified + " " + existingModified); - } - } - element.merge(existing); - return true; - } - - // create new elastic id for all element json, update modified time, modifier (use dummy for now), set _projectId, _refId, _inRefIds - public NodeChangeInfo processPostJson(List elements, boolean overwriteJson, - CommitJson cmjs, NodeService service, String lastCommitId) { - if (lastCommitId != null && !lastCommitId.isEmpty()) { - if (!lastCommitId.equals(getLatestRefCommitId())) { - throw new ConflictException("Given commitId " + lastCommitId + " is not the latest"); - } - } - NodeChangeInfo info = initInfo(elements, cmjs); - - // Logic for update/add - for (ElementJson element : elements) { - if (element == null) { - continue; - } - boolean added = false; - boolean updated = false; - if (element.getId() == null || element.getId().isEmpty()) { - element.setId(UUID.randomUUID().toString()); - } - ElementJson indexElement = info.getExistingElementMap().get(element.getId()); - Node n = info.getExistingNodeMap().get(element.getId()); - if (n == null) { - added = true; - } else if (indexElement == null) { - logger.warn("node db and index mismatch on element update: nodeId: " + n.getNodeId() + ", docId not found: " + n.getDocId()); - //info.addRejection(element.getId(), new Rejection(element, 500, "Update failed: previous element not found")); - //continue; - indexElement = new ElementJson().setId(n.getNodeId()).setDocId(n.getDocId()); - Optional init = commitRepository.findByCommitId(n.getInitialCommit()); - if (init.isPresent()) { - indexElement.setCreator(init.get().getCreator()); - indexElement.setCreated(formatter.format(init.get().getTimestamp())); - } - } - - if (!added) { - if (!overwriteJson) { - if (n.isDeleted() || isUpdated(element, indexElement, info)) { - updated = diffUpdateJson(element, indexElement, info); - } - } else { - updated = true; - String ARTIFACTS = "_artifacts"; - if (indexElement.containsKey(ARTIFACTS) && !element.containsKey(ARTIFACTS)) { - element.put(ARTIFACTS, indexElement.get(ARTIFACTS)); - } - element.setCreated(indexElement.getCreated()); - element.setCreator(indexElement.getCreator()); - } - } - - // create new elastic id for all element json, update modified time, modifier (use dummy for now), set _projectId, _refId, _inRefIds - if (added) { - Node node = new Node(); - processElementAdded(element, node, info); - service.extraProcessPostedElement(element, node, info); - } else if (updated) { - Node node = info.getExistingNodeMap().get(element.getId()); - processElementUpdated(element, node, info); - service.extraProcessPostedElement(element, node, info); - } - } - return info; - } -} diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java b/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java new file mode 100644 index 000000000..4736e3cf2 --- /dev/null +++ b/crud/src/main/java/org/openmbee/mms/crud/services/ProjectDeleteService.java @@ -0,0 +1,62 @@ +package org.openmbee.mms.crud.services; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.dao.ProjectPersistence; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.core.objects.ProjectsResponse; +import org.openmbee.mms.json.ProjectJson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +public class ProjectDeleteService { + + private ProjectPersistence projectPersistence; + protected ObjectMapper om; + + @Autowired + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; + } + + @Autowired + public void setOm(ObjectMapper om) { + this.om = om; + } + + public ProjectsResponse deleteProject(String projectId, boolean hard) { + ProjectsResponse response = new ProjectsResponse(); + ProjectJson projectJson; + ContextHolder.setContext(projectId); + Optional projectJsonOption = projectPersistence.findById(projectId); + + List res = new ArrayList<>(); + + //Do not try to do a soft delete when an error condition is present. + if(projectJsonOption.isEmpty() && !hard) { + throw new NotFoundException("Project state is invalid"); + } + + projectJson = projectJsonOption.orElseGet(() -> { + ProjectJson newProject = new ProjectJson(); + newProject.setProjectId(projectId); + return newProject; + }); + + if(hard){ + projectPersistence.hardDelete(projectId); + } else { + projectPersistence.softDelete(projectId); + } + + projectJson.setIsDeleted(Constants.TRUE); + res.add(projectJson); + return response.setProjects(res); + } +} diff --git a/crud/src/main/java/org/openmbee/mms/crud/services/ServiceFactory.java b/crud/src/main/java/org/openmbee/mms/crud/services/ServiceFactory.java index f6e31fca9..d701f0576 100644 --- a/crud/src/main/java/org/openmbee/mms/crud/services/ServiceFactory.java +++ b/crud/src/main/java/org/openmbee/mms/crud/services/ServiceFactory.java @@ -13,6 +13,7 @@ public class ServiceFactory implements ApplicationContextAware { private ApplicationContext context; + @Override public void setApplicationContext(ApplicationContext context) { this.context = context; } diff --git a/crud/src/test/java/org/openmbee/mms/crud/controllers/branches/BranchesControllerTest.java b/crud/src/test/java/org/openmbee/mms/crud/controllers/branches/BranchesControllerTest.java index 7dbd4eab0..c5addb154 100644 --- a/crud/src/test/java/org/openmbee/mms/crud/controllers/branches/BranchesControllerTest.java +++ b/crud/src/test/java/org/openmbee/mms/crud/controllers/branches/BranchesControllerTest.java @@ -6,7 +6,6 @@ public class BranchesControllerTest { - private void checkFail(String id){ assertFalse(BranchesController.isBranchIdValid(id)); } diff --git a/crud/src/test/java/org/openmbee/mms/crud/services/DefaultCommitServiceTest.java b/crud/src/test/java/org/openmbee/mms/crud/services/DefaultCommitServiceTest.java new file mode 100644 index 000000000..ddb163d4b --- /dev/null +++ b/crud/src/test/java/org/openmbee/mms/crud/services/DefaultCommitServiceTest.java @@ -0,0 +1,350 @@ +package org.openmbee.mms.crud.services; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.openmbee.mms.core.dao.BranchPersistence; +import org.openmbee.mms.core.dao.CommitPersistence; +import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.core.objects.CommitsRequest; +import org.openmbee.mms.core.objects.CommitsResponse; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.RefJson; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +public class DefaultCommitServiceTest { + + @Spy + @InjectMocks + private DefaultCommitService defaultCommitService; + + @Mock + private CommitPersistence commitPersistence; + @Mock + private BranchPersistence branchPersistence; + + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + defaultCommitService.setCommitPersistence(commitPersistence); + defaultCommitService.setBranchPersistence(branchPersistence); + } + + + @Test + public void getRefCommitsBranchNotFound() { + String projectId = "PROJECT_ID"; + String refId = "REF_ID"; + + Map params = new HashMap<>(); + params.put("limit", Integer.toString(1)); + params.put("maxTimestamp", "2026-07-26T12:06:01.072-0400"); + + when(branchPersistence.findById(projectId, refId)).thenReturn(Optional.empty()); + + try { + defaultCommitService.getRefCommits(projectId, refId, params); + } catch (NotFoundException e) { + assertNotNull(e); + } + + resetMocks(); + } + + @Test + public void getRefCommitsBadParams() { + String projectId = "PROJECT_ID"; + String refId = "REF_ID"; + + Map params = new HashMap<>(); + params.put("limit", Integer.toString(1)); + params.put("maxTimestamp", "1776-07-04"); + + when(branchPersistence.findById(projectId, refId)).thenReturn(Optional.empty()); + + try { + defaultCommitService.getRefCommits(projectId, refId, params); + } catch (BadRequestException e) { + assertNotNull(e); + } + + resetMocks(); + } + + + @Test + public void getRefCommits() { + String projectId = "PROJECT_ID"; + String refId = "REF_ID"; + + Map params = new HashMap<>(); + params.put("limit", Integer.toString(1)); + params.put("maxTimestamp", "2026-07-26T12:06:01.072-0400"); + + CommitJson testCommit = new CommitJson(); + testCommit.setCommitId("TEST_COMMIT_ID"); + testCommit.setCreator("TEST_CREATOR"); + testCommit.setCreated("TEST_CREATED"); + testCommit.setDocId("TEST_DOC_ID"); + testCommit.setComment("TEST_COMMENT"); + + List testCommitList = new ArrayList(); + testCommitList.add(testCommit); + + RefJson refJson = new RefJson(); + when(branchPersistence.findById(projectId, refId)).thenReturn(Optional.of(refJson)); + + when(commitPersistence.findByProjectAndRefAndTimestampAndLimit(eq(projectId), eq(refId), any(), anyInt())).thenReturn(testCommitList); + + CommitsResponse response = defaultCommitService.getRefCommits(projectId, refId, params); + + assertFalse(response.getCommits().isEmpty()); + + resetMocks(); + } + + @Test + public void getCommitCommitNotFound() { + String projectId = "PROJECT_ID"; + String commitId = "COMMIT_ID"; + + when(commitPersistence.findById(projectId, commitId)).thenReturn(Optional.empty()); + + try { + defaultCommitService.getCommit(projectId, commitId); + } catch (NotFoundException e) { + assertNotNull(e); + } + + resetMocks(); + } + + @Test + public void getCommit() { + String projectId = "PROJECT_ID"; + String commitId = "COMMIT_ID"; + + CommitJson testCommit = new CommitJson(); + testCommit.setCommitId(commitId); + + when(commitPersistence.findById(projectId, commitId)).thenReturn(Optional.of(testCommit)); + + CommitsResponse response = defaultCommitService.getCommit(projectId, commitId); + + assertFalse(response.getCommits().isEmpty()); + + resetMocks(); + } + + @Test + public void getElementCommitsBranchNotFound() { + String projectId = "PROJECT_ID"; + String refId = "REF_ID"; + String elementId = "ELEMENT_ID"; + + Map params = new HashMap<>(); + params.put("limit", Integer.toString(1)); + params.put("maxTimestamp", "2026-07-26T12:06:01.072-0400"); + + when(branchPersistence.findById(projectId, refId)).thenReturn(Optional.empty()); + + try { + defaultCommitService.getElementCommits(projectId, refId, elementId, params); + } catch (NotFoundException e) { + assertNotNull(e); + } + + resetMocks(); + } + + @Test + public void getElementCommitsCommitsNotFound() { + String projectId = "PROJECT_ID"; + String refId = "REF_ID"; + String elementId = "ELEMENT_ID"; + + Map params = new HashMap<>(); + params.put("limit", Integer.toString(1)); + params.put("maxTimestamp", "2026-07-26T12:06:01.072-0400"); + + RefJson refJson = new RefJson(); + when(branchPersistence.findById(projectId, refId)).thenReturn(Optional.of(refJson)); + + when(commitPersistence.findByProjectAndRefAndTimestampAndLimit(projectId, refId, null, 0)).thenReturn(new ArrayList<>()); + when(commitPersistence.elementHistory(eq(projectId), eq(elementId), any())).thenReturn(new ArrayList<>()); + + CommitsResponse response = defaultCommitService.getElementCommits(projectId, refId, elementId, params); + + assertTrue(response.getCommits().isEmpty()); + + resetMocks(); + } + + @Test + public void getElementCommits() { + String projectId = "PROJECT_ID"; + String refId = "REF_ID"; + String elementId = "ELEMENT_ID"; + + Map params = new HashMap<>(); + params.put("limit", Integer.toString(1)); + params.put("maxTimestamp", "2026-07-26T12:06:01.072-0400"); + + CommitJson testCommit = new CommitJson(); + testCommit.setCommitId("TEST_COMMIT_ID"); + testCommit.setCreator("TEST_CREATOR"); + testCommit.setCreated("TEST_CREATED"); + testCommit.setDocId("TEST_DOC_ID"); + testCommit.setComment("TEST_COMMENT"); + + List testCommitList = new ArrayList(); + testCommitList.add(testCommit); + + RefJson refJson = new RefJson(); + when(branchPersistence.findById(projectId, refId)).thenReturn(Optional.of(refJson)); + + when(commitPersistence.findByProjectAndRefAndTimestampAndLimit(projectId, refId, null, 0)).thenReturn(testCommitList); + when(commitPersistence.elementHistory(eq(projectId), eq(elementId), any())).thenReturn(testCommitList); + + CommitsResponse response = defaultCommitService.getElementCommits(projectId, refId, elementId, params); + + assertFalse(response.getCommits().isEmpty()); + + resetMocks(); + } + + @Test + public void getCommitsThrowsError() { + String projectId = "PROJECT_ID"; + + CommitJson testCommit = new CommitJson(); + testCommit.setCommitId("TEST_COMMIT_ID"); + testCommit.setCreator("TEST_CREATOR"); + testCommit.setCreated("TEST_CREATED"); + testCommit.setDocId("TEST_DOC_ID"); + testCommit.setComment("TEST_COMMENT"); + + List testCommitList = new ArrayList(); + testCommitList.add(testCommit); + + CommitsRequest req = new CommitsRequest(); + req.setCommits(testCommitList); + + when(commitPersistence.findAllById(eq(projectId), any())).thenThrow(InternalErrorException.class); + + try { + defaultCommitService.getCommits(projectId, req); + } catch (InternalErrorException e) { + assertNotNull(e); + } + + resetMocks(); + } + + + @Test + public void getCommits() { + String projectId = "PROJECT_ID"; + + CommitJson testCommit = new CommitJson(); + testCommit.setCommitId("TEST_COMMIT_ID"); + testCommit.setCreator("TEST_CREATOR"); + testCommit.setCreated("TEST_CREATED"); + testCommit.setDocId("TEST_DOC_ID"); + testCommit.setComment("TEST_COMMENT"); + + List testCommitList = new ArrayList(); + testCommitList.add(testCommit); + + CommitsRequest req = new CommitsRequest(); + req.setCommits(testCommitList); + + when(commitPersistence.findAllById(eq(projectId), any())).thenReturn(testCommitList); + + CommitsResponse response = defaultCommitService.getCommits(projectId, req); + + assertFalse(response.getCommits().isEmpty()); + + resetMocks(); + } + + + @Test + public void getCommitsCommitsNotFound() { + String projectId = "PROJECT_ID"; + + List testCommitList = new ArrayList(); + CommitsRequest req = new CommitsRequest(); + req.setCommits(testCommitList); + + when(commitPersistence.findAllById(eq(projectId), any())).thenReturn(new ArrayList<>()); + + try { + defaultCommitService.getCommits(projectId, req); + } catch (Exception e) { + assertNotNull(e); + } + } + + @Test + public void isProjectNewCommitsIsEmpty() { + String projectId = "PROJECT_ID"; + + when(commitPersistence.findAllByProjectId(projectId)).thenReturn(new ArrayList<>()); + + assertTrue(defaultCommitService.isProjectNew(projectId)); + + resetMocks(); + } + + @Test + public void isProjectNewCommitsIsNull() { + String projectId = "PROJECT_ID"; + + when(commitPersistence.findAllByProjectId(projectId)).thenReturn(null); + + assertTrue(defaultCommitService.isProjectNew(projectId)); + + resetMocks(); + } + + @Test + public void isProjectNew() { + String projectId = "PROJECT_ID"; + + CommitJson testCommit = new CommitJson(); + testCommit.setCommitId("TEST_COMMIT_ID"); + testCommit.setCreator("TEST_CREATOR"); + testCommit.setCreated("TEST_CREATED"); + testCommit.setDocId("TEST_DOC_ID"); + testCommit.setComment("TEST_COMMENT"); + + List testCommitList = new ArrayList(); + testCommitList.add(testCommit); + + when(commitPersistence.findAllByProjectId(projectId)).thenReturn(testCommitList); + + assertFalse(defaultCommitService.isProjectNew(projectId)); + + resetMocks(); + } + + public void resetMocks() { + reset(branchPersistence); + reset(commitPersistence); + } +} diff --git a/data/data.gradle b/data/data.gradle index ee1f7eaa4..cce0e85d9 100644 --- a/data/data.gradle +++ b/data/data.gradle @@ -1,4 +1,7 @@ dependencies { + implementation project(':json') + implementation project(':core') + api commonDependencies.'spring-security-core' api commonDependencies.'spring-data-commons' diff --git a/core/src/main/java/org/openmbee/mms/core/dao/BranchDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/BranchDAO.java similarity index 89% rename from core/src/main/java/org/openmbee/mms/core/dao/BranchDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/BranchDAO.java index 5b8c8bebb..5f055a69e 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/BranchDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/BranchDAO.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/org/openmbee/mms/core/dao/BranchGDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/BranchGDAO.java similarity index 87% rename from core/src/main/java/org/openmbee/mms/core/dao/BranchGDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/BranchGDAO.java index 5f76f5700..e8d656fe3 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/BranchGDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/BranchGDAO.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.Optional; import org.openmbee.mms.data.domains.global.Branch; diff --git a/core/src/main/java/org/openmbee/mms/core/dao/BranchIndexDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/BranchIndexDAO.java similarity index 94% rename from core/src/main/java/org/openmbee/mms/core/dao/BranchIndexDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/BranchIndexDAO.java index 125c4e977..99e1165b8 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/BranchIndexDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/BranchIndexDAO.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.Collection; import java.util.List; diff --git a/core/src/main/java/org/openmbee/mms/core/dao/CommitDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/CommitDAO.java similarity index 94% rename from core/src/main/java/org/openmbee/mms/core/dao/CommitDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/CommitDAO.java index 938e872ea..870da6cf0 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/CommitDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/CommitDAO.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.time.Instant; import java.util.List; diff --git a/core/src/main/java/org/openmbee/mms/core/dao/CommitIndexDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/CommitIndexDAO.java similarity index 95% rename from core/src/main/java/org/openmbee/mms/core/dao/CommitIndexDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/CommitIndexDAO.java index 41d91fb04..d8c86a79b 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/CommitIndexDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/CommitIndexDAO.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.Collection; import java.util.List; diff --git a/core/src/main/java/org/openmbee/mms/core/dao/NodeDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/NodeDAO.java similarity index 89% rename from core/src/main/java/org/openmbee/mms/core/dao/NodeDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/NodeDAO.java index 478582103..d28040b6f 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/NodeDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/NodeDAO.java @@ -1,9 +1,10 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.Collection; import java.util.List; import java.util.Optional; +import org.openmbee.mms.core.dao.BaseDAO; import org.openmbee.mms.data.domains.scoped.Node; public interface NodeDAO extends BaseDAO { diff --git a/core/src/main/java/org/openmbee/mms/core/dao/NodeIndexDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/NodeIndexDAO.java similarity index 95% rename from core/src/main/java/org/openmbee/mms/core/dao/NodeIndexDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/NodeIndexDAO.java index 8d14d5fed..2bfb25ddb 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/NodeIndexDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/NodeIndexDAO.java @@ -1,10 +1,9 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; - import org.openmbee.mms.json.BaseJson; import org.openmbee.mms.json.ElementJson; diff --git a/core/src/main/java/org/openmbee/mms/core/dao/OrgDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/OrgDAO.java similarity index 91% rename from core/src/main/java/org/openmbee/mms/core/dao/OrgDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/OrgDAO.java index 4dd579bc7..1c2d0e0ca 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/OrgDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/OrgDAO.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/org/openmbee/mms/core/dao/ProjectDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/ProjectDAO.java similarity index 75% rename from core/src/main/java/org/openmbee/mms/core/dao/ProjectDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/ProjectDAO.java index 244f543c9..e3f791727 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/ProjectDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/ProjectDAO.java @@ -1,20 +1,22 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.List; import java.util.Optional; import org.openmbee.mms.data.domains.global.Project; +import javax.transaction.Transactional; + public interface ProjectDAO { Optional findByProjectId(String id); Optional findByProjectName(String name); + List findAllByOrgId(String id); + Project save(Project p); - void delete(Project p); + void delete(String projectId); List findAll(); - - List findAllByOrgId(String id); } diff --git a/core/src/main/java/org/openmbee/mms/core/dao/ProjectIndex.java b/data/src/main/java/org/openmbee/mms/data/dao/ProjectIndex.java similarity index 92% rename from core/src/main/java/org/openmbee/mms/core/dao/ProjectIndex.java rename to data/src/main/java/org/openmbee/mms/data/dao/ProjectIndex.java index 5461c6300..009db5f69 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/ProjectIndex.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/ProjectIndex.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import java.util.List; import java.util.Optional; diff --git a/core/src/main/java/org/openmbee/mms/core/dao/WebhookDAO.java b/data/src/main/java/org/openmbee/mms/data/dao/WebhookDAO.java similarity index 92% rename from core/src/main/java/org/openmbee/mms/core/dao/WebhookDAO.java rename to data/src/main/java/org/openmbee/mms/data/dao/WebhookDAO.java index b9e0957e3..fe44ed1e5 100644 --- a/core/src/main/java/org/openmbee/mms/core/dao/WebhookDAO.java +++ b/data/src/main/java/org/openmbee/mms/data/dao/WebhookDAO.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.core.dao; +package org.openmbee.mms.data.dao; import org.openmbee.mms.data.domains.global.Webhook; diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java b/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java index 21dd2d860..9766154af 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/Group.java @@ -10,6 +10,7 @@ @Table(name = "groups") public class Group extends Base { + public static final String NAME_COLUMN = "name"; @Column(unique = true) private String name; diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/Privilege.java b/data/src/main/java/org/openmbee/mms/data/domains/global/Privilege.java index aed832ece..c2bbd1b64 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/Privilege.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/Privilege.java @@ -1,10 +1,7 @@ package org.openmbee.mms.data.domains.global; import java.util.Set; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.ManyToMany; -import javax.persistence.Table; +import javax.persistence.*; @Entity @Table(name = "privileges") @@ -13,7 +10,7 @@ public class Privilege extends Base { @Column(unique = true) private String name; - @ManyToMany(mappedBy = "privileges") + @ManyToMany(mappedBy = "privileges", fetch = FetchType.EAGER) private Set roles; public Privilege() { diff --git a/data/src/main/java/org/openmbee/mms/data/domains/global/User.java b/data/src/main/java/org/openmbee/mms/data/domains/global/User.java index 549a4bf81..e25bc5992 100644 --- a/data/src/main/java/org/openmbee/mms/data/domains/global/User.java +++ b/data/src/main/java/org/openmbee/mms/data/domains/global/User.java @@ -2,12 +2,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Collection; import javax.persistence.*; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; - @JsonInclude(JsonInclude.Include.NON_EMPTY) @Entity @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "username"), @@ -20,7 +17,6 @@ public class User extends Base { private String lastName; private boolean admin; - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String password; private boolean enabled; @@ -77,22 +73,6 @@ public void setPassword(String password) { this.password = password; } - public void encodePassword(String password) { - BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder(); - this.password = bcrypt.encode(password); - } - - public void updatePassword(String old, String newPass1, String newPass2) { - BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder(); - if (!password.equals(bcrypt.encode(old))) { - throw new IllegalArgumentException("Existing Password invalid"); - } - if (!newPass1.equals(newPass2)) { - throw new IllegalArgumentException("New Passwords don't match"); - } - this.password = bcrypt.encode(newPass1); - } - public String getFirstName() { return firstName; } diff --git a/elastic/elastic.gradle b/elastic/elastic.gradle index 4147846ef..5e0f73c22 100644 --- a/elastic/elastic.gradle +++ b/elastic/elastic.gradle @@ -1,8 +1,15 @@ dependencies { implementation project(':core') + implementation project(':data') implementation project(':search') implementation "org.elasticsearch.client:elasticsearch-rest-high-level-client:$elasticVersion" testImplementation commonDependencies.'spring-boot-starter-test' } + +tasks { + processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} \ No newline at end of file diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/BranchElasticDAOImpl.java b/elastic/src/main/java/org/openmbee/mms/elastic/BranchElasticDAOImpl.java index 83ccb3e9b..c78fe68bd 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/BranchElasticDAOImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/BranchElasticDAOImpl.java @@ -6,7 +6,7 @@ import java.util.Set; import java.util.UUID; -import org.openmbee.mms.core.dao.BranchIndexDAO; +import org.openmbee.mms.data.dao.BranchIndexDAO; import org.openmbee.mms.json.BaseJson; import org.openmbee.mms.json.RefJson; import org.springframework.stereotype.Component; 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 7c760023a..2be63696b 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/CommitElasticDAOImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/CommitElasticDAOImpl.java @@ -6,7 +6,6 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.client.RequestOptions; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; @@ -14,7 +13,7 @@ import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortOrder; -import org.openmbee.mms.core.dao.CommitIndexDAO; +import org.openmbee.mms.data.dao.CommitIndexDAO; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.elastic.utils.Index; import org.openmbee.mms.json.BaseJson; @@ -201,14 +200,18 @@ private List getDocs(String commitId) { 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" + if (hits.getTotalHits().value > 0) { + for (SearchHit hit : hits.getHits()) { + CommitJson ob = new CommitJson(); + ob.putAll(hit.getSourceAsMap()); + rawCommits.add(ob); // gets "_source" + } + } + if (rawCommits.isEmpty()) { + //try getting directly using id + Optional c = super.findById(this.getIndex(), commitId); + c.ifPresent(commit -> rawCommits.add(commit)); } return rawCommits; } catch (IOException ioe) { 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 15d82b963..d1d150c8b 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/NodeElasticDAOImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/NodeElasticDAOImpl.java @@ -10,7 +10,6 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.client.RequestOptions; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.script.Script; @@ -20,7 +19,7 @@ import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortOrder; import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.dao.NodeIndexDAO; +import org.openmbee.mms.data.dao.NodeIndexDAO; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.elastic.utils.BulkProcessor; import org.openmbee.mms.elastic.utils.Index; diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java b/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java index ccfc173f2..1d4a1d18f 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/ProjectElasticImpl.java @@ -13,7 +13,7 @@ import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.xcontent.XContentType; import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.dao.ProjectIndex; +import org.openmbee.mms.data.dao.ProjectIndex; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.json.ProjectJson; import org.springframework.beans.factory.annotation.Autowired; diff --git a/elastic/src/main/java/org/openmbee/mms/elastic/services/ElasticSearchService.java b/elastic/src/main/java/org/openmbee/mms/elastic/services/ElasticSearchService.java index 74bb12b70..9b407af27 100644 --- a/elastic/src/main/java/org/openmbee/mms/elastic/services/ElasticSearchService.java +++ b/elastic/src/main/java/org/openmbee/mms/elastic/services/ElasticSearchService.java @@ -18,8 +18,9 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.openmbee.mms.core.config.Constants; import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.dao.NodeDAO; +import org.openmbee.mms.data.dao.NodeDAO; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.objects.ElementsSearchResponse; import org.openmbee.mms.core.objects.Rejection; @@ -100,7 +101,7 @@ public ElementsSearchResponse recursiveSearch(String projectId, String refId, Ma boolean showDeletedAsRejected = false; String showDeleted = params.remove(SearchConstants.SHOW_DELETED_FIELD); - if(showDeleted != null && showDeleted.equals("true")) { + if(showDeleted != null && showDeleted.equals(Constants.TRUE)) { showDeletedAsRejected = true; } diff --git a/elastic/src/main/resources/elastic_mappings/msosa_node.json b/elastic/src/main/resources/elastic_mappings/msosa_node.json new file mode 100644 index 000000000..515df203c --- /dev/null +++ b/elastic/src/main/resources/elastic_mappings/msosa_node.json @@ -0,0 +1,110 @@ +{ + + "dynamic_templates": [ + { + "id_as_keywords": { + "match_mapping_type": "string", + "match_pattern": "regex", + "match": ".*(Id|Ids)", + "mapping": { + "type": "keyword" + } + } + }, + { + "id_and_type": { + "match_mapping_type": "string", + "match_pattern": "regex", + "match": "(id|ids|type|uri|URI)", + "mapping": { + "type": "keyword" + } + } + }, + { + "boolean": { + "match_mapping_type": "*", + "match_pattern": "regex", + "match": "is[A-Z].*", + "mapping": { + "type": "boolean" + } + } + }, + { + "text": { + "match_mapping_type": "string", + "match_pattern": "regex", + "match": "(body|documentation)", + "mapping": { + "type": "text" + } + } + } + ], + "properties": { + "aggregation": { + "type": "keyword" + }, + "visibility": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + }, + "ordering": { + "type": "keyword" + }, + "concurrency": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + }, + "mode": { + "type": "keyword" + }, + "interactionOperator": { + "type": "keyword" + }, + "messageKind": { + "type": "keyword" + }, + "messageSort": { + "type": "keyword" + }, + "effect": { + "type": "keyword" + }, + "mustIsolate": { + "type": "boolean" + }, + "_creator": { + "type": "keyword" + }, + "_modifier": { + "type": "keyword" + }, + "_qualifiedName": { + "type": "keyword" + }, + "_qualifiedId": { + "type": "keyword" + }, + "_created": { + "type": "date", + "format": "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + }, + "_modified": { + "type": "date", + "format": "yyyy-MM-dd'T'HH:mm:ss.SSSZ" + }, + "_docId": { + "type": "keyword" + }, + "value" : { + "type": "text" + } + } + +} \ No newline at end of file diff --git a/example/example.gradle b/example/example.gradle index 5eca0e129..e25098d06 100644 --- a/example/example.gradle +++ b/example/example.gradle @@ -26,6 +26,9 @@ dependencies { project(':search'), project(':storage'), project(':groups'), + project(':view'), + project(':msosa'), + project(':federatedpersistence'), 'org.springframework.boot:spring-boot-starter-web', 'org.postgresql:postgresql:42.2.5', //'mysql:mysql-connector-java:8.0.17', diff --git a/example/makeBranchFromCommit.postman_collection.json b/example/makeBranchFromCommit.postman_collection.json index 817a46cf2..6d863f1a7 100644 --- a/example/makeBranchFromCommit.postman_collection.json +++ b/example/makeBranchFromCommit.postman_collection.json @@ -285,7 +285,7 @@ "response": [] }, { - "name": "delete a in refa", + "name": "delete a in master", "event": [ { "listen": "test", @@ -329,7 +329,7 @@ "response": [] }, { - "name": "update b in refa", + "name": "update b in master", "event": [ { "listen": "test", @@ -381,7 +381,7 @@ "response": [] }, { - "name": "delete c in refa", + "name": "delete c in master", "event": [ { "listen": "test", @@ -433,7 +433,7 @@ "response": [] }, { - "name": "add e to refa", + "name": "add e to master", "event": [ { "listen": "test", @@ -480,7 +480,7 @@ "response": [] }, { - "name": "recurrect c in refa", + "name": "recurrect c in master", "event": [ { "listen": "test", @@ -527,7 +527,7 @@ "response": [] }, { - "name": "create branch from \"add c to master\"", + "name": "create branch from \"add d to master\"", "event": [ { "listen": "test", @@ -535,7 +535,7 @@ "exec": [ "pm.test(\"branch created with right parentRef and commit id\", function () {", " var jsonData = pm.response.json();", - " pm.expect(jsonData.refs[0].id).to.eql('addCCommitId_branch');", + " pm.expect(jsonData.refs[0].id).to.eql('addDCommitId_branch');", " pm.expect(jsonData.refs[0].parentRefId).to.eql('master');", "});" ], @@ -554,7 +554,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"refs\": [\n\t\t{\n\t\t\t\"id\": \"addCCommitId_branch\",\n\t\t\t\"name\": \"addCCommitId_branch\",\n\t\t\t\"type\": \"Branch\",\n\t\t\t\"parentCommitId\": \"{{addCCommitId}}\"\n\t\t}\n\t]\n}" + "raw": "{\n\t\"refs\": [\n\t\t{\n\t\t\t\"id\": \"addDCommitId_branch\",\n\t\t\t\"name\": \"addDCommitId_branch\",\n\t\t\t\"type\": \"Branch\",\n\t\t\t\"parentCommitId\": \"{{addDCommitId}}\"\n\t\t}\n\t]\n}" }, "url": { "raw": "{{host}}/projects/branch_from_commit/refs", @@ -571,19 +571,191 @@ "response": [] }, { - "name": "get elements from branch from \"add c to master\"", + "name": "create branch from \"delete c\"", "event": [ { "listen": "test", "script": { "exec": [ - "pm.test(\"response has a,b,c\", function () {", + "pm.test(\"branch created with right parentRef and commit id\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.refs[0].id).to.eql('deleteCCommitId_branch');", + " pm.expect(jsonData.refs[0].parentRefId).to.eql('master');", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"refs\": [\n\t\t{\n\t\t\t\"id\": \"deleteCCommitId_branch\",\n\t\t\t\"name\": \"deleteCCommitId_branch\",\n\t\t\t\"type\": \"Branch\",\n\t\t\t\"parentCommitId\": \"{{deleteCCommitId}}\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs" + ] + } + }, + "response": [] + }, + { + "name": "create branch from \"resurrect c\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"branch created with right parentRef and commit id\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.refs[0].id).to.eql('resurrectCCommitId_branch');", + " pm.expect(jsonData.refs[0].parentRefId).to.eql('master');", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"refs\": [\n\t\t{\n\t\t\t\"id\": \"resurrectCCommitId_branch\",\n\t\t\t\"name\": \"resurrectCCommitId_branch\",\n\t\t\t\"type\": \"Branch\",\n\t\t\t\"parentCommitId\": \"{{resurrectCCommitId}}\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs" + ] + } + }, + "response": [] + }, + { + "name": "try to change parentCommitId on existing branch", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"400 response\", function () {", + " pm.response.to.have.status(400);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"refs\": [\n\t\t{\n\t\t\t\"id\": \"resurrectCCommitId_branch\",\n\t\t\t\"name\": \"resurrectCCommitId_branch\",\n\t\t\t\"type\": \"Branch\",\n\t\t\t\"parentCommitId\": \"{{deleteCCommitId}}\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs" + ] + } + }, + "response": [] + }, + { + "name": "add more metadata to existing branch", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"200 response\", function () {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"refs\": [\n\t\t{\n\t\t\t\"id\": \"resurrectCCommitId_branch\",\n\t\t\t\"name\": \"resurrectCCommitId_branch\",\n\t\t\t\"type\": \"Branch\",\n\t\t\t\"newAttribute\": \"new data\"\n\t\t}\n\t]\n}" + }, + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs" + ] + } + }, + "response": [] + }, + { + "name": "get elements from branch from \"add d to master\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has a,b,c,d\", function () {", " var jsonData = pm.response.json();", "", - " pm.expect(jsonData.elements.length).to.eql(3);", + " pm.expect(jsonData.elements.length).to.eql(4);", " var result = jsonData.elements.map(e => ({id: e.id}));", - " pm.expect(result).to.deep.have.members([{id: 'a'}, {id: 'b'}, {id: 'c'}]);", - " pm.expect(jsonData.commitId).to.eql(pm.environment.get('addCCommitId'));", + " pm.expect(result).to.deep.have.members([{id: 'a'}, {id: 'b'}, {id: 'c'}, {id: 'd'}]);", + " pm.expect(jsonData.commitId).to.eql(pm.environment.get('addDCommitId'));", " ", "})", "", @@ -603,7 +775,7 @@ } ], "url": { - "raw": "{{host}}/projects/branch_from_commit/refs/addCCommitId_branch/elements", + "raw": "{{host}}/projects/branch_from_commit/refs/addDCommitId_branch/elements", "host": [ "{{host}}" ], @@ -611,12 +783,213 @@ "projects", "branch_from_commit", "refs", - "addCCommitId_branch", + "addDCommitId_branch", "elements" ] } }, "response": [] + }, + { + "name": "get elements from branch from \"delete c\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has b,d\", function () {", + " var jsonData = pm.response.json();", + "", + " pm.expect(jsonData.elements.length).to.eql(2);", + " var result = jsonData.elements.map(e => ({id: e.id, name: e.name}));", + " pm.expect(result).to.deep.have.members([{id: 'b', name: 'b updated'}, {id: 'd', name: 'd'}]);", + " pm.expect(jsonData.commitId).to.eql(pm.environment.get('deleteCCommitId'));", + " ", + "})", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/deleteCCommitId_branch/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "deleteCCommitId_branch", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "get elements from branch from \"resurrect c\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has b,c,d,e\", function () {", + " var jsonData = pm.response.json();", + "", + " pm.expect(jsonData.elements.length).to.eql(4);", + " var result = jsonData.elements.map(e => ({id: e.id}));", + " pm.expect(result).to.deep.have.members([{id: 'b'}, {id: 'c'}, {id: 'd'}, {id: 'e'}]);", + " pm.expect(jsonData.commitId).to.eql(pm.environment.get('resurrectCCommitId'));", + " ", + "})", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/resurrectCCommitId_branch/elements", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "resurrectCCommitId_branch", + "elements" + ] + } + }, + "response": [] + }, + { + "name": "get commits from \"add d to master\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has right commit history\", function () {", + " var jsonData = pm.response.json();", + "", + " pm.expect(jsonData.commits.length).to.eql(3);", + " var result = jsonData.commits.map(e => ({id: e.id}));", + " pm.expect(result).to.deep.include.ordered.members([", + " {id: pm.environment.get('addDCommitId')}, ", + " {id: pm.environment.get('addCCommitId')}, ", + " {id: pm.environment.get('addABCommitId')}", + " ]);", + " ", + "})", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/addDCommitId_branch/commits", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "addDCommitId_branch", + "commits" + ] + } + }, + "response": [] + }, + { + "name": "get commits from \"delete c\"", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"response has right commit history\", function () {", + " var jsonData = pm.response.json();", + "", + " pm.expect(jsonData.commits.length).to.eql(6);", + " var result = jsonData.commits.map(e => ({id: e.id}));", + " pm.expect(result).to.deep.include.ordered.members([", + " {id: pm.environment.get('deleteCCommitId')},", + " {id: pm.environment.get('updateBCommitId')},", + " {id: pm.environment.get('deleteACommitId')},", + " {id: pm.environment.get('addDCommitId')}, ", + " {id: pm.environment.get('addCCommitId')}, ", + " {id: pm.environment.get('addABCommitId')},", + " ]);", + "", + "})", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "{{host}}/projects/branch_from_commit/refs/deleteCCommitId_branch/commits", + "host": [ + "{{host}}" + ], + "path": [ + "projects", + "branch_from_commit", + "refs", + "deleteCCommitId_branch", + "commits" + ] + } + }, + "response": [] } ], "auth": { diff --git a/federatedpersistence/README.md b/federatedpersistence/README.md new file mode 100644 index 000000000..7851fbe41 --- /dev/null +++ b/federatedpersistence/README.md @@ -0,0 +1,3 @@ +## Federated Persistence + +This module implements the federated persistence model diff --git a/federatedpersistence/federatedpersistence.gradle b/federatedpersistence/federatedpersistence.gradle new file mode 100644 index 000000000..1f4d5bd15 --- /dev/null +++ b/federatedpersistence/federatedpersistence.gradle @@ -0,0 +1,11 @@ +dependencies { + implementation project(':core') + implementation project(':crud') + implementation project(':data') + implementation project(':rdb') + implementation project(':webhooks') + implementation 'org.apache.commons:commons-lang3:3.10' + implementation commonDependencies.'spring-boot' + + testImplementation commonDependencies.'spring-boot-starter-test' +} diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/config/DefaultPermissionsDelegateConfig.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/DefaultPermissionsDelegateConfig.java similarity index 64% rename from permissions/src/main/java/org/openmbee/mms/permissions/config/DefaultPermissionsDelegateConfig.java rename to federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/DefaultPermissionsDelegateConfig.java index f4f00f365..b1e1d3d3a 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/config/DefaultPermissionsDelegateConfig.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/DefaultPermissionsDelegateConfig.java @@ -1,7 +1,7 @@ -package org.openmbee.mms.permissions.config; +package org.openmbee.mms.federatedpersistence.config; import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; -import org.openmbee.mms.permissions.delegation.DefaultPermissionsDelegateFactory; +import org.openmbee.mms.federatedpersistence.permissions.DefaultFederatedPermissionsDelegateFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -12,7 +12,7 @@ public class DefaultPermissionsDelegateConfig { @Bean @Order(0) public PermissionsDelegateFactory permissionsDelegateFactory(){ - return new DefaultPermissionsDelegateFactory(); + return new DefaultFederatedPermissionsDelegateFactory(); } } diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/config/PermissionInit.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/PermissionInit.java similarity index 98% rename from permissions/src/main/java/org/openmbee/mms/permissions/config/PermissionInit.java rename to federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/PermissionInit.java index e2eff178b..caa3cd062 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/config/PermissionInit.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/config/PermissionInit.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.permissions.config; +package org.openmbee.mms.federatedpersistence.config; import org.openmbee.mms.data.domains.global.Group; import org.openmbee.mms.data.domains.global.Privilege; diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java new file mode 100644 index 000000000..bfc586066 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedBranchPersistence.java @@ -0,0 +1,161 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.config.Formats; +import org.openmbee.mms.core.dao.*; +import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.core.exceptions.DeletedException; +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.core.objects.RefsResponse; +import org.openmbee.mms.data.dao.*; +import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.data.domains.scoped.Branch; +import org.openmbee.mms.data.domains.scoped.Commit; +import org.openmbee.mms.json.RefJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; + +@Component +public class FederatedBranchPersistence implements BranchPersistence { + private static Logger logger = LoggerFactory.getLogger(FederatedBranchPersistence.class); + + private BranchDAO branchDAO; + private BranchGDAO branchGDAO; + private BranchIndexDAO branchIndexDAO; + private CommitDAO commitDAO; + private ProjectDAO projectDAO; + + @Autowired + public FederatedBranchPersistence(BranchDAO branchDAO, BranchGDAO branchGDAO, BranchIndexDAO branchIndexDAO, CommitDAO commitDAO, ProjectDAO projectDAO) { + this.branchDAO = branchDAO; + this.branchGDAO = branchGDAO; + this.branchIndexDAO = branchIndexDAO; + this.commitDAO = commitDAO; + this.projectDAO = projectDAO; + } + + @Override + public RefJson save(RefJson refJson) { + //Master branch special case + boolean isMasterBranch = refJson.getId().equals(Constants.MASTER_BRANCH); + + //Fill in docId + if (refJson.getDocId() == null || refJson.getDocId().isEmpty()) { + refJson.setDocId(branchIndexDAO.createDocId(refJson)); + } + + //Setup scoped Branch object + Branch scopedBranch = new Branch(); + scopedBranch.setBranchId(refJson.getId()); + scopedBranch.setBranchName(refJson.getName()); + scopedBranch.setDescription(refJson.getDescription()); + scopedBranch.setTag(refJson.isTag()); + scopedBranch.setTimestamp(Formats.FORMATTER.parse(refJson.getCreated(), Instant::from)); + scopedBranch.setParentRefId(refJson.getParentRefId()); + scopedBranch.setDocId(refJson.getDocId()); + + //Setup global Branch object + Optional project = projectDAO.findByProjectId(refJson.getProjectId()); + if(project.isEmpty()) { + throw new NotFoundException("project not found"); + } + org.openmbee.mms.data.domains.global.Branch globalBranch = new org.openmbee.mms.data.domains.global.Branch(); + globalBranch.setProject(project.get()); + globalBranch.setBranchId(refJson.getId()); + globalBranch.setInherit(true); + + //Master branch case. Can skip parent branch and parent commit check + if (!isMasterBranch) { + //Validate parent branch + Optional refOption = branchDAO.findByBranchId(scopedBranch.getParentRefId()); + if (!refOption.isPresent()) { + throw new InternalErrorException("Cannot determine parent branch."); + } + + //Validate parent commit + Optional parentCommit = commitDAO.findByCommitId(refJson.getParentCommitId()); + if (!parentCommit.isPresent()) { + throw new InternalErrorException("Cannot determine parent commit."); + } + parentCommit.ifPresent(parent -> scopedBranch.setParentCommit(parent.getId())); + } + + //DO save + ContextHolder.setContext(null); + branchGDAO.save(globalBranch); + + String projectId = refJson.getProjectId(); + ContextHolder.setContext(projectId); + branchIndexDAO.update(refJson); + branchDAO.save(scopedBranch); + return refJson; + } + + @Override + public RefJson update(RefJson refJson) { + ContextHolder.setContext(refJson.getProjectId()); + Optional existing = branchDAO.findByBranchId(refJson.getId()); + existing.get().setDeleted(refJson.isDeleted()); + branchDAO.save(existing.get()); + branchIndexDAO.update(refJson); + return refJson; + } + + @Override + public List findAll(String projectId) { + ContextHolder.setContext(projectId); + List branches = branchDAO.findAll(); + Set docIds = new HashSet<>(); + branches.forEach(branch -> docIds.add(branch.getDocId())); + return branchIndexDAO.findAllById(docIds); + } + + @Override + public Optional findById(String projectId, String refId) { + ContextHolder.setContext(projectId); + Optional branchesOption = this.branchDAO.findByBranchId(refId); + if (!branchesOption.isPresent()) { + return Optional.empty(); + } + Branch b = branchesOption.get(); + if (b.isDeleted()) { + throw new DeletedException(new RefsResponse()); + } + Optional refOption = branchIndexDAO.findById(b.getDocId()); + if (!refOption.isPresent()) { + logger.error("Federated data inconsistency: JSON Not found for {} with docId: {}", + b.getBranchId(), b.getDocId()); + throw new NotFoundException(new RefsResponse()); + } + return refOption; + } + + @Override + public Optional deleteById(String projectId, String refId) { + ContextHolder.setContext(projectId); + Optional branch = this.branchDAO.findByBranchId(refId); + if (!branch.isPresent()) { + return Optional.empty(); + } + Branch b = branch.get(); + b.setDeleted(true); + branchDAO.save(b); + RefJson refJson = new RefJson().setDocId(b.getDocId()).setDeleted(true) + .setProjectId(projectId).setId(refId); + return Optional.of(branchIndexDAO.update(refJson)); + } + + @Override + public boolean inheritsPermissions(String projectId, String branchId) { + Optional branch = + branchGDAO.findByProject_ProjectIdAndBranchId(projectId, branchId); + return branch.map(org.openmbee.mms.data.domains.global.Branch::isInherit).orElse(false); + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedCommitPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedCommitPersistence.java new file mode 100644 index 000000000..9b3abc81d --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedCommitPersistence.java @@ -0,0 +1,193 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.config.Formats; +import org.openmbee.mms.data.dao.BranchDAO; +import org.openmbee.mms.data.dao.CommitDAO; +import org.openmbee.mms.data.dao.CommitIndexDAO; +import org.openmbee.mms.core.dao.CommitPersistence; +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.data.domains.scoped.Branch; +import org.openmbee.mms.data.domains.scoped.Commit; +import org.openmbee.mms.data.domains.scoped.CommitType; +import org.openmbee.mms.json.BaseJson; +import org.openmbee.mms.json.CommitJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; + +@Component("federatedCommitPersistence") +public class FederatedCommitPersistence implements CommitPersistence { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private CommitDAO commitDAO; + private CommitIndexDAO commitIndexDAO; + private BranchDAO branchDAO; + + @Autowired + public FederatedCommitPersistence(CommitDAO commitDAO, CommitIndexDAO commitIndexDAO, BranchDAO branchDAO) { + this.commitDAO = commitDAO; + this.commitIndexDAO = commitIndexDAO; + this.branchDAO = branchDAO; + } + + @Override + public CommitJson save(CommitJson commitJson, Instant now) { + ContextHolder.setContext(commitJson.getProjectId()); + Commit commit = new Commit(); + commit.setComment(commitJson.getComment()); + commit.setCommitId(commitJson.getId()); + commit.setCreator(commitJson.getCreator()); + commit.setBranchId(commitJson.getRefId()); + commit.setCommitType(CommitType.COMMIT); + commit.setTimestamp(now); + + try { + commitDAO.save(commit); + commitIndexDAO.index(commitJson); + return commitJson; + } catch (Exception e) { + logger.error("Couldn't create commit: {}", commitJson.getId(), e); + //Need to clean up in case of partial creation + try { + deleteById(commitJson.getProjectId(), commitJson.getId()); + } catch (Exception ex) { + logger.error("Commit cleanup error: ", ex); + } + throw new InternalErrorException("Could not create commit"); + } + } + + @Override + public CommitJson update(CommitJson commitJson) { + //TODO need to work on this logic + ContextHolder.setContext(commitJson.getProjectId()); + Optional commitOptional = commitDAO.findByCommitId(commitJson.getId()); + + if (commitOptional.isPresent()) { + Instant now = Instant.now(); + commitJson.setModified(Formats.FORMATTER.format(now)); + commitJson.setDocId(commitJson.getId()); + Commit commit = commitOptional.get(); + commit.setComment(commitJson.getComment()); + //commit.setTimestamp(now); + commitDAO.save(commit); + return commitIndexDAO.update(commitJson); + } + throw new NotFoundException("Could not update commit"); + } + + @Override + public Optional findById(String projectId, String commitId) { + ContextHolder.setContext(projectId); + Optional commit = commitDAO.findByCommitId(commitId); + if(!commit.isPresent()) { + return Optional.empty(); + } + Optional commitJson = commitIndexDAO.findById(commit.get().getCommitId()); + if(!commitJson.isPresent()) { + throw new InternalErrorException( + String.format("Federated data model inconsistency: Could not find commit json for docId %s", + commit.get().getCommitId())); + } + return commitJson; + } + + @Override + public List findAllById(String projectId, Set commitIds) { + ContextHolder.setContext(projectId); + Set foundCommitIds = new HashSet<>(); + + commitIds.forEach(commitId -> { + Optional commitOptional = commitDAO.findByCommitId(commitId); + if (commitOptional.isPresent()) { + foundCommitIds.add(commitOptional.get().getCommitId()); + } + }); + List commits = commitIndexDAO.findAllById(foundCommitIds); + commits.sort(Comparator.comparing(CommitJson::getCreated).reversed()); + return commits; + } + + @Override + public List findAllByProjectId(String projectId) { + ContextHolder.setContext(projectId); + Set commitIds = new HashSet<>(); + commitDAO.findAll().forEach(commit -> commitIds.add(commit.getCommitId())); + List commits = commitIndexDAO.findAllById(commitIds); + commits.sort(Comparator.comparing(CommitJson::getCreated).reversed()); + return commits; + } + + @Override + public Optional findLatestByProjectAndRef(String projectId, String refId) { + ContextHolder.setContext(projectId); + Optional branch = branchDAO.findByBranchId(refId); + if(!branch.isPresent()) { + return Optional.empty(); + } + Optional commit = commitDAO.findLatestByRef(branch.get()); + if(!commit.isPresent()) { + return Optional.empty(); + } + Optional commitJson = commitIndexDAO.findById(commit.get().getCommitId()); + if(!commitJson.isPresent()) { + throw new InternalErrorException( + String.format("Federated data model inconsistency: Could not find commit json for commitId %s", + commit.get().getCommitId())); + } + return commitJson; + } + + @Override + public List findByProjectAndRefAndTimestampAndLimit(String projectId, String refId, Instant timestamp, int limit) { + ContextHolder.setContext(projectId); + Set commitIds = new HashSet<>(); + Optional branchOptional = branchDAO.findByBranchId(refId); + if(!branchOptional.isPresent()) { + return new ArrayList<>(); + } + Branch b = branchOptional.get(); + List commitList = commitDAO.findByRefAndTimestampAndLimit(b, timestamp, limit); + commitList.forEach(commit -> commitIds.add(commit.getCommitId())); + List commits = commitIndexDAO.findAllById(commitIds); + commits.sort(Comparator.comparing(CommitJson::getCreated).reversed()); + return commits; + } + + @Override + public List elementHistory(String projectId, String refId, String elementId) { + ContextHolder.setContext(projectId); + Optional branchOptional = branchDAO.findByBranchId(refId); + if(!branchOptional.isPresent()) { + return new ArrayList<>(); + } + Branch b = branchOptional.get(); + List commitList = commitDAO.findByRefAndTimestampAndLimit(b, null, 0); + Set commitIds = new HashSet<>(); + for (Commit commit: commitList) { + commitIds.add(commit.getCommitId()); + } + List commits = commitIndexDAO.elementHistory(elementId, commitIds); + commits.sort(Comparator.comparing(CommitJson::getCreated).reversed()); + return commits; + } + + @Override + public Optional deleteById(String projectId, String commitId) { + // this is used for potentially cleaning up a failed commit save + // should not be used intentionally + ContextHolder.setContext(projectId); + Optional commitJsonOptional = commitIndexDAO.findById(commitId); + if (commitJsonOptional.isPresent()) { + commitIndexDAO.deleteById(commitId); + return commitJsonOptional; + } + return Optional.empty(); + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java new file mode 100644 index 000000000..f85d3d94f --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedGroupPersistence.java @@ -0,0 +1,64 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.dao.GroupPersistence; +import org.openmbee.mms.data.domains.global.Group; +import org.openmbee.mms.federatedpersistence.utils.FederatedJsonUtils; +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.rdb.repositories.GroupRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +public class FederatedGroupPersistence implements GroupPersistence { + + private GroupRepository groupRepository; + private FederatedJsonUtils jsonUtils; + + @Autowired + public void setGroupRepository(GroupRepository groupRepository) { + this.groupRepository = groupRepository; + } + + @Autowired + public void setJsonUtils(FederatedJsonUtils jsonUtils) { + this.jsonUtils = jsonUtils; + } + + @Override + public GroupJson save(GroupJson groupJson) { + Group groupObj = new Group(); + groupObj.setName(groupJson.getName()); + Group saved = groupRepository.saveAndFlush(groupObj); + return getJson(saved); + } + + @Override + public void delete(GroupJson groupJson) { + Optional group = groupRepository.findByName(groupJson.getName()); + group.ifPresent(g -> groupRepository.delete(g)); + } + + @Override + public Optional findByName(String name) { + Optional group = groupRepository.findByName(name); + return group.map(this::getJson); + } + + @Override + public Collection findAll() { + List groups = groupRepository.findAll(Sort.by(Group.NAME_COLUMN)); + return groups.stream().map(this::getJson).collect(Collectors.toList()); + } + + private GroupJson getJson(Group saved) { + GroupJson json = new GroupJson(); + json.merge(jsonUtils.convertToMap(saved)); + return json; + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeChangeInfo.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeChangeInfo.java new file mode 100644 index 000000000..d8103939a --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeChangeInfo.java @@ -0,0 +1,17 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.data.domains.scoped.Node; + +import java.util.Map; +import java.util.Set; + +public interface FederatedNodeChangeInfo extends NodeChangeInfo, FederatedNodeGetInfo { + Map getToSaveNodeMap(); + + NodeChangeInfo setToSaveNodeMap(Map toSaveNodeMap); + + Set getOldDocIds(); + + NodeChangeInfo setOldDocIds(Set oldDocIds); +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeChangeInfoImpl.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeChangeInfoImpl.java new file mode 100644 index 000000000..39574a3d2 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeChangeInfoImpl.java @@ -0,0 +1,60 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.core.services.NodeChangeInfoImpl; +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.data.domains.scoped.Node; + +import java.util.Map; +import java.util.Set; + +public class FederatedNodeChangeInfoImpl extends NodeChangeInfoImpl implements FederatedNodeChangeInfo { + private Map toSaveNodeMap; + private Map existingNodeMap; + private Set oldDocIds; + private Set reqIndexIds; + + @Override + public Map getToSaveNodeMap() { + return toSaveNodeMap; + } + + @Override + public NodeChangeInfo setToSaveNodeMap(Map toSaveNodeMap) { + this.toSaveNodeMap = toSaveNodeMap; + return this; + } + + @Override + public Map getExistingNodeMap() { + return existingNodeMap; + } + + @Override + public NodeGetInfo setExistingNodeMap(Map existingNodeMap) { + this.existingNodeMap = existingNodeMap; + return this; + } + + @Override + public Set getOldDocIds() { + return oldDocIds; + } + + @Override + public NodeChangeInfo setOldDocIds(Set oldDocIds) { + this.oldDocIds = oldDocIds; + return this; + } + + @Override + public Set getReqIndexIds() { + return reqIndexIds; + } + + @Override + public NodeChangeInfo setReqIndexIds(Set reqIndexIds) { + this.reqIndexIds = reqIndexIds; + return this; + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeGetInfo.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeGetInfo.java new file mode 100644 index 000000000..6a61f2f78 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeGetInfo.java @@ -0,0 +1,18 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.data.domains.scoped.Node; + +import java.util.Map; +import java.util.Set; + +public interface FederatedNodeGetInfo extends NodeGetInfo { + + Set getReqIndexIds(); + + NodeGetInfo setReqIndexIds(Set reqIndexIds); + + Map getExistingNodeMap(); + + NodeGetInfo setExistingNodeMap(Map existingNodeMap); +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeGetInfoImpl.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeGetInfoImpl.java new file mode 100644 index 000000000..ead54f239 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodeGetInfoImpl.java @@ -0,0 +1,36 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.core.services.NodeGetInfoImpl; +import org.openmbee.mms.data.domains.scoped.Node; + +import java.util.Map; +import java.util.Set; + +public class FederatedNodeGetInfoImpl extends NodeGetInfoImpl implements FederatedNodeGetInfo { + + private Set reqIndexIds; + private Map existingNodeMap; + + @Override + public Set getReqIndexIds() { + return reqIndexIds; + } + + @Override + public NodeGetInfo setReqIndexIds(Set reqIndexIds) { + this.reqIndexIds = reqIndexIds; + return this; + } + + @Override + public Map getExistingNodeMap() { + return existingNodeMap; + } + + @Override + public NodeGetInfo setExistingNodeMap(Map existingNodeMap) { + this.existingNodeMap = existingNodeMap; + return this; + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java new file mode 100644 index 000000000..446d8499c --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedNodePersistence.java @@ -0,0 +1,282 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.dao.*; +import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.data.dao.*; +import org.openmbee.mms.federatedpersistence.domain.*; +import org.openmbee.mms.data.domains.scoped.Branch; +import org.openmbee.mms.data.domains.scoped.Commit; +import org.openmbee.mms.data.domains.scoped.Node; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.json.RefJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +@Component +public class FederatedNodePersistence implements NodePersistence { + protected static final Logger logger = LoggerFactory.getLogger(FederatedNodePersistence.class); + + protected CommitPersistence commitPersistence; + + @Value("${mms.stream.batch.size:100000}") + protected int streamLimit; + protected final ObjectMapper objectMapper = new ObjectMapper(); + + private final NodeDAO nodeDAO; + private final NodeIndexDAO nodeIndexDAO; + private final CommitDAO commitDAO; + private final BranchDAO branchDAO; + private final ObjectFactory nodeGetDomainFactory; + private final ObjectFactory nodeChangeDomainObjectFactory; + + public FederatedNodePersistence(NodeDAO nodeDAO, NodeIndexDAO nodeIndexDAO, CommitDAO commitDAO, + BranchDAO branchDAO, ObjectFactory nodeGetDomainFactory, + ObjectFactory nodeChangeDomainObjectFactory, + CommitPersistence commitPersistence) { + this.nodeDAO = nodeDAO; + this.nodeIndexDAO = nodeIndexDAO; + this.commitDAO = commitDAO; + this.branchDAO = branchDAO; + this.nodeGetDomainFactory = nodeGetDomainFactory; + this.nodeChangeDomainObjectFactory = nodeChangeDomainObjectFactory; + this.commitPersistence = commitPersistence; + } + + public FederatedNodeGetDomain getNodeGetDomain() { + return nodeGetDomainFactory.getObject(); + } + + public FederatedNodeChangeDomain getNodeChangeDomain() { + return nodeChangeDomainObjectFactory.getObject(); + } + + + @Override + public NodeChangeInfo prepareChange(CommitJson commitJson, boolean overwrite, boolean preserveTimestamps) { + return getNodeChangeDomain().initInfo(commitJson, overwrite, preserveTimestamps); + } + + @Override + public NodeChangeInfo prepareAddsUpdates(NodeChangeInfo nodeChangeInfo, Collection elements) { + ContextHolder.setContext(nodeChangeInfo.getCommitJson().getProjectId(), nodeChangeInfo.getCommitJson().getRefId()); + primeNodeChangeInfo(nodeChangeInfo, elements); + return getNodeChangeDomain().processPostJson(nodeChangeInfo, elements); + } + + @Override + public NodeChangeInfo prepareDeletes(NodeChangeInfo info, Collection jsons) { + if(!(info instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Invalid NodeChangeInfo type presented to NodeFederatedDAO"); + } + ContextHolder.setContext(info.getCommitJson().getProjectId(), info.getCommitJson().getRefId()); + primeNodeChangeInfo(info, jsons); + return getNodeChangeDomain().processDeleteJson(info, jsons); + } + + protected void primeNodeChangeInfo(NodeChangeInfo nodeChangeInfo, Collection transactedElements) { + getNodeChangeDomain().primeNodeChangeInfo(nodeChangeInfo, transactedElements); + } + + @Override + public NodeGetInfo findById(String projectId, String refId, String commitId, String elementId) { + ContextHolder.setContext(projectId, refId); + List elements = new ArrayList<>(); + elements.add(new ElementJson().setId(elementId)); + return getNodeGetDomain().processGetJson(elements, commitId); + } + + @Override + public List findAllByNodeType(String projectId, String refId, String commitId, int nodeType) { + ContextHolder.setContext(projectId, refId); + String commitToPass = checkCommit(refId, commitId); + List nodes; + if (commitToPass != null) { + nodes = nodeDAO.findAllByNodeType(nodeType); + } else { + nodes = nodeDAO.findAllByDeletedAndNodeType(false, nodeType); + } + return new ArrayList<>(getNodeGetDomain().processGetJsonFromNodes(nodes, commitToPass).getActiveElementMap().values()); + } + + + @Override + public NodeGetInfo findAll(String projectId, String refId, String commitId, List elements) { + ContextHolder.setContext(projectId, refId); + return getNodeGetDomain().processGetJson(elements, checkCommit(refId, commitId)); + } + + @Override + public List findAll(String projectId, String refId, String commitId) { + ContextHolder.setContext(projectId, refId); + List nodes; + String commitToPass = checkCommit(refId, commitId); + if (commitToPass != null) { + nodes = nodeDAO.findAll(); + } else { + nodes = nodeDAO.findAllByDeleted(false); + } + return new ArrayList<>(getNodeGetDomain().processGetJsonFromNodes(nodes, commitToPass).getActiveElementMap().values()); + } + + + @Override + public void streamAllAtCommit(String projectId, String refId, String commitId, OutputStream stream, String separator) { + ContextHolder.setContext(projectId, refId); + List nodes; + final String commitToPass = checkCommit(refId, commitId); + if (commitToPass != null) { + nodes = nodeDAO.findAll(); + } else { + nodes = nodeDAO.findAllByDeleted(false); + } + AtomicInteger counter = new AtomicInteger(); + batches(nodes, streamLimit).forEach(ns -> { + try { + if (counter.get() == 0) { + counter.getAndIncrement(); + } else { + stream.write(separator.getBytes(StandardCharsets.UTF_8)); + } + Collection result = getNodeGetDomain().processGetJsonFromNodes(ns, commitToPass) + .getActiveElementMap().values(); + result.forEach(v -> v.setRefId(refId)); + stream.write(result.stream().map(this::toJson).collect(Collectors.joining(separator)) + .getBytes(StandardCharsets.UTF_8)); + } catch (IOException ioe) { + logger.error("Error writing to stream", ioe); + } + }); + } + + + @Override + public void branchElements(RefJson parentBranch, CommitJson parentCommit, RefJson targetBranch) { + if (parentBranch != null && parentCommit != null && targetBranch != null) { + String projectId = parentBranch.getProjectId(); + ContextHolder.setContext(projectId, parentBranch.getId()); + Optional latest = commitPersistence.findLatestByProjectAndRef(projectId, parentBranch.getId()); + Set docIds = new HashSet<>(); + ContextHolder.setContext(projectId, targetBranch.getId()); + if (latest.isPresent() && !latest.get().getId().equals(parentCommit.getId())) { + FederatedNodeGetInfo info = (FederatedNodeGetInfo)getNodeGetDomain().processGetJsonFromNodes(nodeDAO.findAll(), parentCommit.getId()); + for (Node node: info.getExistingNodeMap().values()) { + ElementJson el = info.getActiveElementMap().get(node.getNodeId()); + if (el != null) { + node.setDocId(el.getDocId()); + node.setLastCommit(el.getCommitId()); + docIds.add(el.getDocId()); + node.setDeleted(false); + } else { + node.setDeleted(true); + } + } + nodeDAO.saveAll(info.getExistingNodeMap().values().stream().toList()); + + } else { + for (Node n : nodeDAO.findAllByDeleted(false)) { + docIds.add(n.getDocId()); + } + } + nodeIndexDAO.addToRef(docIds); + } else { + throw new InternalErrorException("Error committing transaction"); + } + } + + protected static Stream> batches(List source, int length) { + return IntStream.iterate(0, i -> i < source.size(), i -> i + length) + .mapToObj(i -> source.subList(i, Math.min(i + length, source.size()))); + } + + protected String toJson(ElementJson elementJson) { + try { + return objectMapper.writeValueAsString(elementJson); + } catch (JsonProcessingException e) { + logger.error("Error in toJson: ", e); + } + return ""; + } + + @Override + public FederatedNodeChangeInfo commitChanges(NodeChangeInfo info) { + if(!(info instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Invalid NodeChangeInfo type presented to NodeFederatedDAO"); + } + //TODO: Test rollback on IndexDAO failure + //TODO: move transaction stuff out into transaction service + ContextHolder.setContext(info.getCommitJson().getProjectId(), info.getCommitJson().getRefId()); + TransactionDefinition def = new DefaultTransactionDefinition(); + TransactionStatus status = nodeDAO.getTransactionManager().getTransaction(def); + + FederatedNodeChangeInfo federatedInfo = (FederatedNodeChangeInfo) info; + Map nodes = federatedInfo.getToSaveNodeMap(); + Map json = federatedInfo.getUpdatedMap(); + CommitJson cmjs = federatedInfo.getCommitJson(); + Instant now = federatedInfo.getInstant(); + if (!nodes.isEmpty()) { + try { + if (json != null && !json.isEmpty()) { + nodeIndexDAO.indexAll(json.values()); + } + nodeDAO.saveAll(new ArrayList<>(nodes.values())); + commitPersistence.save(cmjs,now); + nodeIndexDAO.removeFromRef(federatedInfo.getOldDocIds()); + nodeDAO.getTransactionManager().commit(status); + } catch (Exception e) { + logger.error("commitChanges error: ", e); + nodeDAO.getTransactionManager().rollback(status); + throw new InternalErrorException("Error committing transaction"); + } + } + return federatedInfo; + } + + private void validateBranch(Optional branch) { + if (!branch.isPresent()) { + throw new InternalErrorException("Cannot find branch"); + } + } + + private void validateCommit(Optional commit) { + if (!commit.isPresent()) { + throw new BadRequestException("commit id is invalid"); + } + } + + // if commitId is latest, return null + private String checkCommit(String refId, String commitId) { + Optional branch = branchDAO.findByBranchId(refId); + validateBranch(branch); + if (commitId != null && !commitId.isEmpty() && !commitId.equals( + commitDAO.findLatestByRef(branch.get()).map(Commit::getCommitId).orElse(null))) { + validateCommit(commitDAO.findByCommitId(commitId)); + return commitId; + } else { + return null; + } + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java new file mode 100644 index 000000000..c293e6804 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedOrgPersistence.java @@ -0,0 +1,80 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.exceptions.ForbiddenException; +import org.openmbee.mms.data.dao.OrgDAO; +import org.openmbee.mms.core.dao.OrgPersistence; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.data.domains.global.Organization; +import org.openmbee.mms.federatedpersistence.utils.FederatedJsonUtils; +import org.openmbee.mms.json.OrgJson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component("federatedOrgPersistence") +public class FederatedOrgPersistence implements OrgPersistence { + + private OrgDAO orgDAO; + private FederatedJsonUtils jsonUtils; + + @Autowired + public void setOrgDAO(OrgDAO orgDAO) { + this.orgDAO = orgDAO; + } + + @Autowired + public void setJsonUtils(FederatedJsonUtils jsonUtils) { + this.jsonUtils = jsonUtils; + } + + @Override + public OrgJson save(OrgJson orgJson) { + Optional organizationOptional = orgDAO.findByOrganizationId(orgJson.getId()); + Organization organization = organizationOptional.orElse(new Organization()); + organization.setOrganizationId(orgJson.getId()); + organization.setOrganizationName(orgJson.getName()); + return getOrgJson(orgDAO.save(organization)); + } + + @Override + public Optional findById(String orgId) { + return orgDAO.findByOrganizationId(orgId).map(this::getOrgJson); + } + + @Override + public Collection findAll() { + return orgDAO.findAll().stream().map(this::getOrgJson).collect(Collectors.toList()); + } + + @Override + public OrgJson deleteById(String orgId) { + Optional organization = orgDAO.findByOrganizationId(orgId); + if(organization.isEmpty()) { + throw new NotFoundException(getOrgNotFoundMessage(orgId)); + } + orgDAO.delete(organization.get()); + return getOrgJson(organization.get()); + } + + @Override + public boolean hasPublicPermissions(String orgId) { + Optional organization = orgDAO.findByOrganizationId(orgId); + if (organization.isEmpty()) { + throw new NotFoundException(getOrgNotFoundMessage(orgId)); + } + return organization.get().isPublic(); + } + + protected OrgJson getOrgJson(Organization organization) { + OrgJson orgJson = new OrgJson(); + orgJson.merge(jsonUtils.convertToMap(organization)); + return orgJson; + } + + private String getOrgNotFoundMessage(String id) { + return String.format("org %s not found", id); + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java new file mode 100644 index 000000000..68d545a13 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedProjectPersistence.java @@ -0,0 +1,222 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.dao.ProjectPersistence; +import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.data.dao.OrgDAO; +import org.openmbee.mms.data.dao.ProjectDAO; +import org.openmbee.mms.data.dao.ProjectIndex; +import org.openmbee.mms.data.domains.global.Organization; +import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.json.ProjectJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.transaction.Transactional; +import java.util.*; +import java.util.stream.Collectors; + +@Component("federatedProjectPersistence") +public class FederatedProjectPersistence implements ProjectPersistence { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private ProjectDAO projectDAO; + private ProjectIndex projectIndexDAO; + private OrgDAO orgRepository; + + @Autowired + public FederatedProjectPersistence(ProjectDAO projectDAO, ProjectIndex projectIndexDAO, OrgDAO orgDAO) { + this.projectDAO = projectDAO; + this.projectIndexDAO = projectIndexDAO; + this.orgRepository = orgDAO; + } + + @Override + public Optional findById(String projectId) { + ContextHolder.setContext(projectId); + Optional projectOption = projectDAO.findByProjectId(projectId); + + if (projectOption.isEmpty()) { + return Optional.empty(); + } + + Project project = projectOption.get(); + + Optional projectJsonOption = projectIndexDAO.findById(project.getDocId()); + if (projectJsonOption.isEmpty()) { + logger.error("Federated data inconsistency: JSON Not found for {} with docId: {}", + project.getProjectId(), project.getDocId()); + throw new NotFoundException("Project not found"); + } + + return projectJsonOption; + } + + @Override + public List findAllById(Set projectIds) { + return projectIds.stream().map(projectId -> { + Optional projectOption = projectDAO.findByProjectId(projectId); + if (projectOption.isPresent()) { + Project project = projectOption.get(); + ContextHolder.setContext(project.getProjectId()); + return projectIndexDAO.findById(project.getDocId()).orElse(null); + } + return null; + }).filter(Objects::nonNull).collect(Collectors.toList()); + } + + @Override + public List findAll() { + return projectDAO.findAll().stream().map(project -> { + ContextHolder.setContext(project.getProjectId()); + return projectIndexDAO.findById(project.getDocId()).orElse(null); + }).filter(Objects::nonNull).collect(Collectors.toList()); + } + + @Override + @Transactional + public Collection findAllByOrgId(String orgId) { + Optional org = orgRepository.findByOrganizationId(orgId); + if (org.isEmpty()) { + throw new NotFoundException("org not found"); + } + if (org.get().getProjects() == null) { + return List.of(); + } + return org.get().getProjects().stream().map(project -> { + ContextHolder.setContext(project.getProjectId()); + return projectIndexDAO.findById(project.getDocId()).orElse(null); + }).filter(Objects::nonNull).collect(Collectors.toList()); + } + + @Override + public void hardDelete(String projectId) { + String message = ""; + try { + ContextHolder.clearContext(); + projectDAO.delete(projectId); + } catch (Exception e) { + message.concat(e.getMessage()); + } + try { + ContextHolder.setContext(projectId); + projectIndexDAO.delete(projectId); + } catch (Exception e) { + message.concat(e.getMessage()); + } + if (!message.isEmpty()) { + throw new InternalErrorException(message); + } + } + + @Override + public boolean inheritsPermissions(String projectId) { + Optional project = projectDAO.findByProjectId(projectId); + if (project.isEmpty()) { + throw new NotFoundException("project " + projectId + " not found"); + } + return project.get().isInherit(); + } + + @Override + public boolean hasPublicPermissions(String projectId) { + Optional project = projectDAO.findByProjectId(projectId); + if (project.isEmpty()) { + throw new NotFoundException("project " + projectId + " not found"); + } + return project.get().isPublic(); + } + + @Override + public void softDelete(String projectId) { + //TODO not called locally, otherwise delete + ContextHolder.setContext(projectId); + Optional project = this.projectDAO.findByProjectId(projectId); + + ContextHolder.setContext(projectId); + Optional projectJsonOption = project.isPresent() ? + projectIndexDAO.findById(project.get().getDocId()) : Optional.empty(); + if (project.isEmpty() || projectJsonOption.isEmpty()) { + throw new NotFoundException("Project state is invalid, cannot delete."); + } + + Project p = project.get(); + + p.setDeleted(true); + ContextHolder.setContext(null); + projectDAO.save(p); + + ProjectJson projectJson = projectJsonOption.get(); + projectJson.setIsDeleted(Constants.TRUE); + + ContextHolder.setContext(projectId); + projectIndexDAO.update(projectJson); + } + + @Override + public ProjectJson save(ProjectJson projectJson) { + + Optional org = orgRepository.findByOrganizationId(projectJson.getOrgId()); + + if(org.isEmpty()) { + throw new NotFoundException("org not found"); + } + + if (projectJson.getDocId() == null || projectJson.getDocId().isEmpty()) { + projectJson.setDocId(UUID.randomUUID().toString()); + } + + Project proj = new Project(); + proj.setProjectId(projectJson.getId()); + proj.setProjectName(projectJson.getName()); + proj.setOrganization(org.get()); + proj.setProjectType(projectJson.getProjectType()); + proj.setDocId(projectJson.getDocId()); + proj.setDeleted(Boolean.parseBoolean(projectJson.getIsDeleted())); + + try { + projectDAO.save(proj); + ContextHolder.setContext(projectJson.getProjectId()); + projectIndexDAO.create(projectJson); + + return projectJson; + } catch (Exception e) { + logger.error("Couldn't create project: {}", projectJson.getProjectId(), e); + //Need to clean up in case of partial creation + hardDelete(projectJson.getProjectId()); + throw new InternalErrorException("Could not create project"); + } + } + + @Override + public ProjectJson update(ProjectJson projectJson) { + + ContextHolder.setContext(projectJson.getProjectId()); + + Optional projOption = projectDAO.findByProjectId(projectJson.getProjectId()); + if (projOption.isPresent()) { + Project proj = projOption.get(); + if (projectJson.getName() != null && !projectJson.getName().isEmpty()) { + proj.setProjectName(projectJson.getName()); + } + if (projectJson.getOrgId() != null && !projectJson.getOrgId().isEmpty()) { + Optional org = orgRepository.findByOrganizationId(projectJson.getOrgId()); + if (org.isPresent() && !org.get().getOrganizationId().isEmpty()) { + proj.setOrganization(org.get()); + } else { + throw new BadRequestException("Invalid organization"); + } + } + projectJson.setDocId(proj.getDocId()); + projectDAO.save(proj); + ContextHolder.setContext(projectJson.getProjectId()); + return projectIndexDAO.update(projectJson); + } + throw new NotFoundException("Could not update project"); + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserGroupsPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserGroupsPersistence.java new file mode 100644 index 000000000..4cb9de80d --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserGroupsPersistence.java @@ -0,0 +1,114 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.data.domains.global.Group; +import org.openmbee.mms.data.domains.global.User; +import org.openmbee.mms.federatedpersistence.utils.FederatedJsonUtils; +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.rdb.repositories.GroupRepository; +import org.openmbee.mms.rdb.repositories.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.transaction.Transactional; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +public class FederatedUserGroupsPersistence implements UserGroupsPersistence { + private UserRepository userRepository; + private GroupRepository groupRepository; + private FederatedJsonUtils jsonUtils; + + @Autowired + public void setUserRepository(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Autowired + public void setGroupRepository(GroupRepository groupRepository) { + this.groupRepository = groupRepository; + } + + @Autowired + public void setJsonUtils(FederatedJsonUtils jsonUtils) { + this.jsonUtils = jsonUtils; + } + + @Override + @Transactional + public boolean addUserToGroup(String groupName, String username) { + Optional userOptional = userRepository.findByUsernameIgnoreCase(username); + if(userOptional.isEmpty()) { + return false; + } + Optional groupOptional = groupRepository.findByName(groupName); + if(groupOptional.isEmpty()) { + return false; + } + + User user = userOptional.get(); + if(user.getGroups().contains(groupOptional.get())) { + return false; + } + user.getGroups().add(groupOptional.get()); + userRepository.save(user); + return true; + } + + @Override + @Transactional + public boolean removeUserFromGroup(String groupName, String username) { + Optional userOptional = userRepository.findByUsernameIgnoreCase(username); + if(userOptional.isEmpty()) { + return false; + } + Optional groupOptional = groupRepository.findByName(groupName); + if(groupOptional.isEmpty()) { + return false; + } + + User user = userOptional.get(); + if(!user.getGroups().contains(groupOptional.get())) { + return false; + } + user.getGroups().remove(groupOptional.get()); + userRepository.save(user); + return true; + } + + @Override + @Transactional + public Collection findUsersInGroup(String groupName) { + Optional groupOptional = groupRepository.findByName(groupName); + if(groupOptional.isEmpty()){ + return List.of(); + } + return groupOptional.get().getUsers().stream().map(this::getJson).collect(Collectors.toList()); + } + + @Override + @Transactional + public Collection findGroupsAssignedToUser(String username) { + Optional userOptional = userRepository.findByUsernameIgnoreCase(username); + if(userOptional.isEmpty()) { + return List.of(); + } + return userOptional.get().getGroups().stream().map(this::getJson).collect(Collectors.toList()); + } + + private GroupJson getJson(Group saved) { + GroupJson json = new GroupJson(); + json.merge(jsonUtils.convertToMap(saved)); + return json; + } + + private UserJson getJson(User saved) { + UserJson savedJson = new UserJson(); + savedJson.merge(jsonUtils.convertToMap(saved)); + return savedJson; + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserPersistence.java new file mode 100644 index 000000000..21fdd4b42 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedUserPersistence.java @@ -0,0 +1,73 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.dao.UserPersistence; +import org.openmbee.mms.data.domains.global.User; +import org.openmbee.mms.federatedpersistence.utils.FederatedJsonUtils; +import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.rdb.repositories.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +public class FederatedUserPersistence implements UserPersistence { + + private UserRepository userRepository; + private FederatedJsonUtils jsonUtils; + + @Autowired + public void setUserRepository(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Autowired + public void setJsonUtils(FederatedJsonUtils jsonUtils) { + this.jsonUtils = jsonUtils; + } + + @Override + public UserJson save(UserJson userJson) { + + Optional existing = userRepository.findByUsernameIgnoreCase(userJson.getUsername()); + + User user; + if(existing.isPresent()) { + user = existing.get(); + } else { + user = new User(); + user.setUsername(userJson.getUsername()); + } + + user.setEmail(userJson.getEmail()); + user.setFirstName(userJson.getFirstName()); + user.setLastName(userJson.getLastName()); + user.setPassword(userJson.getPassword()); + user.setEnabled(userJson.isEnabled()); + user.setAdmin(userJson.isAdmin()); + + User saved = userRepository.save(user); + + return getJson(saved); + } + + @Override + public Optional findByUsername(String username) { + Optional user = userRepository.findByUsernameIgnoreCase(username); + return user.map(this::getJson); + } + + @Override + public Collection findAll() { + Collection users = userRepository.findAll(); + return users.stream().map(this::getJson).collect(Collectors.toList()); + } + + private UserJson getJson(User saved) { + UserJson savedJson = new UserJson(); + savedJson.merge(jsonUtils.convertToMap(saved)); + return savedJson; + } +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedWebhookPersistence.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedWebhookPersistence.java new file mode 100644 index 000000000..ffe46caa3 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/dao/FederatedWebhookPersistence.java @@ -0,0 +1,129 @@ +package org.openmbee.mms.federatedpersistence.dao; + +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.data.dao.ProjectDAO; +import org.openmbee.mms.data.dao.WebhookDAO; +import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.data.domains.global.Webhook; +import org.openmbee.mms.federatedpersistence.utils.FederatedJsonUtils; +import org.openmbee.mms.webhooks.json.WebhookJson; +import org.openmbee.mms.webhooks.persistence.WebhookPersistence; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +public class FederatedWebhookPersistence implements WebhookPersistence { + + private static Logger logger = LoggerFactory.getLogger(FederatedWebhookPersistence.class); + + private WebhookDAO webhookDao; + private ProjectDAO projectDao; + private FederatedJsonUtils jsonUtils; + + + @Autowired + public void setWebhookDao(WebhookDAO webhookDao) { + this.webhookDao = webhookDao; + } + + @Autowired + public void setProjectDao(ProjectDAO projectDao) { + this.projectDao = projectDao; + } + + @Autowired + public void setJsonUtils(FederatedJsonUtils jsonUtils) { + this.jsonUtils = jsonUtils; + } + + @Override + public WebhookJson save(WebhookJson webhookJson) { + if(webhookJson.getProjectId() == null || webhookJson.getProjectId().isEmpty()) { + logger.error("Webhook cannot be saved, missing project id"); + throw new InternalErrorException("Missing project id"); + } + Optional project = projectDao.findByProjectId(webhookJson.getProjectId()); + if(project.isEmpty()) { + throw new InternalErrorException("Cannot find project"); + } + + Optional existing = getWebhookById(webhookJson.getId()); + if(existing.isEmpty()) { + existing = webhookDao.findByProject_ProjectIdAndUrl(webhookJson.getProjectId(), webhookJson.getUrl()); + if(existing.isPresent()) { + //This webhook already exists, so just return what's there. + return existing.map(this::getWebhookJson).orElseThrow(() -> + new InternalErrorException("Could not generated webhook json from webhook")); + } + } + + Webhook webhook; + if (existing.isEmpty()) { + //Webhook doesn't exist, create it + webhook = new Webhook(); + webhook.setProject(project.get()); + } else { + //Webhook is there, but needs to have url updated + webhook = existing.get(); + } + webhook.setUrl(webhookJson.getUrl()); + Webhook saved = webhookDao.save(webhook); + webhookJson.setId(String.valueOf(saved.getId())); + return webhookJson; + } + + + @Override + public Optional findById(String id) { + if(id == null || id.isEmpty()) { + return Optional.empty(); + } + return getWebhookById(id).map(this::getWebhookJson); + } + + @Override + public List findAllByProjectId(String projectId) { + List webhooks = webhookDao.findAllByProject_ProjectId(projectId); + + return webhooks.stream().map(webhook -> { + WebhookJson webhookJson = getWebhookJson(webhook); + webhookJson.setProjectId(projectId); + return webhookJson; + }).collect(Collectors.toList()); + } + + + @Override + public Optional findByProjectIdAndUrl(String projectId, String url) { + return webhookDao.findByProject_ProjectIdAndUrl(projectId, url).map(this::getWebhookJson); + } + + @Override + public void delete(WebhookJson webhookJson) { + Optional webhook = getWebhookById(webhookJson.getId()); + webhook.ifPresent(w -> webhookDao.delete(w)); + } + + protected Optional getWebhookById(String id) { + try { + return webhookDao.findById(Long.parseLong(id)); + } catch(NumberFormatException ex) { + logger.error("Invalid webhook id format: {}", id); + return Optional.empty(); + } + } + + protected WebhookJson getWebhookJson(Webhook webhook) { + WebhookJson webhookJson = new WebhookJson(); + webhookJson.merge(jsonUtils.convertToMap(webhook)); + webhookJson.setId(webhook.getId().toString()); + return webhookJson; + } + +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedElementDomain.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedElementDomain.java new file mode 100644 index 000000000..7a5b707e9 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedElementDomain.java @@ -0,0 +1,64 @@ +package org.openmbee.mms.federatedpersistence.domain; + +import java.util.Optional; + +import org.openmbee.mms.data.dao.ProjectDAO; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.core.utils.ElementUtils; +import org.openmbee.mms.crud.domain.ElementDomain; +import org.openmbee.mms.crud.services.ElementUtilsFactory; +import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.json.ElementJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class FederatedElementDomain implements ElementDomain { + private static final Logger logger = LoggerFactory.getLogger(FederatedNodeChangeDomain.class); + + private ProjectDAO projectRepository; + private ElementUtilsFactory elementUtilsFactory; + + @Autowired + public void setProjectRepository(ProjectDAO projectRepository) { + this.projectRepository = projectRepository; + } + + @Autowired + public void setElementUtilsFactory(ElementUtilsFactory elementUtilsFactory) { + this.elementUtilsFactory = elementUtilsFactory; + } + + @Override + public ElementUtils getElementUtils(String projectId) { + return elementUtilsFactory.getElementUtil(getProjectType(projectId)); + } + + @Override + public Integer getNodeType(String projectId, ElementJson element){ + if (null == projectId) { + return 0; + } + + ElementUtils elementUtils = getElementUtils(projectId); + if (null == elementUtils) { + return 0; + } + return elementUtils.getNodeType(element).ordinal()+1; + } + + private String getProjectType(String projectId) { + return getProject(projectId).getProjectType(); + } + + private Project getProject(String projectId) { + Optional p = projectRepository.findByProjectId(projectId); + if (p.isPresent()) { + return p.get(); + } + throw new NotFoundException("project not found"); + } + +} \ No newline at end of file diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java new file mode 100644 index 000000000..1d4ecc866 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeChangeDomain.java @@ -0,0 +1,295 @@ +package org.openmbee.mms.federatedpersistence.domain; + +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.config.Formats; +import org.openmbee.mms.crud.domain.NodeUpdateFilter; +import org.openmbee.mms.data.dao.CommitDAO; +import org.openmbee.mms.data.dao.NodeDAO; +import org.openmbee.mms.data.dao.NodeIndexDAO; +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.objects.Rejection; +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.crud.domain.CommitDomain; +import org.openmbee.mms.crud.domain.NodeChangeDomain; +import org.openmbee.mms.data.domains.scoped.Commit; +import org.openmbee.mms.data.domains.scoped.Node; +import org.openmbee.mms.federatedpersistence.dao.FederatedNodeChangeInfo; +import org.openmbee.mms.federatedpersistence.dao.FederatedNodeChangeInfoImpl; +import org.openmbee.mms.federatedpersistence.dao.FederatedNodeGetInfo; +import org.openmbee.mms.json.BaseJson; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.json.ElementVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.stream.Collectors; + +@Component +public class FederatedNodeChangeDomain extends NodeChangeDomain { + private static final Logger logger = LoggerFactory.getLogger(FederatedNodeChangeDomain.class); + + protected FederatedNodeGetDomain getDomain; + protected FederatedElementDomain elementDomain; + protected NodeDAO nodeRepository; + protected NodeIndexDAO nodeIndex; + protected CommitDAO commitDAO; + + @Autowired + public FederatedNodeChangeDomain(CommitDomain commitDomain, + FederatedNodeGetDomain getDomain, NodeDAO nodeRepository, NodeIndexDAO nodeIndex, FederatedElementDomain elementDomain, + List nodeUpdateFilters, CommitDAO commitDAO) { + super(getDomain, commitDomain, nodeUpdateFilters); + this.getDomain = getDomain; + this.nodeRepository = nodeRepository; + this.nodeIndex = nodeIndex; + this.elementDomain = elementDomain; + this.commitDAO = commitDAO; + } + + @Override + protected NodeChangeInfo createNodeChangeInfo() { + return new FederatedNodeChangeInfoImpl(); + } + + @Override + public NodeChangeInfo initInfo(CommitJson commitJson, boolean overwrite, boolean preserveTimestamps) { + commitJson.setId(UUID.randomUUID().toString()); + commitJson.setDocId(commitJson.getId()); + + NodeChangeInfo info = super.initInfo(commitJson, overwrite, preserveTimestamps); + if(info instanceof FederatedNodeChangeInfo) { + ((FederatedNodeChangeInfo)info).setOldDocIds(new HashSet<>()); + ((FederatedNodeChangeInfo)info).setReqIndexIds(new HashSet<>()); + ((FederatedNodeChangeInfo)info).setToSaveNodeMap(new HashMap<>()); + } else { + throw new InternalErrorException("Unexpected NodeChangeInfo type in FederatedNodeChangeDomain"); + } + return info; + } + + + @Override + public void processElementAdded(NodeChangeInfo info, ElementJson element) { + if(!(info instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Unexpected NodeChangeInfo type in FederatedNodeChangeDomain"); + } + + Node node = new Node(); + node.setNodeId(element.getId()); + + ((FederatedNodeChangeInfo) info).getExistingNodeMap().put(element.getId(), node); + super.processElementAdded(info, element); + + node.setInitialCommit(element.getCommitId()); + + CommitJson commitJson = info.getCommitJson(); + ElementVersion newObj = new ElementVersion() + .setDocId(element.getDocId()) + .setId(element.getId()) + .setType("Element"); + commitJson.getAdded().add(newObj); + + ((FederatedNodeChangeInfo) info).getToSaveNodeMap().put(element.getId(), node); + } + + @Override + public boolean processElementUpdated(NodeChangeInfo info, ElementJson element, ElementJson existing) { + if(!(info instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Unexpected NodeChangeInfo type in FederatedNodeChangeDomain"); + } + String previousDocId; + Node n = ((FederatedNodeChangeInfo) info).getExistingNodeMap().get(element.getId()); + if(n != null) { + previousDocId = n.getDocId(); + ((FederatedNodeChangeInfo) info).getOldDocIds().add(previousDocId); + if(n.isDeleted()) { + existing.setIsDeleted(Constants.TRUE); + } + } else { + return false; + } + + if (!super.processElementUpdated(info, element, existing)) { + return false; + } + ElementVersion newObj= new ElementVersion() + .setPreviousDocId(previousDocId) + .setDocId(element.getDocId()) + .setId(element.getId()) + .setType("Element"); + info.getCommitJson().getUpdated().add(newObj); + return true; + } + + @Override + protected void processElementAddedOrUpdated(NodeChangeInfo info, ElementJson element) { + + if(! (info instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Unexpected NodeChangeInfo type in FederatedNodeChangeDomain"); + } + + Node n = ((FederatedNodeChangeInfo) info).getExistingNodeMap().get(element.getId()); + if(n == null) { + return; + } + + String docId = UUID.randomUUID().toString(); + element.setDocId(docId); + + super.processElementAddedOrUpdated(info, element); + n.setDocId(element.getDocId()); + n.setLastCommit(info.getCommitJson().getId()); + n.setDeleted(false); + n.setNodeType(elementDomain.getNodeType(element.getProjectId(), element)); + + ((FederatedNodeChangeInfo) info).getToSaveNodeMap().put(element.getId(), n); + } + + @Override + public void processElementDeleted(NodeChangeInfo info, ElementJson element) { + if(! (info instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Unexpected NodeChangeInfo type in FederatedNodeChangeDomain"); + } + Node n = ((FederatedNodeChangeInfo) info).getExistingNodeMap().get(element.getId()); + if (n == null) { + return; + } + + super.processElementDeleted(info, element); + + ElementVersion newObj = new ElementVersion() + .setPreviousDocId(n.getDocId()) + .setId(element.getId()) + .setType("Element"); + info.getCommitJson().getDeleted().add(newObj); + ((FederatedNodeChangeInfo) info).getOldDocIds().add(n.getDocId()); + ((FederatedNodeChangeInfo) info).getToSaveNodeMap().put(n.getNodeId(), n); + n.setDeleted(true); + } + + @Override + public FederatedNodeChangeInfo processDeleteJson(NodeChangeInfo info, Collection elements) { + if(! (info instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Unexpected NodeChangeInfo type in FederatedNodeChangeDomain"); + } + + FederatedNodeChangeInfo federatedInfo = (FederatedNodeChangeInfo) info; + + for (String nodeId : info.getReqElementMap().keySet()) { + if (!existingNodeContainsNodeId(federatedInfo, nodeId)) { + continue; + } + Node node = federatedInfo.getExistingNodeMap().get(nodeId); + ElementJson indexElement = info.getExistingElementMap().get(nodeId); + + if (node.isDeleted()) { + info.addRejection(nodeId, new Rejection(indexElement, 410, "Already deleted")); + continue; + } + if (indexElement == null) { + logger.warn("node db and index mismatch on element delete: nodeId: " + nodeId + + ", docId not found: " + federatedInfo.getExistingNodeMap().get(nodeId).getDocId()); + indexElement = new ElementJson().setId(nodeId).setDocId(node.getDocId()); + } + + ElementJson request = info.getReqElementMap().get(nodeId); + request.putAll(indexElement); + processElementDeleted(info, request); + info.getDeletedMap().put(nodeId, request); + } + return federatedInfo; + } + + protected boolean existingNodeContainsNodeId(FederatedNodeGetInfo info, String nodeId) { + if (!info.getExistingNodeMap().containsKey(nodeId)) { + rejectNotFound(info, nodeId); + return false; + } + return true; + } + + // create new elastic id for all element json, update modified time, modifier (use dummy for now), set _projectId, _refId, _inRefIds + @Override + public FederatedNodeChangeInfo processPostJson(NodeChangeInfo info, Collection elements) { + + if(!(info instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Unexpected NodeChangeInfo type in FederatedNodeChangeDomain"); + } + FederatedNodeChangeInfo federatedInfo = (FederatedNodeChangeInfo) info; + + // Logic for update/add + for (ElementJson element : elements) { + if (element == null) { + continue; + } + boolean added = false; + if (element.getId() == null || element.getId().isEmpty()) { + element.setId(UUID.randomUUID().toString()); + } + ElementJson indexElement = info.getExistingElementMap().get(element.getId()); + Node n = federatedInfo.getExistingNodeMap().get(element.getId()); + if (n == null) { + added = true; + } else if (indexElement == null) { + logger.warn("node db and index mismatch on element update: nodeId: " + n.getNodeId() + ", docId not found: " + n.getDocId()); + indexElement = new ElementJson().setId(n.getNodeId()).setDocId(n.getDocId()); + Optional init = commitDAO.findByCommitId(n.getInitialCommit()); + if (init.isPresent()) { + indexElement.setCreator(init.get().getCreator()); + indexElement.setCreated(Formats.FORMATTER.format(init.get().getTimestamp())); + } + } + + // create new doc id for all element json, update modified time, modifier (use dummy for now), set _projectId, _refId, _inRefIds + if (added) { + processElementAdded(info, element); + } else { + processElementUpdated(info, element, indexElement); + } + } + return federatedInfo; + } + + + @Override + public void primeNodeChangeInfo(NodeChangeInfo nodeChangeInfo, Collection transactedElements) { + Set elementIds = transactedElements.stream().map(BaseJson::getId).filter(id->null!=id).collect(Collectors.toSet()); + List existingNodes = nodeRepository.findAllByNodeIds(elementIds); + + if (!(nodeChangeInfo instanceof FederatedNodeChangeInfo)) { + throw new InternalErrorException("Node processing is not using FederatedNodeChangeInfo"); + } + + FederatedNodeChangeInfo info = (FederatedNodeChangeInfo)nodeChangeInfo; + Set indexIds = Optional.ofNullable(info.getReqIndexIds()).orElse(new HashSet<>()); + Map existingNodeMap = Optional.ofNullable(info.getExistingNodeMap()).orElse(new HashMap<>()); + Map reqElementMap = new HashMap<>(); // don't add to any existing since this change processing is only for transactedElements + Map existingElementMap = Optional.ofNullable(info.getExistingElementMap()).orElse(new HashMap<>()); + + Set transactedIndexIds = new HashSet<>(); + for (Node node : existingNodes) { + transactedIndexIds.add(node.getDocId()); + indexIds.add(node.getDocId()); + existingNodeMap.put(node.getNodeId(), node); + } + if (!transactedElements.isEmpty()) { + reqElementMap.putAll(convertJsonToMap(transactedElements)); + } + + // bulk read existing elements in elastic + List existingElements = nodeIndex.findAllById(transactedIndexIds); + existingElementMap.putAll(convertJsonToMap(existingElements)); + + info.setExistingElementMap(existingElementMap); + info.setReqElementMap(reqElementMap); + info.setRejected(Optional.ofNullable(info.getRejected()).orElse(new HashMap<>())); + info.setActiveElementMap(Optional.ofNullable(info.getActiveElementMap()).orElse(new HashMap<>())); + info.setExistingNodeMap(existingNodeMap); + info.setReqIndexIds(indexIds); + } + +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeGetDomain.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeGetDomain.java new file mode 100644 index 000000000..36489e1d3 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/domain/FederatedNodeGetDomain.java @@ -0,0 +1,261 @@ +package org.openmbee.mms.federatedpersistence.domain; + +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.config.Formats; +import org.openmbee.mms.data.dao.*; +import org.openmbee.mms.core.exceptions.BadRequestException; +import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.crud.domain.NodeGetDomain; +import org.openmbee.mms.data.domains.scoped.Branch; +import org.openmbee.mms.data.domains.scoped.Commit; +import org.openmbee.mms.data.domains.scoped.Node; +import org.openmbee.mms.federatedpersistence.dao.FederatedNodeGetInfo; +import org.openmbee.mms.federatedpersistence.dao.FederatedNodeGetInfoImpl; +import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.ElementJson; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +import static org.openmbee.mms.core.config.ContextHolder.getContext; + +@Component +public class FederatedNodeGetDomain extends NodeGetDomain { + + protected static final Logger logger = LoggerFactory.getLogger(FederatedNodeGetDomain.class); + + private NodeDAO nodeDAO; + private NodeIndexDAO nodeIndex; + private CommitDAO commitDAO; + private BranchDAO branchDAO; + private CommitIndexDAO commitIndex; + + @Autowired + public FederatedNodeGetDomain(NodeDAO nodeDAO, NodeIndexDAO nodeIndex, + CommitDAO commitDAO, BranchDAO branchDAO, CommitIndexDAO commitIndex) { + this.nodeDAO = nodeDAO; + this.nodeIndex = nodeIndex; + this.commitDAO = commitDAO; + this.branchDAO = branchDAO; + this.commitIndex = commitIndex; + } + + public NodeGetInfo initInfo(List elements, CommitJson commitJson) { + Set indexIds = new HashSet<>(); + elements.stream().map(ElementJson::getId).forEach(indexIds::add); + List existingNodes = nodeDAO.findAllByNodeIds(indexIds); + NodeGetInfo nodeGetInfo = initInfoFromNodes(existingNodes, commitJson); + nodeGetInfo.getReqElementMap().putAll(convertJsonToMap(elements)); + return nodeGetInfo; + + } + + public NodeGetInfo initInfoFromNodes(List existingNodes, CommitJson commitJson) { + NodeGetInfo nodeGetInfo = super.initInfo(commitJson, this::createNodeGetInfo); + Set indexIds = existingNodes.stream().map(Node::getDocId).collect(Collectors.toSet()); + if (nodeGetInfo instanceof FederatedNodeGetInfo) { + FederatedNodeGetInfo federatedInfo = (FederatedNodeGetInfo)nodeGetInfo; + federatedInfo.setExistingNodeMap(new HashMap<>()); + federatedInfo.setReqIndexIds(new HashSet<>()); + for (Node node : existingNodes) { + federatedInfo.getReqIndexIds().add(node.getDocId()); + federatedInfo.getExistingNodeMap().put(node.getNodeId(), node); + federatedInfo.getReqElementMap().put(node.getNodeId(), new ElementJson().setId(node.getNodeId())); + } + } + // bulk read existing elements in elastic + List existingElements = nodeIndex.findAllById(indexIds); + addExistingElements(nodeGetInfo, existingElements); // handeled in addExistingElements + return nodeGetInfo; + } + + public NodeGetInfo processGetJsonFromNodes(List nodes, String commitId) { + if (commitId == null || commitId.isEmpty()) { + return processGetJsonFromNodes(nodes); + } + NodeGetInfo info = initInfoFromNodes(nodes, null); + return processCommit(info, commitId); + } + + public NodeGetInfo processGetJson(List elements, String commitId) { + if (commitId == null || commitId.isEmpty()) { + return processGetJson(elements); + } + NodeGetInfo info = initInfo(elements, null); //gets all current nodes + return processCommit(info, commitId); + } + + + public NodeGetInfo processGetJsonFromNodes(List nodes) { + NodeGetInfo info = initInfoFromNodes(nodes, null); + return processLatest(info); + } + + public NodeGetInfo processGetJson(List elements) { + NodeGetInfo info = initInfo(elements,null); + return processLatest(info); + } + + protected NodeGetInfo processLatest(NodeGetInfo info) { + if(!(info instanceof FederatedNodeGetInfo)) { + throw new InternalErrorException("Invalid use of FederatedNodeGetDomain"); + } + FederatedNodeGetInfo federatedInfo = (FederatedNodeGetInfo) info; + for (String nodeId : info.getReqElementMap().keySet()) { + if (!existingNodeContainsNodeId(federatedInfo, nodeId)) { + continue; + } + ElementJson indexElement = info.getExistingElementMap().get(nodeId); + if (indexElement == null) { + logger.warn("node db and index mismatch on element get: nodeId: " + nodeId + + ", docId not found: " + federatedInfo.getExistingNodeMap().get(nodeId).getDocId()); + rejectNotFound(info, nodeId); + continue; + } + if (federatedInfo.getExistingNodeMap().get(nodeId).isDeleted()) { + rejectDeleted(info, nodeId, indexElement); + continue; + } + info.getActiveElementMap().put(nodeId, indexElement); + } + return info; + } + + protected NodeGetInfo processCommit(NodeGetInfo info, String commitId) { + if (!(info instanceof FederatedNodeGetInfo)) { + throw new InternalErrorException("Invalid use of FederatedNodeGetDomain"); + } + FederatedNodeGetInfo federatedInfo = (FederatedNodeGetInfo) info; + Optional commit = commitDAO.findByCommitId(commitId); + if (!commit.isPresent() ) { + throw new BadRequestException("commitId is invalid"); + } + Instant time = commit.get().getTimestamp(); //time of commit + List refCommitIds = null; //get it later if needed + for (String nodeId : info.getReqElementMap().keySet()) { + if (!existingNodeContainsNodeId(federatedInfo, nodeId)) { // nodeId not found + continue; + } + ElementJson indexElement = info.getExistingElementMap().get(nodeId); + if (indexElement == null) { + // latest element not found, mock an object to continue + Node n = federatedInfo.getExistingNodeMap().get(nodeId); + logger.warn("node db and index mismatch on element commit get: nodeId: " + nodeId + + ", docId not found: " + n.getDocId()); + Optional last = commitDAO.findByCommitId(n.getLastCommit()); + Optional first = commitDAO.findByCommitId(n.getInitialCommit()); + if (!last.isPresent() || !first.isPresent()) { + rejectNotFound(info, nodeId); + continue; + } + indexElement = new ElementJson().setId(nodeId).setDocId(n.getDocId()); + indexElement.setModified(Formats.FORMATTER.format(last.get().getTimestamp())); + indexElement.setModifier(last.get().getCreator()); + indexElement.setCommitId(last.get().getCommitId()); + indexElement.setCreator(first.get().getCreator()); + indexElement.setCreated(Formats.FORMATTER.format(first.get().getTimestamp())); + } + if (info.getCommitJson() != null && info.getCommitJson().getRefId() != null) { + indexElement.setRefId(info.getCommitJson().getRefId()); + } else { + indexElement.setRefId(ContextHolder.getContext().getBranchId()); + } + + Instant modified = Instant.from(Formats.FORMATTER.parse(indexElement.getModified())); + Instant created = Instant.from(Formats.FORMATTER.parse(indexElement.getCreated())); + + if (commitId.equals(indexElement.getCommitId())) { //exact match + addActiveElement(info, nodeId, indexElement); + } else if (created.isAfter(time)) { // element created after commit + rejectNotFound(info, nodeId); + } else if (modified.isAfter(time)) { // latest element is after commit + Optional tryExact = nodeIndex.getByCommitId(commitId, nodeId); + if (tryExact.isPresent()) { + addActiveElement(info, nodeId, tryExact.get()); + continue; // found exact match at commit + } + if (refCommitIds == null) { // need list of commitIds of current ref to filter on + refCommitIds = getRefCommitIds(time); + } + Optional e = nodeIndex.getElementLessThanOrEqualTimestamp(nodeId, + Formats.FORMATTER.format(time), refCommitIds); + if (e.isPresent()) { // found version of element at commit time + Instant realModified = Instant.from(Formats.FORMATTER.parse(e.get().getModified())); + if (elementDeleted(nodeId, commitId, time, realModified, refCommitIds)) { + rejectDeleted(info, nodeId, e.get()); + } else { + addActiveElement(info, nodeId, e.get()); + } + } else { + rejectNotFound(info, nodeId); // element not found at commit time + } + } else if (federatedInfo.getExistingNodeMap().get(nodeId).isDeleted()) { // latest element is before commit, but deleted + if (refCommitIds == null) { // need list of commitIds of current ref to filter on + refCommitIds = getRefCommitIds(time); + } + if (elementDeleted(nodeId, commitId, time, modified, refCommitIds)) { + rejectDeleted(info, nodeId, indexElement); + } else { + addActiveElement(info, nodeId, indexElement); + } + } else { // latest element version is version at commit, not deleted + addActiveElement(info, nodeId, indexElement); + } + } + return info; + } + + private boolean elementDeleted(String nodeId, String commitId, Instant time, Instant modified, List refCommitIds) { + List commits = commitIndex.elementDeletedHistory(nodeId, refCommitIds); + for (CommitJson c: commits) { + Instant deletedTime = Instant.from(Formats.FORMATTER.parse(c.getCreated())); + if ((deletedTime.isBefore(time) || c.getId().equals(commitId)) && deletedTime.isAfter(modified)) { + //there's a delete between element last modified time and requested commit time + //or element is deleted at commit + return true; + } + } + return false; + } + + public boolean existingNodeContainsNodeId(FederatedNodeGetInfo info, String nodeId) { + if (!info.getExistingNodeMap().containsKey(nodeId)) { + rejectNotFound(info, nodeId); + return false; + } + return true; + } + + protected void addActiveElement(NodeGetInfo info, String nodeId, ElementJson indexElement) { + info.getActiveElementMap().put(nodeId, indexElement); + } + + protected List getRefCommitIds(Instant time) { + List commitIds = new ArrayList<>(); + + Optional ref = branchDAO.findByBranchId(getContext().getBranchId()); + ref.ifPresent(current -> { + List refCommits = commitDAO.findByRefAndTimestampAndLimit(current, time, 0); + for (Commit c : refCommits) { + commitIds.add(c.getCommitId()); + } + }); + return commitIds; + } + + @Override + public void addExistingElements(NodeGetInfo info, List elements) { + super.addExistingElements(info, elements); + } + + @Override + public NodeGetInfo createNodeGetInfo() { //ToDo :: check + return new FederatedNodeGetInfoImpl(); + } +} diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/AbstractDefaultPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/AbstractDefaultPermissionsDelegate.java similarity index 92% rename from permissions/src/main/java/org/openmbee/mms/permissions/delegation/AbstractDefaultPermissionsDelegate.java rename to federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/AbstractDefaultPermissionsDelegate.java index b591fe2f9..f3086980a 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/AbstractDefaultPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/AbstractDefaultPermissionsDelegate.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.permissions.delegation; +package org.openmbee.mms.federatedpersistence.permissions; import org.openmbee.mms.core.delegation.PermissionsDelegate; import org.openmbee.mms.core.objects.PermissionUpdateRequest; @@ -60,11 +60,12 @@ protected Pair getGroupAndRole(PermissionUpdateRequest.Permission p Optional group = getGroupRepo().findByName(p.getName()); Optional role = getRoleRepo().findByName(p.getRole()); if (!role.isPresent()) { - return Pair.of(group.orElse(null), null); + return null; } if (!group.isPresent()) { group = Optional.of(new Group(p.getName())); - getGroupRepo().save(group.get()); + Group g = getGroupRepo().save(group.get()); + group = Optional.of(g); } return Pair.of(group.get(), role.get()); } diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultBranchPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultBranchPermissionsDelegate.java similarity index 90% rename from permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultBranchPermissionsDelegate.java rename to federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultBranchPermissionsDelegate.java index ded88a232..aaeb0bdb1 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultBranchPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultBranchPermissionsDelegate.java @@ -1,8 +1,6 @@ -package org.openmbee.mms.permissions.delegation; +package org.openmbee.mms.federatedpersistence.permissions; import org.apache.commons.lang3.builder.EqualsBuilder; -import org.openmbee.mms.core.builders.PermissionUpdateResponseBuilder; -import org.openmbee.mms.core.builders.PermissionUpdatesResponseBuilder; import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.objects.PermissionResponse; import org.openmbee.mms.core.objects.PermissionUpdateRequest; @@ -17,18 +15,23 @@ import org.openmbee.mms.data.domains.global.ProjectUserPerm; import org.openmbee.mms.data.domains.global.Role; import org.openmbee.mms.data.domains.global.User; -import org.openmbee.mms.permissions.exceptions.PermissionException; +import org.openmbee.mms.federatedpersistence.permissions.exceptions.PermissionException; import org.openmbee.mms.rdb.repositories.BranchGroupPermRepository; import org.openmbee.mms.rdb.repositories.BranchRepository; import org.openmbee.mms.rdb.repositories.BranchUserPermRepository; import org.openmbee.mms.rdb.repositories.ProjectGroupPermRepository; import org.openmbee.mms.rdb.repositories.ProjectUserPermRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; import org.springframework.data.util.Pair; import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import javax.transaction.Transactional; import java.util.*; +@Component +@Scope(value = "prototype") public class DefaultBranchPermissionsDelegate extends AbstractDefaultPermissionsDelegate { private BranchRepository branchRepo; @@ -68,6 +71,7 @@ public void setProjectUserPermRepo(ProjectUserPermRepository projectUserPermRepo } @Override + @Transactional public boolean hasPermission(String user, Set groups, String privilege) { Optional priv = getPrivRepo().findByName(privilege); @@ -85,6 +89,18 @@ public boolean hasPermission(String user, Set groups, String privilege) return false; } + @Override + @Transactional + public boolean hasGroupPermissions(String group, String privilege) { + for (BranchGroupPerm perm: branchGroupPermRepo.findAllByBranch(branch)) { + if (perm.getGroup().getName().equals(group) && perm.getRole().getPrivileges().stream() + .anyMatch(v -> v.getName().equals(privilege))) { + return true; + } + } + return false; + } + @Override public void initializePermissions(String creator) { initializePermissions(creator, false); @@ -103,7 +119,7 @@ public void initializePermissions(String creator, boolean inherit) { } branch.setInherit(inherit); - branchRepo.save(branch); + branch = branchRepo.save(branch); BranchUserPerm perm = new BranchUserPerm(branch, user.get(), role.get(), false); branchUserPermRepo.save(perm); @@ -119,14 +135,26 @@ public boolean setInherit(boolean isInherit) { return false; } + @Override + public PermissionResponse getInherit() { + PermissionResponse response = PermissionResponse.getDefaultResponse(); + if (branch.isInherit()) { + PermissionResponse.Permission permission = new PermissionResponse.Permission(); + permission.setInherited(branch.isInherit()); + response.getPermissions().add(permission); + } + return response; + } + @Override public void setPublic(boolean isPublic) { //Not supported } @Override + @Transactional public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest req) { - PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); switch(req.getAction()) { case MODIFY: @@ -191,14 +219,15 @@ public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest re } @Override + @Transactional public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest req) { - PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); switch(req.getAction()) { case MODIFY: for (PermissionUpdateRequest.Permission p: req.getPermissions()) { Pair pair = getGroupAndRole(p); - if (pair.getFirst() == null || pair.getSecond() == null) { + if (pair == null) { continue; } Optional exist = branchGroupPermRepo.findByBranchAndGroupAndInheritedIsFalse(branch, pair.getFirst()); @@ -225,7 +254,7 @@ public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest r branchGroupPermRepo.deleteByBranchAndInherited(branch, false); for (PermissionUpdateRequest.Permission p: req.getPermissions()) { Pair pair = getGroupAndRole(p); - if (pair.getFirst() == null || pair.getSecond() == null) { + if (pair == null) { continue; } BranchGroupPerm perm = new BranchGroupPerm(branch, pair.getFirst(), pair.getSecond(), false); @@ -253,6 +282,7 @@ public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest r } @Override + @Transactional public PermissionResponse getUserRoles() { PermissionResponse res = PermissionResponse.getDefaultResponse(); for (BranchUserPerm perm: branchUserPermRepo.findAllByBranch(branch)) { @@ -266,6 +296,7 @@ public PermissionResponse getUserRoles() { } @Override + @Transactional public PermissionResponse getGroupRoles() { PermissionResponse res = PermissionResponse.getDefaultResponse(); for (BranchGroupPerm perm: branchGroupPermRepo.findAllByBranch(branch)) { @@ -365,7 +396,7 @@ public Collection getAll() { @Override public PermissionUpdatesResponse recalculateInheritedPerms() { - PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); + FederatedPermissionUpdatesResponseBuilder responseBuilder = new FederatedPermissionUpdatesResponseBuilder(); Collection branchUserPerms = branchUserPermRepo.findAllByBranchAndInherited(branch, true); branchUserPermRepo.deleteAll(branchUserPerms); diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java new file mode 100644 index 000000000..8a9cf14d6 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultFederatedPermissionsDelegateFactory.java @@ -0,0 +1,77 @@ +package org.openmbee.mms.federatedpersistence.permissions; + +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.data.dao.BranchGDAO; +import org.openmbee.mms.data.dao.OrgDAO; +import org.openmbee.mms.data.dao.ProjectDAO; +import org.openmbee.mms.core.delegation.PermissionsDelegate; +import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; +import org.openmbee.mms.core.exceptions.NotFoundException; +import org.openmbee.mms.data.domains.global.Branch; +import org.openmbee.mms.data.domains.global.Organization; +import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; + +import java.util.Optional; + +public class DefaultFederatedPermissionsDelegateFactory implements PermissionsDelegateFactory { + + private ApplicationContext applicationContext; + private ProjectDAO projectDAO; + private BranchGDAO branchDAO; + private OrgDAO orgDAO; + + @Autowired + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Autowired + public void setProjectDAO(ProjectDAO projectDAO) { + this.projectDAO = projectDAO; + } + + @Autowired + public void setBranchDAO(BranchGDAO branchDAO) { + this.branchDAO = branchDAO; + } + + @Autowired + public void setOrgDAO(OrgDAO orgDAO) { + this.orgDAO = orgDAO; + } + + @Override + public PermissionsDelegate getPermissionsDelegate(ProjectJson project) { + Optional projectOptional = projectDAO.findByProjectId(project.getProjectId()); + + if(projectOptional.isEmpty()) { + throw new NotFoundException("project not found"); + } + + return applicationContext.getBean(DefaultProjectPermissionsDelegate.class, projectOptional.get()); + } + + @Override + public PermissionsDelegate getPermissionsDelegate(OrgJson organization) { + Optional orgOptional = orgDAO.findByOrganizationId(organization.getId()); + if(orgOptional.isEmpty()) { + throw new NotFoundException("org not found"); + } + return applicationContext.getBean(DefaultOrgPermissionsDelegate.class, orgOptional.get()); + } + + @Override + public PermissionsDelegate getPermissionsDelegate(RefJson branch) { + ContextHolder.setContext(null); + Optional branchOptional = branchDAO.findByProject_ProjectIdAndBranchId(branch.getProjectId(), branch.getId()); + if(branchOptional.isEmpty()) { + throw new NotFoundException("branch not found"); + } + return applicationContext.getBean(DefaultBranchPermissionsDelegate.class, branchOptional.get()); + } +} diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultOrgPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultOrgPermissionsDelegate.java similarity index 89% rename from permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultOrgPermissionsDelegate.java rename to federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultOrgPermissionsDelegate.java index 64ed7f179..370217058 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultOrgPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultOrgPermissionsDelegate.java @@ -1,6 +1,5 @@ -package org.openmbee.mms.permissions.delegation; +package org.openmbee.mms.federatedpersistence.permissions; -import org.openmbee.mms.core.builders.PermissionUpdateResponseBuilder; import org.openmbee.mms.core.builders.PermissionUpdatesResponseBuilder; import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.objects.PermissionResponse; @@ -14,18 +13,21 @@ import org.openmbee.mms.data.domains.global.Privilege; import org.openmbee.mms.data.domains.global.Role; import org.openmbee.mms.data.domains.global.User; -import org.openmbee.mms.permissions.exceptions.PermissionException; +import org.openmbee.mms.federatedpersistence.permissions.exceptions.PermissionException; import org.openmbee.mms.rdb.repositories.OrgGroupPermRepository; import org.openmbee.mms.rdb.repositories.OrgUserPermRepository; import org.openmbee.mms.rdb.repositories.OrganizationRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; import org.springframework.data.util.Pair; import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; +import javax.transaction.Transactional; +import java.util.*; +@Component +@Scope(value = "prototype") public class DefaultOrgPermissionsDelegate extends AbstractDefaultPermissionsDelegate { private OrganizationRepository orgRepo; @@ -54,6 +56,7 @@ public void setOrgGroupPermRepo(OrgGroupPermRepository orgGroupPermRepo) { } @Override + @Transactional public boolean hasPermission(String user, Set groups, String privilege) { Optional priv = getPrivRepo().findByName(privilege); @@ -71,6 +74,17 @@ public boolean hasPermission(String user, Set groups, String privilege) return false; } + @Override + public boolean hasGroupPermissions(String group, String privilege) { + for (OrgGroupPerm perm: orgGroupPermRepo.findAllByOrganization_OrganizationId(organization.getOrganizationId())) { + if (perm.getGroup().getName().equals(group) && perm.getRole().getPrivileges().stream() + .anyMatch(v -> v.getName().equals(privilege))) { + return true; + } + } + return false; + } + @Override public void initializePermissions(String creator) { initializePermissions(creator, false); @@ -103,6 +117,12 @@ public boolean setInherit(boolean isInherit) { return false; } + @Override + public PermissionResponse getInherit() { + //Orgs will not inherit + return PermissionResponse.getDefaultResponse(); + } + @Override public void setPublic(boolean isPublic) { organization.setPublic(isPublic); @@ -110,8 +130,9 @@ public void setPublic(boolean isPublic) { } @Override + @Transactional public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest req) { - PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); switch(req.getAction()) { case MODIFY: @@ -176,13 +197,14 @@ public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest re } @Override + @Transactional public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest req) { - PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); switch(req.getAction()) { case MODIFY: for (PermissionUpdateRequest.Permission p: req.getPermissions()) { Pair pair = getGroupAndRole(p); - if (pair.getFirst() == null || pair.getSecond() == null) { + if (pair == null) { continue; } Optional exist = orgGroupPermRepo.findByOrganizationAndGroup(organization, pair.getFirst()); @@ -209,7 +231,7 @@ public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest r orgGroupPermRepo.deleteByOrganization(organization); for (PermissionUpdateRequest.Permission p: req.getPermissions()) { Pair pair = getGroupAndRole(p); - if (pair.getFirst() == null || pair.getSecond() == null) { + if (pair == null) { continue; } OrgGroupPerm p1 = new OrgGroupPerm(organization, pair.getFirst(), pair.getSecond()); diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultProjectPermissionsDelegate.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultProjectPermissionsDelegate.java similarity index 90% rename from permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultProjectPermissionsDelegate.java rename to federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultProjectPermissionsDelegate.java index 5244e15e1..e5beb45e9 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultProjectPermissionsDelegate.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/DefaultProjectPermissionsDelegate.java @@ -1,34 +1,30 @@ -package org.openmbee.mms.permissions.delegation; +package org.openmbee.mms.federatedpersistence.permissions; import org.apache.commons.lang3.builder.EqualsBuilder; -import org.openmbee.mms.core.builders.PermissionUpdateResponseBuilder; -import org.openmbee.mms.core.builders.PermissionUpdatesResponseBuilder; + import org.openmbee.mms.core.config.AuthorizationConstants; import org.openmbee.mms.core.objects.PermissionResponse; import org.openmbee.mms.core.objects.PermissionUpdateRequest; import org.openmbee.mms.core.objects.PermissionUpdateResponse; import org.openmbee.mms.core.objects.PermissionUpdatesResponse; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.data.domains.global.OrgGroupPerm; -import org.openmbee.mms.data.domains.global.OrgUserPerm; -import org.openmbee.mms.data.domains.global.Privilege; -import org.openmbee.mms.data.domains.global.Project; -import org.openmbee.mms.data.domains.global.ProjectGroupPerm; -import org.openmbee.mms.data.domains.global.ProjectUserPerm; -import org.openmbee.mms.data.domains.global.Role; -import org.openmbee.mms.data.domains.global.User; -import org.openmbee.mms.permissions.exceptions.PermissionException; +import org.openmbee.mms.data.domains.global.*; +import org.openmbee.mms.federatedpersistence.permissions.exceptions.PermissionException; import org.openmbee.mms.rdb.repositories.OrgGroupPermRepository; import org.openmbee.mms.rdb.repositories.OrgUserPermRepository; import org.openmbee.mms.rdb.repositories.ProjectGroupPermRepository; import org.openmbee.mms.rdb.repositories.ProjectRepository; import org.openmbee.mms.rdb.repositories.ProjectUserPermRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; import org.springframework.data.util.Pair; import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import javax.transaction.Transactional; import java.util.*; +@Component +@Scope(value = "prototype") public class DefaultProjectPermissionsDelegate extends AbstractDefaultPermissionsDelegate { private ProjectUserPermRepository projectUserPermRepo; @@ -69,6 +65,7 @@ public void setOrgUserPermRepo(OrgUserPermRepository orgUserPermRepo) { } @Override + @Transactional public boolean hasPermission(String user, Set groups, String privilege) { Optional priv = getPrivRepo().findByName(privilege); @@ -88,6 +85,17 @@ public boolean hasPermission(String user, Set groups, String privilege) return false; } + @Override + public boolean hasGroupPermissions(String group, String privilege) { + for (ProjectGroupPerm perm: projectGroupPermRepo.findAllByProject_ProjectId(project.getProjectId())) { + if (perm.getGroup().getName().equals(group) && perm.getRole().getPrivileges().stream(). + anyMatch(v -> v.getName().equals(privilege))) { + return true; + } + } + return false; + } + @Override public void initializePermissions(String creator) { initializePermissions(creator, false); @@ -120,6 +128,18 @@ public boolean setInherit(boolean isInherit) { return false; } + @Override + public PermissionResponse getInherit() { + PermissionResponse response = PermissionResponse.getDefaultResponse(); + if (project.isInherit()) { + PermissionResponse.Permission permission = new PermissionResponse.Permission(); + permission.setInherited(project.isInherit()); + response.getPermissions().add(permission); + } + return response; + } + + @Override public void setPublic(boolean isPublic) { project.setPublic(isPublic); @@ -127,8 +147,9 @@ public void setPublic(boolean isPublic) { } @Override + @Transactional public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest req) { - PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); switch(req.getAction()) { case MODIFY: @@ -193,14 +214,15 @@ public PermissionUpdateResponse updateUserPermissions(PermissionUpdateRequest re } @Override + @Transactional public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest req) { - PermissionUpdateResponseBuilder responseBuilder = new PermissionUpdateResponseBuilder(); + FederatedPermissionsUpdateResponseBuilder responseBuilder = new FederatedPermissionsUpdateResponseBuilder(); switch(req.getAction()) { case MODIFY: for (PermissionUpdateRequest.Permission p: req.getPermissions()) { Pair pair = getGroupAndRole(p); - if (pair.getFirst() == null || pair.getSecond() == null) { + if (pair == null) { continue; } Optional exist = projectGroupPermRepo.findByProjectAndGroupAndInheritedIsFalse(project, pair.getFirst()); @@ -227,7 +249,7 @@ public PermissionUpdateResponse updateGroupPermissions(PermissionUpdateRequest r projectGroupPermRepo.deleteByProjectAndInherited(project, false); for (PermissionUpdateRequest.Permission p: req.getPermissions()) { Pair pair = getGroupAndRole(p); - if (pair.getFirst() == null || pair.getSecond() == null) { + if (pair == null) { continue; } ProjectGroupPerm p1 = new ProjectGroupPerm(project, pair.getFirst(), pair.getSecond(), false); @@ -363,7 +385,7 @@ public Collection getAll() { @Override public PermissionUpdatesResponse recalculateInheritedPerms() { - PermissionUpdatesResponseBuilder responseBuilder = new PermissionUpdatesResponseBuilder(); + FederatedPermissionUpdatesResponseBuilder responseBuilder = new FederatedPermissionUpdatesResponseBuilder(); List projectUserPermList = projectUserPermRepo.findAllByProjectAndInherited(project, true); projectUserPermRepo.deleteAll(projectUserPermList); diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionUpdatesResponseBuilder.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionUpdatesResponseBuilder.java new file mode 100644 index 000000000..9922c4e77 --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionUpdatesResponseBuilder.java @@ -0,0 +1,45 @@ +package org.openmbee.mms.federatedpersistence.permissions; + +import org.openmbee.mms.core.builders.PermissionUpdatesResponseBuilder; +import org.openmbee.mms.core.objects.PermissionUpdateResponse; +import org.openmbee.mms.core.objects.PermissionUpdatesResponse; + +public class FederatedPermissionUpdatesResponseBuilder extends PermissionUpdatesResponseBuilder { + + private FederatedPermissionsUpdateResponseBuilder usersBuilder = new FederatedPermissionsUpdateResponseBuilder(); + private FederatedPermissionsUpdateResponseBuilder groupsBuilder = new FederatedPermissionsUpdateResponseBuilder(); + + + @Override + public FederatedPermissionUpdatesResponseBuilder insertUsers(PermissionUpdateResponse permissionUpdateResponse) { + usersBuilder.insert(permissionUpdateResponse); + return this; + } + + @Override + public FederatedPermissionUpdatesResponseBuilder insertGroups(PermissionUpdateResponse permissionUpdateResponse) { + groupsBuilder.insert(permissionUpdateResponse); + return this; + } + + @Override + public PermissionUpdatesResponse getPermissionUpdatesReponse() { + PermissionUpdatesResponse permissionUpdatesResponse = new PermissionUpdatesResponse(); + permissionUpdatesResponse.setInherit(this.inherit); + permissionUpdatesResponse.setPublic(this.isPublic); + permissionUpdatesResponse.setUsers(usersBuilder.getPermissionUpdateResponse()); + permissionUpdatesResponse.setGroups(groupsBuilder.getPermissionUpdateResponse()); + return permissionUpdatesResponse; + } + + @Override + public FederatedPermissionsUpdateResponseBuilder getUsers() { + return usersBuilder; + } + + @Override + public FederatedPermissionsUpdateResponseBuilder getGroups() { + return groupsBuilder; + } + +} diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionsUpdateResponseBuilder.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionsUpdateResponseBuilder.java new file mode 100644 index 000000000..bde43a17e --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/FederatedPermissionsUpdateResponseBuilder.java @@ -0,0 +1,99 @@ +package org.openmbee.mms.federatedpersistence.permissions; + +import org.openmbee.mms.core.builders.PermissionUpdateResponseBuilder; +import org.openmbee.mms.core.objects.PermissionUpdateResponse; +import org.openmbee.mms.data.domains.global.*; + +import java.util.Collection; + +public class FederatedPermissionsUpdateResponseBuilder extends PermissionUpdateResponseBuilder { + + + public void insertPermissionUpdates_OrgUserPerm(PermissionUpdateResponse.Action action, Collection perms) { + perms.forEach(v -> insertPermissionUpdate(action, v)); + } + + public void insertPermissionUpdate(PermissionUpdateResponse.Action action, OrgUserPerm v) { + if(v == null) + return; + + PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( + action, v.getUser().getUsername(), v.getRole().getName(), v.getOrganization().getOrganizationId(), v.getOrganization().getOrganizationName(), + null, null, null, false); + doInsert(update); + } + + public void insertPermissionUpdates_OrgGroupPerm(PermissionUpdateResponse.Action action, Collection perms) { + perms.forEach(v -> insertPermissionUpdate(action, v)); + } + + public void insertPermissionUpdate(PermissionUpdateResponse.Action action, OrgGroupPerm v) { + if(v == null) + return; + + PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( + action, v.getGroup().getName(), v.getRole().getName(), v.getOrganization().getOrganizationId(), v.getOrganization().getOrganizationName(), + null, null, null, false); + doInsert(update); + } + + public void insertPermissionUpdates_ProjectUserPerm(PermissionUpdateResponse.Action action, Collection perms) { + perms.forEach(v -> insertPermissionUpdate(action, v)); + } + + public void insertPermissionUpdate(PermissionUpdateResponse.Action action, ProjectUserPerm v) { + if(v == null) + return; + + PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( + action, v.getUser().getUsername(), v.getRole().getName(), v.getProject().getOrganization().getOrganizationId(), + v.getProject().getOrganization().getOrganizationName(), v.getProject().getProjectId(), v.getProject().getProjectName(), + null, v.isInherited()); + doInsert(update); + } + + public void insertPermissionUpdates_ProjectGroupPerm(PermissionUpdateResponse.Action action, Collection perms) { + perms.forEach(v -> insertPermissionUpdate(action, v)); + } + + public void insertPermissionUpdate(PermissionUpdateResponse.Action action, ProjectGroupPerm v) { + if(v == null) + return; + + PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( + action, v.getGroup().getName(), v.getRole().getName(), v.getProject().getOrganization().getOrganizationId(), + v.getProject().getOrganization().getOrganizationName(), v.getProject().getProjectId(), v.getProject().getProjectName(), + null, v.isInherited()); + doInsert(update); + } + + public void insertPermissionUpdates_BranchUserPerm(PermissionUpdateResponse.Action action, Collection perms) { + perms.forEach(v -> insertPermissionUpdate(action, v)); + } + + public void insertPermissionUpdate(PermissionUpdateResponse.Action action, BranchUserPerm v) { + if(v == null) + return; + + PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( + action, v.getUser().getUsername(), v.getRole().getName(), v.getBranch().getProject().getOrganization().getOrganizationId(), + v.getBranch().getProject().getOrganization().getOrganizationName(), v.getBranch().getProject().getProjectId(), + v.getBranch().getProject().getProjectName(), v.getBranch().getBranchId(), v.isInherited()); + doInsert(update); + } + + public void insertPermissionUpdates_BranchGroupPerm(PermissionUpdateResponse.Action action, Collection perms) { + perms.forEach(v -> insertPermissionUpdate(action, v)); + } + + public void insertPermissionUpdate(PermissionUpdateResponse.Action action, BranchGroupPerm v) { + if(v == null) + return; + + PermissionUpdateResponse.PermissionUpdate update = new PermissionUpdateResponse.PermissionUpdate( + action, v.getGroup().getName(), v.getRole().getName(), v.getBranch().getProject().getOrganization().getOrganizationId(), + v.getBranch().getProject().getOrganization().getOrganizationName(), v.getBranch().getProject().getProjectId(), + v.getBranch().getProject().getProjectName(),v.getBranch().getBranchId(), v.isInherited()); + doInsert(update); + } +} diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/exceptions/PermissionException.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/exceptions/PermissionException.java similarity index 78% rename from permissions/src/main/java/org/openmbee/mms/permissions/exceptions/PermissionException.java rename to federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/exceptions/PermissionException.java index 5b24c67d5..cf7674088 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/exceptions/PermissionException.java +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/permissions/exceptions/PermissionException.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.permissions.exceptions; +package org.openmbee.mms.federatedpersistence.permissions.exceptions; import org.openmbee.mms.core.exceptions.MMSException; import org.springframework.http.HttpStatus; diff --git a/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/utils/FederatedJsonUtils.java b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/utils/FederatedJsonUtils.java new file mode 100644 index 000000000..8f325a01c --- /dev/null +++ b/federatedpersistence/src/main/java/org/openmbee/mms/federatedpersistence/utils/FederatedJsonUtils.java @@ -0,0 +1,23 @@ +package org.openmbee.mms.federatedpersistence.utils; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +public class FederatedJsonUtils { + + private ObjectMapper objectMapper; + + @Autowired + public void setObjectMapper(ObjectMapper om) { + this.objectMapper = om; + } + + public Map convertToMap(Object obj) { + return objectMapper.convertValue(obj, new TypeReference>() {}); + } +} diff --git a/gradle.properties b/gradle.properties index 64311ee4c..8d975667a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,4 +7,8 @@ springSecurityVersion=5.7.11 springDataVersion=2.7.18 jacksonVersion=2.16.1 jacksonBomVersion=2.16.1 -elasticVersion=7.8.1 \ No newline at end of file +elasticVersion=7.8.1 +jwtTokenVersion=0.10.5 + +org.gradle.daemon=true +org.gradle.parallel=true diff --git a/groups/groups.gradle b/groups/groups.gradle index db3d02ba1..e4dbb7fe3 100644 --- a/groups/groups.gradle +++ b/groups/groups.gradle @@ -1,4 +1,3 @@ dependencies { implementation project(':core') - implementation project(':rdb') } diff --git a/groups/src/main/java/org/openmbee/mms/groups/constants/GroupConstants.java b/groups/src/main/java/org/openmbee/mms/groups/constants/GroupConstants.java index 2e75c4027..148773d16 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/constants/GroupConstants.java +++ b/groups/src/main/java/org/openmbee/mms/groups/constants/GroupConstants.java @@ -6,8 +6,7 @@ public class GroupConstants { public static final String GROUP_NOT_EMPTY = "Group is not empty"; public static final String GROUP_NOT_FOUND = "Group not found"; public static final String INVALID_ACTION = "Invalid action"; - public static final String INVALID_GROUP_NAME = "Invalid group name"; - public static final String NAME = "name"; + public static final String INVALID_GROUP_NAME= "Invalid group name"; public static final String NO_USERS_PROVIDED = "No users provided"; public static final String RESTRICTED_GROUP = "Restricted group"; diff --git a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java index b4c957553..5dc3c6732 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java +++ b/groups/src/main/java/org/openmbee/mms/groups/controllers/LocalGroupsController.java @@ -3,58 +3,55 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.util.ArrayList; -import java.util.List; +import java.util.Collection; import java.util.stream.Collectors; import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.dao.GroupPersistence; +import org.openmbee.mms.core.dao.UserGroupsPersistence; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.ConflictException; import org.openmbee.mms.core.exceptions.NotFoundException; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.data.domains.global.User; import org.openmbee.mms.groups.constants.GroupConstants; import org.openmbee.mms.groups.objects.*; import org.openmbee.mms.groups.services.GroupValidationService; -import org.openmbee.mms.rdb.repositories.GroupRepository; -import org.openmbee.mms.rdb.repositories.UserRepository; +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.UserJson; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Sort; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/groups") @Tag(name = "Groups") -@Transactional public class LocalGroupsController { - private GroupRepository groupRepository; + private GroupPersistence groupPersistence; + private UserGroupsPersistence userGroupsPersistence; private GroupValidationService groupValidationService; - private UserRepository userRepository; @Autowired - public void setGroupRepository(GroupRepository groupRepository) { - this.groupRepository = groupRepository; + public void setGroupPersistence(GroupPersistence groupPersistence) { + this.groupPersistence = groupPersistence; } @Autowired - public void setGroupValidationService(GroupValidationService groupValidationService) { - this.groupValidationService = groupValidationService; + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; } @Autowired - public void setUserRepository(UserRepository userRepository) { - this.userRepository = userRepository; + public void setGroupValidationService(GroupValidationService groupValidationService) { + this.groupValidationService = groupValidationService; } @PutMapping("/{group}") @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) @ResponseBody public void createLocalGroup(@PathVariable String group) { - Group groupObj = groupRepository.findByName(group).orElse(null); - if (groupObj != null) { + GroupJson groupJson = groupPersistence.findByName(group).orElse(null); + if (groupJson != null) { throw new ConflictException(GroupConstants.GROUP_ALREADY_EXISTS); } @@ -62,38 +59,37 @@ public void createLocalGroup(@PathVariable String group) { throw new BadRequestException(GroupConstants.INVALID_GROUP_NAME); } - groupObj = new Group(); - groupObj.setName(group); - groupRepository.saveAndFlush(groupObj); + groupJson = new GroupJson(); + groupJson.setName(group); + groupPersistence.save(groupJson); } @GetMapping @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) public GroupsResponse getAllGroups() { - List groups = groupRepository.findAll(Sort.by(GroupConstants.NAME)); + Collection groups = groupPersistence.findAll(); GroupsResponse response = new GroupsResponse(); - response.setGroups(groups.stream().map(Group::getName).collect(Collectors.toList())); + response.setGroups(groups.stream().map(GroupJson::getName).collect(Collectors.toList())); return response; } @GetMapping("/{group}") @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) - @Transactional public GroupResponse getGroup(@PathVariable String group) { if(groupValidationService.isRestrictedGroup(group)) { throw new BadRequestException(GroupConstants.RESTRICTED_GROUP); } - return new GroupResponse(groupRepository.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND))); + return new GroupResponse(groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)), + userGroupsPersistence.findUsersInGroup(group).stream().map(UserJson::getUsername).collect(Collectors.toList())); } @DeleteMapping("/{group}") @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) @ResponseBody - @Transactional public void deleteLocalGroup(@PathVariable String group) { - Group groupObj = groupRepository.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)); - if (groupValidationService.canDeleteGroup(groupObj)) { - this.groupRepository.delete(groupObj); + GroupJson groupJson = groupPersistence.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)); + if (groupValidationService.canDeleteGroup(groupJson)) { + groupPersistence.delete(groupJson); } else { throw new BadRequestException(GroupConstants.GROUP_NOT_EMPTY); } @@ -101,7 +97,6 @@ public void deleteLocalGroup(@PathVariable String group) { @PostMapping("/{group}/users") @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) - @Transactional public GroupUpdateResponse updateGroupUsers(@PathVariable String group, @RequestBody GroupUpdateRequest groupUpdateRequest) { @@ -118,36 +113,28 @@ public GroupUpdateResponse updateGroupUsers(@PathVariable String group, throw new BadRequestException(GroupConstants.RESTRICTED_GROUP); } - Group groupObj = groupRepository.findByName(group).orElseThrow(() -> new NotFoundException(GroupConstants.GROUP_NOT_FOUND)); + if(groupPersistence.findByName(group).isEmpty()) { + throw new NotFoundException(GroupConstants.GROUP_NOT_FOUND); + } GroupUpdateResponse response = new GroupUpdateResponse(); response.setAdded(new ArrayList<>()); response.setRemoved(new ArrayList<>()); response.setRejected(new ArrayList<>()); response.setGroup(group); - groupUpdateRequest.getUsers().forEach(newUser -> { - User user = userRepository.findByUsernameIgnoreCase(newUser).orElse(null); - if (user != null) { - - if (groupUpdateRequest.getAction() == Action.ADD) { - if(groupObj.getUsers().contains(user)){ - response.getRejected().add(newUser); - return; - } - user.getGroups().add(groupObj); - response.getAdded().add(user.getUsername()); - } else { //REMOVE - if(!groupObj.getUsers().contains(user)){ - response.getRejected().add(newUser); - return; - } - user.getGroups().remove(groupObj); - response.getRemoved().add(user.getUsername()); + groupUpdateRequest.getUsers().forEach(user -> { + if (groupUpdateRequest.getAction() == Action.ADD) { + if (!userGroupsPersistence.addUserToGroup(group, user)) { + response.getRejected().add(user); + return; + } + response.getAdded().add(user); + } else { //REMOVE + if (!userGroupsPersistence.removeUserFromGroup(group, user)) { + response.getRejected().add(user); + return; } - userRepository.save(user); - } else { - //Reject users that don't exist - response.getRejected().add(newUser); + response.getRemoved().add(user); } }); return response; diff --git a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupResponse.java b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupResponse.java index edc363694..d6317c14f 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/objects/GroupResponse.java +++ b/groups/src/main/java/org/openmbee/mms/groups/objects/GroupResponse.java @@ -2,11 +2,9 @@ import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; -import java.util.stream.Collectors; +import java.util.Collection; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.data.domains.global.User; +import org.openmbee.mms.json.GroupJson; public class GroupResponse { @@ -14,13 +12,13 @@ public class GroupResponse { private String group; @Schema(nullable = true) - private List users; + private Collection users; public GroupResponse(){} - public GroupResponse(Group group){ + public GroupResponse(GroupJson group, Collection users){ this.group = group.getName(); - this.users = group.getUsers().stream().map(User::getUsername).collect(Collectors.toList()); + this.users = users; } public String getGroup() { @@ -31,11 +29,11 @@ public void setGroup(String group) { this.group = group; } - public List getUsers() { + public Collection getUsers() { return users; } - public void setUsers(List users) { + public void setUsers(Collection users) { this.users = users; } } diff --git a/groups/src/main/java/org/openmbee/mms/groups/services/GroupValidationService.java b/groups/src/main/java/org/openmbee/mms/groups/services/GroupValidationService.java index ae40f307c..01459d0dc 100644 --- a/groups/src/main/java/org/openmbee/mms/groups/services/GroupValidationService.java +++ b/groups/src/main/java/org/openmbee/mms/groups/services/GroupValidationService.java @@ -1,6 +1,8 @@ package org.openmbee.mms.groups.services; -import org.openmbee.mms.data.domains.global.Group; +import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.json.GroupJson; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Set; @@ -14,6 +16,12 @@ public class GroupValidationService { private static final Set RESTRICTED_NAMES = Set.of(MMSADMIN, EVERYONE); private final Pattern VALID_GROUP_NAME_PATTERN = Pattern.compile("^[ -~]+"); + private UserGroupsPersistence userGroupsPersistence; + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; + } public boolean isRestrictedGroup(String groupName) { return RESTRICTED_NAMES.contains(groupName); @@ -25,8 +33,10 @@ public boolean isValidGroupName(String groupName) { VALID_GROUP_NAME_PATTERN.matcher(groupName).matches(); } - public boolean canDeleteGroup(Group group) { - return !isRestrictedGroup(group.getName()) && - (group.getUsers() == null || group.getUsers().isEmpty()); + public boolean canDeleteGroup(GroupJson group) { + if(isRestrictedGroup(group.getName())) { + return false; + } + return userGroupsPersistence.findUsersInGroup(group.getName()).isEmpty(); } } diff --git a/json/src/main/java/org/openmbee/mms/json/BaseJson.java b/json/src/main/java/org/openmbee/mms/json/BaseJson.java index 4c5b0e52f..b99b8b8e7 100644 --- a/json/src/main/java/org/openmbee/mms/json/BaseJson.java +++ b/json/src/main/java/org/openmbee/mms/json/BaseJson.java @@ -22,6 +22,7 @@ public class BaseJson extends HashMap { public static final String CREATED = "_created"; public static final String COMMITID = "_commitId"; public static final String TYPE = "type"; + public static final String IS_DELETED = "_deleted"; public String getId() { return (String) this.get(ID); @@ -157,6 +158,18 @@ public T setCommitId(String commitId) { return (T) this; } + @JsonProperty(IS_DELETED) + public String getIsDeleted() { + return (String) this.get(IS_DELETED); + } + + @SuppressWarnings("unchecked") + @JsonProperty(IS_DELETED) + public T setIsDeleted(String deleted) { + this.put(IS_DELETED, deleted); + return (T) this; + } + public boolean isPartialOf(Map o) { return isPartial(this, o); } diff --git a/json/src/main/java/org/openmbee/mms/json/GroupJson.java b/json/src/main/java/org/openmbee/mms/json/GroupJson.java new file mode 100644 index 000000000..f22a57a1b --- /dev/null +++ b/json/src/main/java/org/openmbee/mms/json/GroupJson.java @@ -0,0 +1,11 @@ +package org.openmbee.mms.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.media.Schema; + +@JsonIgnoreProperties({BaseJson.COMMITID, BaseJson.REFID, BaseJson.PROJECTID, BaseJson.TYPE, + BaseJson.ID, "empty"}) +@Schema(name = "Group", requiredProperties = {BaseJson.NAME}) +public class GroupJson extends BaseJson { + +} diff --git a/json/src/main/java/org/openmbee/mms/json/ProjectJson.java b/json/src/main/java/org/openmbee/mms/json/ProjectJson.java index a829788c3..7fdcd852a 100644 --- a/json/src/main/java/org/openmbee/mms/json/ProjectJson.java +++ b/json/src/main/java/org/openmbee/mms/json/ProjectJson.java @@ -10,6 +10,7 @@ public class ProjectJson extends BaseJson { public static final String ORGID = "orgId"; public static final String PROJECTTYPE = "schema"; + public static final String DELETED = "deleted"; @Override public String getProjectId() { diff --git a/json/src/main/java/org/openmbee/mms/json/UserJson.java b/json/src/main/java/org/openmbee/mms/json/UserJson.java new file mode 100644 index 000000000..24bc6b9fe --- /dev/null +++ b/json/src/main/java/org/openmbee/mms/json/UserJson.java @@ -0,0 +1,82 @@ +package org.openmbee.mms.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.media.Schema; + +@JsonIgnoreProperties({BaseJson.COMMITID, BaseJson.REFID, BaseJson.PROJECTID, BaseJson.TYPE, BaseJson.IS_DELETED, + BaseJson.NAME, BaseJson.ID, UserJson.PASSWORD, "empty"}) +@Schema(name = "User", requiredProperties = {UserJson.USERNAME}) +public class UserJson extends BaseJson { + + public static final String USERNAME = "username"; + public static final String FIRST_NAME = "firstName"; + public static final String LAST_NAME = "lastName"; + public static final String ADMIN = "admin"; + public static final String EMAIL = "email"; + public static final String PASSWORD = "password"; + public static final String ENABLED = "enabled"; + + public String getUsername() { + return (String) get(USERNAME); + } + + public UserJson setUsername(String username) { + put(USERNAME, username); + return this; + } + + public String getFirstName() { + return (String) get(FIRST_NAME); + } + + public UserJson setFirstName(String firstName) { + put(FIRST_NAME, firstName); + return this; + } + + public String getLastName() { + return (String) get(LAST_NAME); + } + + public UserJson setLastName(String lastName) { + put(LAST_NAME, lastName); + return this; + } + + public Boolean isAdmin() { + return (Boolean) get(ADMIN); + } + + public UserJson setAdmin(Boolean admin) { + put(ADMIN, admin); + return this; + } + + public String getEmail() { + return (String) get(EMAIL); + } + + public UserJson setEmail(String email) { + put(EMAIL, email); + return this; + } + + public String getPassword() { + return (String) get(PASSWORD); + } + + public UserJson setPassword(String password) { + put(PASSWORD, password); + return this; + } + + public Boolean isEnabled() { + return (Boolean) get(ENABLED); + } + + public UserJson setEnabled(Boolean enabled) { + put(ENABLED, enabled); + return this; + } + +} diff --git a/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterHelper.java b/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterHelper.java index 44d50e208..795b0c9c5 100644 --- a/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterHelper.java +++ b/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterHelper.java @@ -4,11 +4,13 @@ import org.openmbee.mms.jupyter.JupyterConstants; import org.openmbee.mms.jupyter.JupyterNodeType; import org.springframework.stereotype.Component; +import org.openmbee.mms.core.utils.ElementUtils; @Component -public class JupyterHelper { +public class JupyterHelper implements ElementUtils{ - public static JupyterNodeType getNodeType(ElementJson e) { + @Override + public JupyterNodeType getNodeType(ElementJson e) { if (e.containsKey(JupyterConstants.CELLTYPE)) return JupyterNodeType.CELL; return JupyterNodeType.NOTEBOOK; diff --git a/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterNodeService.java b/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterNodeService.java index ab3796061..cfcc1ed8f 100644 --- a/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterNodeService.java +++ b/jupyter/src/main/java/org/openmbee/mms/jupyter/services/JupyterNodeService.java @@ -5,16 +5,14 @@ import java.util.Map; import java.util.UUID; -import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.core.objects.ElementsRequest; import org.openmbee.mms.core.objects.ElementsResponse; import org.openmbee.mms.core.objects.Rejection; import org.openmbee.mms.core.services.NodeChangeInfo; -import org.openmbee.mms.crud.services.NodeOperation; +import org.openmbee.mms.crud.domain.JsonDomain; import org.openmbee.mms.json.ElementJson; import org.openmbee.mms.crud.services.DefaultNodeService; import org.openmbee.mms.core.services.NodeService; -import org.openmbee.mms.data.domains.scoped.Node; import org.openmbee.mms.jupyter.JupyterConstants; import org.openmbee.mms.jupyter.JupyterNodeType; import org.openmbee.mms.jupyter.controllers.NotebooksRequest; @@ -32,21 +30,11 @@ public void setJupyterHelper(JupyterHelper jupyterHelper) { this.jupyterHelper = jupyterHelper; } - @Override - public void extraProcessPostedElement(ElementJson element, Node node, NodeChangeInfo info) { - node.setNodeType(JupyterHelper.getNodeType(element).getValue()); - } - public ElementsResponse readNotebooks(String projectId, String refId, String elementId, Map params) { ElementsRequest req = new ElementsRequest(); List reqs = new ArrayList<>(); if (elementId == null || elementId.isEmpty()) { - ContextHolder.setContext(projectId, refId); - List notebooks = this.nodeRepository - .findAllByDeletedAndNodeType(false, JupyterNodeType.NOTEBOOK.getValue()); - for (Node n : notebooks) { - reqs.add((new ElementJson()).setId(n.getNodeId())); - } + reqs.addAll(getNodePersistence().findAllByNodeType(projectId, refId, null,JupyterNodeType.NOTEBOOK.getValue())); } else { reqs.add((new ElementJson()).setId(elementId)); } @@ -72,7 +60,7 @@ public ElementsResponse readNotebooks(String projectId, String refId, ElementsRe ElementsRequest req2 = new ElementsRequest(); req2.setElements(req2s); ElementsResponse cells = this.read(projectId, refId, req2, params); - Map cellmap = NodeOperation.convertJsonToMap(cells.getElements()); + Map cellmap = JsonDomain.convertJsonToMap(cells.getElements()); e.put(JupyterConstants.CELLS, order((List)e.get(JupyterConstants.CELLS), cellmap)); } res.getElements().removeAll(nonNotebooks); diff --git a/ldap/ldap.gradle b/ldap/ldap.gradle index 437b9aee2..588391d59 100644 --- a/ldap/ldap.gradle +++ b/ldap/ldap.gradle @@ -3,3 +3,9 @@ dependencies { implementation commonDependencies.'spring-security-ldap' } + +tasks { + processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} \ No newline at end of file diff --git a/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java b/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java index 03ff63141..fa3666b3b 100644 --- a/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java +++ b/ldap/src/main/java/org/openmbee/mms/ldap/LdapSecurityConfig.java @@ -1,14 +1,14 @@ package org.openmbee.mms.ldap; +import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.dao.GroupPersistence; +import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.core.dao.UserPersistence; +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.UserJson; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.*; - -import org.openmbee.mms.core.config.AuthorizationConstants; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.rdb.repositories.GroupRepository; -import org.openmbee.mms.rdb.repositories.UserRepository; -import org.openmbee.mms.data.domains.global.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -92,18 +92,23 @@ public class LdapSecurityConfig { @Value("${ldap.user.search.filter:(uid={0})}") private String userSearchFilter; + private UserPersistence userPersistence; + private GroupPersistence groupPersistence; + private UserGroupsPersistence userGroupsPersistence; - private UserRepository userRepository; - private GroupRepository groupRepository; + @Autowired + public void setUserPersistence(UserPersistence userPersistence) { + this.userPersistence = userPersistence; + } @Autowired - public void setUserRepository(UserRepository userRepository) { - this.userRepository = userRepository; + public void setGroupPersistence(GroupPersistence groupPersistence) { + this.groupPersistence = groupPersistence; } @Autowired - public void setGroupRepository(GroupRepository groupRepository) { - this.groupRepository = groupRepository; + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; } @Autowired @@ -154,16 +159,16 @@ private CustomLdapAuthoritiesPopulator(BaseLdapPathContextSource ldapContextSour public Collection getGrantedAuthorities( DirContextOperations userData, String username) { logger.debug("Populating authorities using LDAP"); - Optional userOptional = userRepository.findByUsernameIgnoreCase(username); + Optional userOptional = userPersistence.findByUsername(username); if (userOptional.isEmpty()) { logger.info("No user record for {} in the userRepository, creating...", userData.getDn()); - User newUser = createLdapUser(userData); + UserJson newUser = createLdapUser(userData); userOptional = Optional.of(newUser); } - User user = userOptional.get(); - if (user.getModified().isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { + UserJson user = userOptional.get(); + if (user.getModified() != null && Instant.parse(user.getModified()).isBefore(Instant.now().minus(userAttributesUpdate, ChronoUnit.HOURS))) { saveLdapUser(userData, user); } user.setPassword(null); @@ -175,11 +180,10 @@ public Collection getGrantedAuthorities( userDnBuilder.append(providerBase); } String userDn = userDnBuilder.toString(); - - List definedGroups = groupRepository.findAll(); + Collection definedGroups = groupPersistence.findAll(); OrFilter orFilter = new OrFilter(); - for (Group definedGroup : definedGroups) { + for (GroupJson definedGroup : definedGroups) { orFilter.or(new EqualsFilter(groupRoleAttribute, definedGroup.getName())); } @@ -196,28 +200,29 @@ public Collection getGrantedAuthorities( .searchForSingleAttributeValues(groupSearchBase, filter, new Object[]{""}, groupRoleAttribute); logger.debug("LDAP search result: {}", Arrays.toString(memberGroups.toArray())); - Set addGroups = new HashSet<>(); + userPersistence.save(user); + //Add groups to user + + Set addGroups = new HashSet<>(); + for (String memberGroup : memberGroups) { - Optional group = groupRepository.findByName(memberGroup); + Optional group = groupPersistence.findByName(memberGroup); + group.ifPresent(g -> userGroupsPersistence.addUserToGroup(g.getName(), user.getUsername())); group.ifPresent(addGroups::add); } if (logger.isDebugEnabled()) { if ((long) addGroups.size() > 0) { - addGroups.forEach(group -> { - logger.debug("Group received: {}", group.getName()); - }); + addGroups.forEach(group -> logger.debug("Group received: {}", group.getName())); } else { logger.debug("No configured groups returned from LDAP"); } } - user.setGroups(addGroups); - userRepository.save(user); List auths = AuthorityUtils .createAuthorityList(memberGroups.toArray(new String[0])); - if (user.isAdmin()) { + if (Boolean.TRUE.equals(user.isAdmin())) { auths.add(new SimpleGrantedAuthority(AuthorizationConstants.MMSADMIN)); } auths.add(new SimpleGrantedAuthority(AuthorizationConstants.EVERYONE)); @@ -265,7 +270,7 @@ public LdapContextSource contextSource() { return contextSource; } - private User saveLdapUser(DirContextOperations userData, User saveUser) { + private UserJson saveLdapUser(DirContextOperations userData, UserJson saveUser) { if (saveUser.getEmail() == null || !saveUser.getEmail().equals(userData.getStringAttribute(userAttributesEmail)) ) { @@ -285,15 +290,13 @@ private User saveLdapUser(DirContextOperations userData, User saveUser) { return saveUser; } - private User createLdapUser(DirContextOperations userData) { + private UserJson createLdapUser(DirContextOperations userData) { String username = userData.getStringAttribute(userAttributesUsername); logger.debug("Creating user for {} using LDAP", username); - User user = saveLdapUser(userData, new User()); + UserJson user = saveLdapUser(userData, new UserJson()); user.setUsername(username); user.setEnabled(true); user.setAdmin(false); - userRepository.save(user); - - return user; + return userPersistence.save(user); } } \ No newline at end of file diff --git a/localuser/localuser.gradle b/localuser/localuser.gradle index 79103fa6f..eefabc146 100644 --- a/localuser/localuser.gradle +++ b/localuser/localuser.gradle @@ -1,3 +1,9 @@ dependencies { - implementation project(':rdb') + implementation project(':core') } + +tasks { + processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} \ No newline at end of file diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/config/AuthProviderConfig.java b/localuser/src/main/java/org/openmbee/mms/localuser/config/AuthProviderConfig.java index b96c10577..eb8b08e4e 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/config/AuthProviderConfig.java +++ b/localuser/src/main/java/org/openmbee/mms/localuser/config/AuthProviderConfig.java @@ -15,7 +15,7 @@ @Configuration public class AuthProviderConfig { - private static Logger logger = LoggerFactory.getLogger(LocalUserSecurityConfig.class); + private static final Logger logger = LoggerFactory.getLogger(AuthProviderConfig.class); private UserDetailsServiceImpl userDetailsService; private PasswordEncoder passwordEncoder; diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java b/localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java new file mode 100644 index 000000000..05cf7cffc --- /dev/null +++ b/localuser/src/main/java/org/openmbee/mms/localuser/config/UserPasswordRulesConfig.java @@ -0,0 +1,16 @@ +package org.openmbee.mms.localuser.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@PropertySource(value = "classpath:application.properties", ignoreResourceNotFound = true) +public class UserPasswordRulesConfig { + @Value("${user.password.allow_self_set_when_blank:false}") + private boolean allowSelfSetPasswordsWhenBlank; + + public boolean isAllowSelfSetPasswordsWhenBlank() { + return allowSelfSetPasswordsWhenBlank; + } +} diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/controllers/LocalUserController.java b/localuser/src/main/java/org/openmbee/mms/localuser/controllers/LocalUserController.java index 4b67a4fc1..858ea4a1f 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/controllers/LocalUserController.java +++ b/localuser/src/main/java/org/openmbee/mms/localuser/controllers/LocalUserController.java @@ -6,7 +6,7 @@ import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.exceptions.UnauthorizedException; import org.openmbee.mms.core.utils.AuthenticationUtils; -import org.openmbee.mms.data.domains.global.User; +import org.openmbee.mms.json.UserJson; import org.openmbee.mms.localuser.security.UserCreateRequest; import org.openmbee.mms.localuser.security.UserDetailsServiceImpl; import org.openmbee.mms.localuser.security.UsersResponse; @@ -15,10 +15,14 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; -import java.util.List; +import java.util.Collection; @RestController @Tag(name = "Auth") @@ -47,7 +51,7 @@ public UserCreateRequest createUser(@RequestBody UserCreateRequest req) { @PreAuthorize("isAuthenticated()") public UsersResponse getUsers(@RequestParam(required = false) String user) { UsersResponse res = new UsersResponse(); - List users = new ArrayList<>(); + Collection users = new ArrayList<>(); if (user != null) { users.add(userDetailsService.loadUserByUsername(user).getUser()); } else { diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsImpl.java b/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsImpl.java index f69f9a705..038e5d4da 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsImpl.java +++ b/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsImpl.java @@ -4,31 +4,33 @@ import java.util.Collection; import org.openmbee.mms.core.config.AuthorizationConstants; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.data.domains.global.User; +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.UserJson; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class UserDetailsImpl implements UserDetails { - private final User user; + private final UserJson user; + private final Collection groups; - public UserDetailsImpl(User user) { + public UserDetailsImpl(UserJson user, Collection groups) { this.user = user; + this.groups = groups; } @Override public Collection getAuthorities() { - Collection groups = user.getGroups(); + Collection authorities = new ArrayList<>(); if (groups != null) { - for (Group group : groups) { + for (GroupJson group : groups) { authorities.add(new SimpleGrantedAuthority(group.getName())); } } - if (user.isAdmin()) { + if (Boolean.TRUE.equals(user.isAdmin())) { authorities.add(new SimpleGrantedAuthority(AuthorizationConstants.MMSADMIN)); } authorities.add(new SimpleGrantedAuthority(AuthorizationConstants.EVERYONE)); @@ -62,10 +64,10 @@ public boolean isCredentialsNonExpired() { @Override public boolean isEnabled() { - return user.getEnabled(); + return user.isEnabled(); } - public User getUser() { + public UserJson getUser() { return user; } diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java b/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java index cd9bc1105..893692b9a 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java +++ b/localuser/src/main/java/org/openmbee/mms/localuser/security/UserDetailsServiceImpl.java @@ -1,27 +1,35 @@ package org.openmbee.mms.localuser.security; -import java.util.List; +import java.util.Collection; import java.util.Optional; +import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.core.dao.UserPersistence; import org.openmbee.mms.core.exceptions.ForbiddenException; -import org.openmbee.mms.rdb.repositories.UserRepository; -import org.openmbee.mms.data.domains.global.User; +import org.openmbee.mms.json.UserJson; +import org.openmbee.mms.localuser.config.UserPasswordRulesConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service public class UserDetailsServiceImpl implements UserDetailsService { - private UserRepository userRepository; + private UserPersistence userPersistence; + private UserGroupsPersistence userGroupsPersistence; private PasswordEncoder passwordEncoder; + private UserPasswordRulesConfig userPasswordRulesConfig; @Autowired - public void setUserRepository(UserRepository userRepository) { - this.userRepository = userRepository; + public void setUserPersistence(UserPersistence userPersistence) { + this.userPersistence = userPersistence; + } + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; } @Autowired @@ -29,53 +37,57 @@ public void setPasswordEncoder(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } + @Autowired + public void setUserPasswordRulesConfig(UserPasswordRulesConfig userPasswordRulesConfig) { + this.userPasswordRulesConfig = userPasswordRulesConfig; + } + @Override public UserDetailsImpl loadUserByUsername(String username) throws UsernameNotFoundException { - Optional user = userRepository.findByUsernameIgnoreCase(username); + Optional user = userPersistence.findByUsername(username); - if (!user.isPresent()) { + if (user.isEmpty()) { throw new UsernameNotFoundException( String.format("No user found with username '%s'.", username)); } - return new UserDetailsImpl(user.get()); + return new UserDetailsImpl(user.get(), userGroupsPersistence.findGroupsAssignedToUser(username)); } - public List getUsers() { - return userRepository.findAll(); + public Collection getUsers() { + return userPersistence.findAll(); } - @Transactional - public User register(UserCreateRequest req) { - User user = new User(); + public UserJson register(UserCreateRequest req) { + UserJson user = new UserJson(); + user.setUsername(req.getUsername()); user.setEmail(req.getEmail()); user.setFirstName(req.getFirstname()); user.setLastName(req.getLastname()); - user.setUsername(req.getUsername()); user.setPassword(encodePassword(req.getPassword())); user.setEnabled(true); user.setAdmin(req.isAdmin()); - return userRepository.save(user); + return userPersistence.save(user); } - @Transactional public void changeUserPassword(String username, String password, boolean asAdmin) { - Optional userOptional = userRepository.findByUsernameIgnoreCase(username); - if(! userOptional.isPresent()) { + Optional userOptional = userPersistence.findByUsername(username); + if(userOptional.isEmpty()) { throw new UsernameNotFoundException( String.format("No user found with username '%s'.", username)); } - User user = userOptional.get(); - if(!asAdmin && (user.getPassword() == null || user.getPassword().isEmpty())) { + UserJson user = userOptional.get(); + if(!asAdmin && !userPasswordRulesConfig.isAllowSelfSetPasswordsWhenBlank() && + (user.getPassword() == null || user.getPassword().isBlank())) { throw new ForbiddenException("Cannot change or set passwords for external users."); } //TODO password strength test? user.setPassword(encodePassword(password)); - userRepository.save(user); + userPersistence.save(user); } private String encodePassword(String password) { - return (password != null && !password.isEmpty()) ? passwordEncoder.encode(password) : null; + return (password != null && !password.isBlank()) ? passwordEncoder.encode(password) : null; } } diff --git a/localuser/src/main/java/org/openmbee/mms/localuser/security/UsersResponse.java b/localuser/src/main/java/org/openmbee/mms/localuser/security/UsersResponse.java index f119e7cf8..db931cb8d 100644 --- a/localuser/src/main/java/org/openmbee/mms/localuser/security/UsersResponse.java +++ b/localuser/src/main/java/org/openmbee/mms/localuser/security/UsersResponse.java @@ -1,17 +1,17 @@ package org.openmbee.mms.localuser.security; -import java.util.List; -import org.openmbee.mms.data.domains.global.User; +import java.util.Collection; +import org.openmbee.mms.json.UserJson; public class UsersResponse { - private List users; + private Collection users; - public List getUsers() { + public Collection getUsers() { return users; } - public void setUsers(List users) { + public void setUsers(Collection users) { this.users = users; } } diff --git a/msosa/README.md b/msosa/README.md new file mode 100644 index 000000000..85cf02eb7 --- /dev/null +++ b/msosa/README.md @@ -0,0 +1 @@ +## MSOSA \ No newline at end of file diff --git a/msosa/msosa.gradle b/msosa/msosa.gradle new file mode 100644 index 000000000..8884015e7 --- /dev/null +++ b/msosa/msosa.gradle @@ -0,0 +1,6 @@ +dependencies { + implementation project(':crud') + implementation project(':view') + + testImplementation commonDependencies.'spring-boot-starter-test' +} diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/MsosaConstants.java b/msosa/src/main/java/org/openmbee/mms/msosa/MsosaConstants.java new file mode 100644 index 000000000..86e9ae2b9 --- /dev/null +++ b/msosa/src/main/java/org/openmbee/mms/msosa/MsosaConstants.java @@ -0,0 +1,136 @@ +package org.openmbee.mms.msosa; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class MsosaConstants { + + private MsosaConstants() { + throw new IllegalStateException("msosa Constants"); + } + + public static final String AGGREGATION = "aggregation"; + public static final String ASSOCIATIONENDID = "associationEndId"; + public static final String ASSOCIATIONID = "associationId"; + public static final String CLASSIFIERIDS = "classifierIds"; + public static final String CLIENTDEPENDENCYIDS = "clientDependencyIds"; + public static final String COLLABORATIONUSEIDS = "collaborationUseIds"; + public static final String COMMITID = "commitId"; + public static final String DATATYPEID = "datatypeId"; + public static final String DEFAULTVALUE = "defaultValue"; + public static final String DEFININGFEATUREID = "definingFeatureId"; + public static final String DEPLOYMENTIDS = "deploymentIds"; + public static final String DESCRIPTION = "description"; + public static final String DOCUMENTATION = "documentation"; + public static final String ELEMENTIMPORTIDS = "elementImportIds"; + public static final String ENDAPATHIDS = "endAPathIds"; + public static final String ENDBPATHIDS = "endBPathIds"; + public static final String ENDIDS = "endIds"; + public static final String GENERALIZATIONIDS = "generalizationIds"; + public static final String INTERFACEID = "interfaceId"; + public static final String ISABSTRACT = "isAbstract"; + public static final String ISDERIVED = "isDerived"; + public static final String ISDERIVEDUNION = "isDerivedUnion"; + public static final String ISFINALSPECIALIZATION = "isFinalSpecialization"; + public static final String ISID = "isID"; + public static final String ISLEAF = "isLeaf"; + public static final String ISORDERED = "isOrdered"; + public static final String ISREADONLY = "isReadOnly"; + public static final String ISSTATIC = "isStatic"; + public static final String ISUNIQUE = "isUnique"; + public static final String LOWERVALUE = "lowerValue"; + public static final String KEYWORDS = "keywords"; + public static final String MDEXTENSIONSIDS = "mdExtensionsIds"; + public static final String MEMBERENDIDS = "memberEndIds"; + public static final String METADATA = "metadata"; + public static final String MOUNTEDELEMENTPROJECTID = "mountedElementProjectId"; + public static final String MOUNTEDREFID = "mountedRefId"; + public static final String NAME = "name"; + public static final String NAMEEXPRESSION = "nameExpression"; + public static final String NAVIGABLEOWNEDENDIDS = "navigableOwnedEndIds"; + public static final String NONE = "none"; + public static final String OWNEDATTRIBUTEIDS = "ownedAttributeIds"; + public static final String OWNEDENDIDS = "ownedEndIds"; + public static final String OWNERID = "ownerId"; + public static final String PACKAGEIMPORTIDS = "packageImportIds"; + public static final String PACKAGEMERGEIDS = "packageMergeIds"; + public static final String PARENTID = "_parentId"; + public static final String POWERTYPEEXTENTIDS = "powertypeExtentIds"; + public static final String PROPERTY = "Property"; + public static final String PROPERTYID = "propertyId"; + public static final String PROPERTYTYPE = "propertyType"; + public static final String PROFILEAPPLICATIONIDS = "profileApplicationIds"; + public static final String QUALIFIERIDS = "qualifierIds"; + public static final String REDEFINEDCLASSIFIERIDS = "redefinedClassifierIds"; + public static final String REDEFINEDPROPERTYIDS = "redefinedPropertyIds"; + public static final String REPRESENTATIONID = "representationId"; + public static final String SPECIFICATION = "specification"; + public static final String STEREOTYPEDELEMENTID = "stereotypedElementId"; + public static final String SUBSETTEDPROPERTYIDS = "subsettedPropertyIds"; + public static final String SUBSTITUTIONIDS = "substitutionIds"; + public static final String SUPPLIERDEPENDENCYIDS = "supplierDependencyIds"; + public static final String SYNCELEMENTID = "syncElementId"; + public static final String TARGET = "targetId"; + public static final String TEMPLATEBINDINGIDS = "templateBindingIds"; + public static final String TEMPLATEPARAMETERID = "templateParameterId"; + public static final String TYPE = "type"; + public static final String TYPEID = "typeId"; + public static final String UPPERVALUE = "upperValue"; + public static final String URI = "URI"; + public static final String USECASEIDS = "useCaseIds"; + public static final String VALUEID = "valueId"; + public static final String VISIBILITY = "visibility"; + public static final String APPLIEDSTEREOTYPEIDS = "_appliedStereotypeIds"; + public static final String CHILDVIEWS = "_childViews"; + public static final String CONTENTS = "_contents"; + public static final String ISGROUP = "_isGroup"; + public static final String MOUNTS = "_mounts"; + public static final String PARENTVIEWS = "_parentViews"; + public static final String PROPERTIES = "_properties"; + public static final String QUALIFIEDID = "_qualifiedId"; + public static final String QUALIFIEDNAME = "_qualifiedName"; + public static final String RELATEDDOCUMENTS = "_relatedDocuments"; + public static final String SITECHARACTERIZATIONID = "_groupId"; + public static final String SITES = "_sites"; + public static final String HOLDING_BIN_PREFIX = "holding_bin_"; + public static final String VIEW_INSTANCES_BIN_PREFIX = "view_instances_bin_"; + public static final String PACKAGE_TYPE = "Package"; + public static final String PUBLIC_VISIBILITY = "public"; + public static final String DOCUMENTSID = "_17_0_2_3_87b0275_1371477871400_792964_43374"; + + public static final Map STEREOTYPEIDS; + static { + STEREOTYPEIDS = new HashMap<>(); + STEREOTYPEIDS.put(DOCUMENTSID, "document"); + STEREOTYPEIDS.put("_17_0_1_232f03dc_1325612611695_581988_21583", "view"); + STEREOTYPEIDS.put("_11_5EAPbeta_be00301_1147420760998_43940_227", "view"); + STEREOTYPEIDS.put("_18_0beta_9150291_1392290067481_33752_4359", "view"); + STEREOTYPEIDS.put("_17_0_1_407019f_1332453225141_893756_11936", "view"); + STEREOTYPEIDS.put("_17_0_2_3_407019f_1389807639137_860750_29082", "conforms"); + STEREOTYPEIDS.put("_16_5_4_409a058d_1259862803278_226185_1083", "exposes"); + STEREOTYPEIDS.put("_17_0_5_1_8660276_1407362513794_939259_26181", "characterizes"); + } + + public static final Map PROPERTYSIDS; + static { + PROPERTYSIDS = new HashMap<>(); + PROPERTYSIDS.put("composite", "_15_0_be00301_1199377756297_348405_2678"); + PROPERTYSIDS.put("none", "_15_0_be00301_1199378032543_992832_3096"); + PROPERTYSIDS.put("shared", "_15_0_be00301_1199378020836_340320_3071"); + } + + public static final String VALUEPROPERTY = "_12_0_be00301_1164123483951_695645_2041"; + public static final String CONSTRAINTPROPERTY = "_11_5EAPbeta_be00301_1147767840464_372327_467"; + + public static final Set VIEWSIDS; + static { + VIEWSIDS = new HashSet<>(); + VIEWSIDS.add(DOCUMENTSID); + VIEWSIDS.add("_17_0_1_232f03dc_1325612611695_581988_21583"); + VIEWSIDS.add("_11_5EAPbeta_be00301_1147420760998_43940_227"); + VIEWSIDS.add("_18_0beta_9150291_1392290067481_33752_4359"); + VIEWSIDS.add("_17_0_1_407019f_1332453225141_893756_11936"); + } +} diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/MsosaNodeType.java b/msosa/src/main/java/org/openmbee/mms/msosa/MsosaNodeType.java new file mode 100644 index 000000000..5ddcf3e62 --- /dev/null +++ b/msosa/src/main/java/org/openmbee/mms/msosa/MsosaNodeType.java @@ -0,0 +1,47 @@ +package org.openmbee.mms.msosa; + +import java.util.HashMap; +import java.util.Map; + +public enum MsosaNodeType { + ELEMENT(1), + SITE(2), + PROJECT(3), + DOCUMENT(4), + COMMENT(5), + CONSTRAINT(6), + INSTANCESPECIFICATION(7), + OPERATION(8), + PACKAGE(9), + PROPERTY(10), + PARAMETER(11), + VIEW(12), + VIEWPOINT(13), + GROUP(14), + HOLDINGBIN(15), + PROJECTUSAGE(16); + + private static Map map = new HashMap<>(); + + static { + for (MsosaNodeType n : MsosaNodeType.values()) { + map.put(n.value, n); + } + } + + private int value; + + MsosaNodeType(int value) { + this.value = value; + } + + public static MsosaNodeType valueOf(int n) { + return map.get(n); + } + + public int getValue() { + return value; + } +} + + diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/MsosaSchemaConfig.java b/msosa/src/main/java/org/openmbee/mms/msosa/MsosaSchemaConfig.java new file mode 100644 index 000000000..7162f3fb6 --- /dev/null +++ b/msosa/src/main/java/org/openmbee/mms/msosa/MsosaSchemaConfig.java @@ -0,0 +1,15 @@ +package org.openmbee.mms.msosa; + +import org.openmbee.mms.core.config.ProjectSchemas; +import org.openmbee.mms.json.SchemaJson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MsosaSchemaConfig { + + @Autowired + public void registerSchema(ProjectSchemas schemas) { + schemas.addSchema(new SchemaJson().setName("msosa").setDescription("msosa/md json handling, creates holding bins")); + } +} diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaCommitService.java b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaCommitService.java new file mode 100644 index 000000000..b8cb8e26c --- /dev/null +++ b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaCommitService.java @@ -0,0 +1,17 @@ +package org.openmbee.mms.msosa.services; + +import org.openmbee.mms.core.services.CommitService; +import org.openmbee.mms.crud.services.DefaultCommitService; +import org.openmbee.mms.json.CommitJson; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service("msosaCommitService") +public class MsosaCommitService extends DefaultCommitService implements CommitService { + @Override + public boolean isProjectNew(String projectId) { + List commits = commitPersistence.findAllByProjectId(projectId); + return commits == null || commits.size() <= 1; // a msosa project gets 1 auto commit, so its still "new" + } +} diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaHelper.java b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaHelper.java new file mode 100644 index 000000000..ac8b62272 --- /dev/null +++ b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaHelper.java @@ -0,0 +1,161 @@ +package org.openmbee.mms.msosa.services; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.openmbee.mms.msosa.MsosaConstants; +import org.openmbee.mms.msosa.MsosaNodeType; +import org.openmbee.mms.core.utils.ElementUtils; +import org.openmbee.mms.json.ElementJson; +import org.springframework.stereotype.Component; + +@Component +public class MsosaHelper implements ElementUtils{ + + @Override + public MsosaNodeType getNodeType(ElementJson e) { + if (isDocument(e)) { + return MsosaNodeType.DOCUMENT; + } + if (isView(e)) { + return MsosaNodeType.VIEW; + } + if (isGroup(e)) { + return MsosaNodeType.GROUP; + } + if (e.getId().contains("holding_bin") || e.getId().contains("view_instances_bin")) { + return MsosaNodeType.HOLDINGBIN; + } + String type = e.getType(); + if (type == null) { + return MsosaNodeType.ELEMENT; + } + switch (type) { + case "Mount": + return MsosaNodeType.PROJECTUSAGE; + case "InstanceSpecification": + return MsosaNodeType.INSTANCESPECIFICATION; + case "Constraint": + return MsosaNodeType.CONSTRAINT; + case "Package": + return MsosaNodeType.PACKAGE; + case "Property": + return MsosaNodeType.PROPERTY; + case "Parameter": + return MsosaNodeType.PARAMETER; + default: + } + return MsosaNodeType.ELEMENT; + } + + public boolean isView(ElementJson e) { + List sids = (List)e.get(MsosaConstants.APPLIEDSTEREOTYPEIDS); + if (sids != null && !sids.isEmpty()) { + Set ids = new HashSet<>(sids); + ids.retainAll(MsosaConstants.VIEWSIDS); + return !ids.isEmpty(); + } + return false; + } + + public boolean isDocument(ElementJson e) { + List sids = (List)e.get(MsosaConstants.APPLIEDSTEREOTYPEIDS); + if (sids != null && sids.contains(MsosaConstants.DOCUMENTSID)) { + return true; + } + return false; + } + + public boolean isGroup(ElementJson e) { + Boolean isGroup = (Boolean)e.get(MsosaConstants.ISGROUP); + if (isGroup != null) { + return isGroup; + } + return false; + } + + public ElementJson createProperty(String id, String name, String ownerId, String aggregation, String typeId, String assocId) { + ElementJson res = new ElementJson(); + res.put(ElementJson.ID, id); + res.put(MsosaConstants.NAME, name); + res.put(MsosaConstants.NAMEEXPRESSION, null); + res.put(MsosaConstants.TYPE, "Property"); + res.put(MsosaConstants.OWNERID, ownerId); + res.put(MsosaConstants.TYPEID, typeId); + res.put(MsosaConstants.AGGREGATION, aggregation); + res.put(MsosaConstants.ASSOCIATIONID, assocId); + List asIds = new ArrayList<>(); + res.put(MsosaConstants.APPLIEDSTEREOTYPEIDS, asIds); + res.put(MsosaConstants.DOCUMENTATION, ""); + res.put(MsosaConstants.MDEXTENSIONSIDS, new ArrayList()); + res.put(MsosaConstants.SYNCELEMENTID, null); + res.put(MsosaConstants.CLIENTDEPENDENCYIDS, new ArrayList()); + res.put(MsosaConstants.SUPPLIERDEPENDENCYIDS, new ArrayList()); + res.put(MsosaConstants.VISIBILITY, "private"); + res.put(MsosaConstants.ISLEAF, false); + res.put(MsosaConstants.ISSTATIC, false); + res.put(MsosaConstants.ISORDERED, false); + res.put(MsosaConstants.ISUNIQUE, true); + res.put(MsosaConstants.LOWERVALUE, null); + res.put(MsosaConstants.UPPERVALUE, null); + res.put(MsosaConstants.ISREADONLY, false); + res.put(MsosaConstants.TEMPLATEPARAMETERID, null); + res.put(MsosaConstants.ENDIDS, new ArrayList()); + res.put(MsosaConstants.DEPLOYMENTIDS, new ArrayList()); + res.put(MsosaConstants.ASSOCIATIONENDID, null); + res.put(MsosaConstants.QUALIFIERIDS, new ArrayList()); + res.put(MsosaConstants.DATATYPEID, null); + res.put(MsosaConstants.DEFAULTVALUE, null); + res.put(MsosaConstants.INTERFACEID, null); + res.put(MsosaConstants.ISDERIVED, false); + res.put(MsosaConstants.ISDERIVEDUNION, false); + res.put(MsosaConstants.ISID, false); + res.put(MsosaConstants.REDEFINEDPROPERTYIDS, new ArrayList()); + res.put(MsosaConstants.SUBSETTEDPROPERTYIDS, new ArrayList()); + return res; + } + + public ElementJson createAssociation(String id, String ownerId, String ownedEnd, String memberEnd2) { + ElementJson association = new ElementJson(); + List memberEndIds = new ArrayList<>(); + memberEndIds.add(ownedEnd); + memberEndIds.add(memberEnd2); + List ownedEndIds = new ArrayList<>(); + ownedEndIds.add(ownedEnd); + + association.put(ElementJson.ID, id); + association.put(MsosaConstants.NAME, ""); + association.put(MsosaConstants.NAMEEXPRESSION, null); + association.put(MsosaConstants.TYPE, "Association"); + association.put(MsosaConstants.OWNERID, ownerId); + association.put(MsosaConstants.MEMBERENDIDS, memberEndIds); + association.put(MsosaConstants.OWNEDENDIDS, ownedEndIds); + // Default Fields + association.put(MsosaConstants.DOCUMENTATION, ""); + association.put(MsosaConstants.MDEXTENSIONSIDS, new ArrayList()); + association.put(MsosaConstants.SYNCELEMENTID, null); + association.put(MsosaConstants.APPLIEDSTEREOTYPEIDS, new ArrayList()); + association.put(MsosaConstants.CLIENTDEPENDENCYIDS, new ArrayList()); + association.put(MsosaConstants.SUPPLIERDEPENDENCYIDS, new ArrayList()); + association.put(MsosaConstants.NAMEEXPRESSION, null); + association.put(MsosaConstants.VISIBILITY, "public"); + association.put(MsosaConstants.TEMPLATEPARAMETERID, null); + association.put(MsosaConstants.ELEMENTIMPORTIDS, new ArrayList()); + association.put(MsosaConstants.PACKAGEIMPORTIDS, new ArrayList()); + association.put(MsosaConstants.ISLEAF, false); + association.put(MsosaConstants.TEMPLATEBINDINGIDS, new ArrayList()); + association.put(MsosaConstants.USECASEIDS, new ArrayList()); + association.put(MsosaConstants.REPRESENTATIONID, null); + association.put(MsosaConstants.COLLABORATIONUSEIDS, new ArrayList()); + association.put(MsosaConstants.GENERALIZATIONIDS, new ArrayList()); + association.put(MsosaConstants.POWERTYPEEXTENTIDS, new ArrayList()); + association.put(MsosaConstants.ISABSTRACT, false); + association.put(MsosaConstants.ISFINALSPECIALIZATION, false); + association.put(MsosaConstants.REDEFINEDCLASSIFIERIDS, new ArrayList()); + association.put(MsosaConstants.SUBSTITUTIONIDS, new ArrayList()); + association.put(MsosaConstants.ISDERIVED, false); + association.put(MsosaConstants.NAVIGABLEOWNEDENDIDS, new ArrayList()); + return association; + } +} diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaNodeService.java b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaNodeService.java new file mode 100644 index 000000000..aeb2828d6 --- /dev/null +++ b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaNodeService.java @@ -0,0 +1,129 @@ +package org.openmbee.mms.msosa.services; + +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.config.Privileges; +import org.openmbee.mms.core.objects.ElementsRequest; +import org.openmbee.mms.core.objects.ElementsResponse; +import org.openmbee.mms.core.security.MethodSecurityService; +import org.openmbee.mms.core.services.HierarchicalNodeService; +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.crud.services.DefaultNodeService; +import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.json.MountJson; +import org.openmbee.mms.msosa.MsosaConstants; +import org.openmbee.mms.msosa.MsosaNodeType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.util.Pair; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service("msosaNodeService") +public class MsosaNodeService extends DefaultNodeService implements HierarchicalNodeService { + + protected MsosaHelper msosaHelper; + private MethodSecurityService mss; + + @Autowired + public void setMsosaHelper(MsosaHelper msosaHelper) { + this.msosaHelper = msosaHelper; + } + + @Autowired + public void setMss(MethodSecurityService mss) { + this.mss = mss; + } + + @Override + public ElementsResponse read(String projectId, String refId, ElementsRequest req, + Map params) { + + String commitId = params.getOrDefault(MsosaConstants.COMMITID, null); + NodeGetInfo info = nodePersistence.findAll(projectId, refId, commitId, req.getElements()); + + if (!info.getRejected().isEmpty()) { + //continue looking in visible mounted projects for elements if not all found + NodeGetInfo curInfo = info; + List> usages = new ArrayList<>(); + getProjectUsages(projectId, refId, commitId, usages, true); + + int i = 1; //0 is entry project, already gotten + while (!curInfo.getRejected().isEmpty() && i < usages.size()) { + ElementsRequest reqNext = buildRequest(curInfo.getRejected().keySet()); + //TODO use the right commitId in child if commitId is present in params + curInfo = getNodePersistence().findAll(usages.get(i).getFirst(), usages.get(i).getSecond(), "", reqNext.getElements()); + info.getActiveElementMap().putAll(curInfo.getActiveElementMap()); + curInfo.getActiveElementMap().forEach((id, json) -> info.getRejected().remove(id)); + curInfo.getRejected().forEach((id, rejection) -> { + if (info.getRejected().containsKey(id) && rejection.getCode() == 410) { + info.getRejected().put(id, rejection); //deleted element is better than not found + } + }); + i++; + } + } + + ElementsResponse response = new ElementsResponse(); + response.getElements().addAll(info.getActiveElementMap().values()); + response.setRejected(new ArrayList<>(info.getRejected().values())); + return response; + } + + @Override + public void extraProcessPostedElement(NodeChangeInfo info, ElementJson element) { + //remove _childViews if posted + element.remove(MsosaConstants.CHILDVIEWS); + } + + @Override + public MountJson getProjectUsages(String projectId, String refId, String commitId, List> saw, + boolean restrictOnPermissions) { + ContextHolder.setContext(projectId, refId); + saw.add(Pair.of(projectId, refId)); + List mountsJson = nodePersistence.findAllByNodeType(projectId,refId,commitId,MsosaNodeType.PROJECTUSAGE.getValue()); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + List mountValues = new ArrayList<>(); + + for (ElementJson mount: mountsJson) { + String mountedProjectId = (String)mount.get(MsosaConstants.MOUNTEDELEMENTPROJECTID); + String mountedRefId = (String)mount.get(MsosaConstants.MOUNTEDREFID); + if(mountedProjectId == null){ + logger.error("Could not find Mounted Project Id"); + continue; + } + if(mountedRefId == null){ + logger.error("Could not find Mounted Ref Id for Project ID: {}" ,mountedProjectId); + continue; + } + if (saw.contains(Pair.of(mountedProjectId, mountedRefId))) { + //prevent circular dependencies or dups - should it be by project or by project and ref? + continue; + } + try { + if (restrictOnPermissions && !mss.hasBranchPrivilege(auth, mountedProjectId, mountedRefId, + Privileges.BRANCH_READ.name(), true)) { + //should permission be considered here? + continue; + } + } catch (Exception e) { + continue; + } + //doing a depth first traversal TODO get appropriate commitId + try { + mountValues.add(getProjectUsages(mountedProjectId, mountedRefId, "", saw, restrictOnPermissions)); + } catch (Exception e) { + //log the error and move on + logger.debug(String.format("Could not get project usages from nested project %s" , mountedProjectId), e); + } + } + MountJson res = new MountJson(); + res.setId(projectId); + res.setProjectId(projectId); + res.setRefId(refId); + res.put(MsosaConstants.MOUNTS, mountValues); + return res; + } +} diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaProjectService.java b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaProjectService.java new file mode 100644 index 000000000..8664e565f --- /dev/null +++ b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaProjectService.java @@ -0,0 +1,68 @@ +package org.openmbee.mms.msosa.services; + +import org.openmbee.mms.msosa.MsosaConstants; +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.objects.ElementsRequest; +import org.openmbee.mms.core.services.NodeService; +import org.openmbee.mms.core.services.ProjectService; +import org.openmbee.mms.crud.services.DefaultProjectService; +import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; + + +@Service("msosaProjectService") +public class MsosaProjectService extends DefaultProjectService implements ProjectService { + + private NodeService nodeService; + + @Autowired + @Qualifier("msosaNodeService") + public void setNodeService(NodeService nodeService) { + this.nodeService = nodeService; + } + + @Override + public ProjectJson create(ProjectJson project) { + ProjectJson projectJson = super.create(project); + + ElementJson projectHoldingBin = createNode(MsosaConstants.HOLDING_BIN_PREFIX + projectJson.getProjectId(), + "Holding Bin", projectJson); + + ElementJson viewInstanceBin = createNode(MsosaConstants.VIEW_INSTANCES_BIN_PREFIX + projectJson.getProjectId(), + "View Instances Bin", projectJson); + + ElementsRequest elementsRequest = new ElementsRequest(); + elementsRequest.setElements(List.of(projectHoldingBin, viewInstanceBin)); + nodeService.createOrUpdate(projectJson.getProjectId(), Constants.MASTER_BRANCH, elementsRequest, + Collections.EMPTY_MAP, projectJson.getCreator()); + + return projectJson; + } + + @Override + public RefJson createMasterRefJson(ProjectJson project){ + RefJson branchJson = super.createMasterRefJson(project); + branchJson.put("twcId",Constants.MASTER_BRANCH); + return branchJson; + + } + + private static ElementJson createNode(String id, String name, ProjectJson projectJson) { + ElementJson e = new ElementJson(); + e.setId(id); + e.setName(name); + e.put(MsosaConstants.OWNERID, projectJson.getProjectId()); + e.put(MsosaConstants.TYPE, MsosaConstants.PACKAGE_TYPE); + e.put(MsosaConstants.ISGROUP, false); + e.put(MsosaConstants.DOCUMENTATION, ""); + e.put(MsosaConstants.VISIBILITY, MsosaConstants.PUBLIC_VISIBILITY); + return e; + } +} diff --git a/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaViewService.java b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaViewService.java new file mode 100644 index 000000000..cf1e7a4da --- /dev/null +++ b/msosa/src/main/java/org/openmbee/mms/msosa/services/MsosaViewService.java @@ -0,0 +1,259 @@ +package org.openmbee.mms.msosa.services; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import org.openmbee.mms.crud.domain.JsonDomain; +import org.openmbee.mms.msosa.MsosaConstants; +import org.openmbee.mms.msosa.MsosaNodeType; +import org.openmbee.mms.core.config.ContextHolder; +import org.openmbee.mms.core.objects.ElementsRequest; +import org.openmbee.mms.core.objects.ElementsResponse; +import org.openmbee.mms.core.services.NodeChangeInfo; +import org.openmbee.mms.core.services.NodeGetInfo; +import org.openmbee.mms.json.ElementJson; +import org.openmbee.mms.view.services.PropertyData; +import org.openmbee.mms.view.services.ViewService; +import org.springframework.stereotype.Service; + +@Service("msosaViewService") +public class MsosaViewService extends MsosaNodeService implements ViewService { + + @Override + public ElementsResponse getDocuments(String projectId, String refId, Map params) { + ContextHolder.setContext(projectId, refId); + String commitId = params.getOrDefault(MsosaConstants.COMMITID, null); + List documents = getNodePersistence().findAllByNodeType(projectId, refId, commitId, MsosaNodeType.DOCUMENT.getValue()); + for (ElementJson e: documents) { + Optional parent = getFirstRelationshipOfType(projectId, refId, commitId, e, + Arrays.asList(MsosaNodeType.GROUP.getValue()), MsosaConstants.OWNERID); + if (parent.isPresent()) { + e.put(MsosaConstants.SITECHARACTERIZATIONID, parent.get().getId()); + } + } + return new ElementsResponse().setElements(documents); + } + + @Override + public ElementsResponse getView(String projectId, String refId, String elementId, Map params) { + return this.getViews(projectId, refId, buildRequest(elementId), params); + } + + @Override + public ElementsResponse getViews(String projectId, String refId, ElementsRequest req, Map params) { + ElementsResponse res = this.read(projectId, refId, req, params); + addChildViews(res, params); + return res; + } + + @Override + public void addChildViews(ElementsResponse res, Map params) { + for (ElementJson element: res.getElements()) { + if (msosaHelper.isView(element)) { + List ownedAttributeIds = (List) element.get(MsosaConstants.OWNEDATTRIBUTEIDS); + ElementsResponse ownedAttributes = this.read(element.getProjectId(), element.getRefId(), + buildRequest(ownedAttributeIds), params); + List filtered = JsonDomain.filter(ownedAttributeIds, ownedAttributes.getElements()); + List childViews = new ArrayList<>(); + for (ElementJson attr : filtered) { + String childId = (String) attr.get(MsosaConstants.TYPEID); + if (MsosaConstants.PROPERTY.equals(attr.getType()) && childId != null && !childId.isEmpty()) { + Map child = new HashMap<>(); + child.put(ElementJson.ID, childId); + child.put(MsosaConstants.AGGREGATION, (String) attr.get(MsosaConstants.AGGREGATION)); + child.put(MsosaConstants.PROPERTYID, attr.getId()); + childViews.add(child); + } + } + element.put(MsosaConstants.CHILDVIEWS, childViews); + } + } + } + + @Override + public ElementsResponse getGroups(String projectId, String refId, Map params) { + ContextHolder.setContext(projectId, refId); + String commitId = params.getOrDefault(MsosaConstants.COMMITID, null); + List groups = getNodePersistence().findAllByNodeType(projectId, refId, commitId, + MsosaNodeType.GROUP.getValue()); + ElementsResponse res = this.read(projectId, refId, buildRequestFromJsons(groups), params); + for (ElementJson e: res.getElements()) { + Optional parent = getFirstRelationshipOfType(projectId, refId, commitId, e, + Arrays.asList(MsosaNodeType.GROUP.getValue()), MsosaConstants.OWNERID); + if (parent.isPresent()) { + e.put(MsosaConstants.PARENTID, parent.get().getId()); + } + } + return res; +} + + @Override + public void extraProcessPostedElement(NodeChangeInfo info, ElementJson element) { + //handle _childViews + List> newChildViews = (List)element.remove(MsosaConstants.CHILDVIEWS); + if (newChildViews == null) { + super.extraProcessPostedElement(info, element); + return; + } + //gather data on "old" attributes + List oldOwnedAttributeIds = (List)element.get(MsosaConstants.OWNEDATTRIBUTEIDS); + //use helper to get access to Nodes + String projectId = info.getCommitJson().getProjectId(); + String refId = info.getCommitJson().getRefId(); + NodeGetInfo oldInfo = getNodePersistence().findAll(projectId, refId, null, buildRequest(oldOwnedAttributeIds).getElements()); + List oldProperties = new ArrayList<>(); + Map oldPropertiesTypeMapping = new HashMap<>(); //typeId to PropertyData + for (String oldOwnedAttributeId: oldOwnedAttributeIds) { + if (!oldInfo.getActiveElementMap().containsKey(oldOwnedAttributeId)) { + continue; //property doesn't exist anymore? indicates existing model inconsistency + } + //TODO This probably breaks view editor. move to federated domain somehow + //Node oldNode = oldInfo.getExistingNodeMap().get(oldOwnedAttributeId); + ElementJson oldJson = oldInfo.getActiveElementMap().get(oldOwnedAttributeId); + PropertyData oldData = new PropertyData(); + oldData.setPropertyJson(oldJson); + //TODO This probably breaks view editor. move to federated domain somehow + //oldData.setPropertyNode(oldNode); + oldProperties.add(oldData); + String typeId = (String)oldJson.get(MsosaConstants.TYPEID); + if (typeId == null || typeId.isEmpty()) { + continue; //property has no type + } + oldPropertiesTypeMapping.put(typeId, oldData); + } + //include project usages when finding types + ElementsResponse oldTypeJsons = this.read(element.getProjectId(), element.getRefId(), + buildRequest(oldPropertiesTypeMapping.keySet()), Collections.emptyMap()); + for (ElementJson oldType: oldTypeJsons.getElements()) { + oldPropertiesTypeMapping.get(oldType.getId()).setTypeJson(oldType); + oldPropertiesTypeMapping.get(oldType.getId()).setView(msosaHelper.isView(oldType)); + } + //now oldProperties is a list of existing property data in existing order (can include ones with + // no type or types that're not views + //reset context since previous type finding could have looked in submodules + ContextHolder.setContext(element.getProjectId(), element.getRefId()); + //go through requested _childView changes + //get the first package element that's in the owner chain of parent class + // msosa/sysml1 requires associations to be placed in the first owning package, is this rule still valid? + Optional p = getFirstRelationshipOfType(projectId, refId, null, element, + Arrays.asList(MsosaNodeType.PACKAGE.getValue(), MsosaNodeType.GROUP.getValue()), MsosaConstants.OWNERID); + String packageId = p.isPresent() ? p.get().getId() : MsosaConstants.HOLDING_BIN_PREFIX + element.getProjectId(); + List newProperties = new ArrayList<>(); + List newAttributeIds = new ArrayList<>(); + for (Map newChildView: newChildViews) { + String typeId = newChildView.get(ElementJson.ID); + if (oldPropertiesTypeMapping.containsKey(typeId)) { + //existing property and type, reuse + PropertyData data = oldPropertiesTypeMapping.get(typeId); + newProperties.add(data); + newAttributeIds.add(data.getPropertyJson().getId()); + continue; + } + //create new properties and association + PropertyData newProperty = createElementsForView(info, newChildView.get(MsosaConstants.AGGREGATION), typeId, element.getId(), packageId); + newProperties.add(newProperty); + newAttributeIds.add(newProperty.getPropertyJson().getId()); + } + //go through old attributes and add back any that wasn't to a view and delete ones that's to a view but not in newProperties + List toDelete = new ArrayList<>(); + for (PropertyData oldProperty: oldProperties) { + if (newProperties.contains(oldProperty)) { + continue; //already added + } + if (!oldProperty.isView()) { + newProperties.add(oldProperty); + newAttributeIds.add(oldProperty.getPropertyJson().getId()); + continue; + } + toDelete.add(oldProperty); + } + deletePropertyElements(projectId, refId, toDelete, info); + //new derived ownedAttributeIds based on changes + element.put(MsosaConstants.OWNEDATTRIBUTEIDS, newAttributeIds); + super.extraProcessPostedElement(info, element); + } + + private PropertyData createElementsForView(NodeChangeInfo info, String aggregation, String typeId, String parentId, String packageId) { + //create new properties and association + String newPropertyId = UUID.randomUUID().toString(); + String newAssocId = UUID.randomUUID().toString(); + String newAssocPropertyId = UUID.randomUUID().toString(); + ElementJson newPropertyJson = msosaHelper.createProperty(newPropertyId, "", parentId, + aggregation, typeId, newAssocId); + ElementJson newAssocJson = msosaHelper.createAssociation(newAssocId, packageId, + newAssocPropertyId, newPropertyId); + ElementJson newAssocPropertyJson = msosaHelper.createProperty(newAssocPropertyId, "", + newAssocId, MsosaConstants.NONE, parentId, newAssocId); + + getNodePersistence().prepareAddsUpdates(info, List.of(newPropertyJson, newAssocJson, newAssocPropertyJson)); + super.extraProcessPostedElement(info, newPropertyJson); + super.extraProcessPostedElement(info, newAssocJson); + super.extraProcessPostedElement(info, newAssocPropertyJson); + PropertyData newProperty = new PropertyData(); + newProperty.setAssocJson(newAssocJson); + newProperty.setPropertyJson(newPropertyJson); + newProperty.setAssocPropertyJson(newAssocPropertyJson); + newProperty.setView(true); + return newProperty; + } + + private void deletePropertyElements(String projectId, String refId, List properties, NodeChangeInfo info) { + Set assocToDelete = new HashSet<>(); + for (PropertyData oldProperty: properties) { + ElementJson oldPropertyJson = oldProperty.getPropertyJson(); + getNodePersistence().prepareDeletes(info, List.of(oldPropertyJson)); + String assocId = (String)oldPropertyJson.get(MsosaConstants.ASSOCIATIONID); + if (assocId == null) { + continue; + } + assocToDelete.add(assocId); + } + Set assocPropToDelete = new HashSet<>(); + NodeGetInfo assocInfo = getNodePersistence().findAll(projectId, refId, null, buildRequest(assocToDelete).getElements()); + for (ElementJson assocJson: assocInfo.getActiveElementMap().values()) { + getNodePersistence().prepareDeletes(info, List.of(assocJson)); + List ownedEndIds = (List)assocJson.get(MsosaConstants.OWNEDENDIDS); + if (ownedEndIds == null) { + continue; + } + assocPropToDelete.addAll(ownedEndIds); + } + NodeGetInfo assocPropInfo = getNodePersistence().findAll(projectId, refId, null, buildRequest(assocPropToDelete).getElements()); + getNodePersistence().prepareDeletes(info, assocPropInfo.getActiveElementMap().values()); + } + + // find first element of type in types following e's relkey (assuming relkey's + // value is an element id) + private Optional getFirstRelationshipOfType(String projectId, String refId, String commitId, + ElementJson e, List types, String relkey) { + // only for latest graph + String nextId = (String) e.get(relkey); + if (nextId == null || nextId.isEmpty()) { + return Optional.empty(); + } + NodeGetInfo getInfo = nodePersistence.findById(projectId, refId, commitId, nextId); + Optional next = Optional.of(getInfo.getActiveElementMap().get(nextId)); + + while (next.isPresent()) { + if (types.contains(msosaHelper.getNodeType(next.get()).getValue())) { + return next; + } + nextId = (String)next.get().get(relkey); + if (nextId == null || nextId.isEmpty()) { + return Optional.empty(); + } + getInfo = nodePersistence.findById(projectId, refId, commitId, nextId); + next = Optional.of(getInfo.getActiveElementMap().get(nextId)); + } + return Optional.empty(); + } + +} diff --git a/oauth/oauth.gradle b/oauth/oauth.gradle new file mode 100644 index 000000000..b9c0cc588 --- /dev/null +++ b/oauth/oauth.gradle @@ -0,0 +1,18 @@ +apply plugin: 'java-library' +apply plugin: 'io.spring.dependency-management' + +Map commonDependencies = rootProject.ext.commonDependencies + +dependencies { + implementation project(':core') + implementation project(':crud') + + implementation 'com.google.code.gson:gson:2.8.2' + implementation 'javax.servlet:javax.servlet-api:4.0.1' + implementation 'org.apache.httpcomponents:httpclient:4.5.3' + + implementation commonDependencies.'spring-security-config' + implementation commonDependencies.'spring-security-web' + + testImplementation commonDependencies.'spring-boot-starter-test' +} diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/config/OAuthSecurityConfig.java b/oauth/src/main/java/org/openmbee/mms/oauth/config/OAuthSecurityConfig.java new file mode 100644 index 000000000..d2a808ad1 --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/config/OAuthSecurityConfig.java @@ -0,0 +1,32 @@ +package org.openmbee.mms.oauth.config; + +import org.openmbee.mms.oauth.security.OAuthAuthenticationFilter; +import org.openmbee.mms.oauth.security.OAuthProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +@Configuration +public class OAuthSecurityConfig { + + private static Logger logger = LoggerFactory.getLogger(OAuthSecurityConfig.class); + + public OAuthSecurityConfig() { + } + + public void setAuthConfig(HttpSecurity http) throws Exception { + + } + + @Bean + public OAuthAuthenticationFilter oAuthAuthenticationFilter() throws Exception { + return new OAuthAuthenticationFilter(); + } + + @Bean + public OAuthProcessor oAuthProcessor() throws Exception { + return new OAuthProcessor(); + } +} diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/constants/OAuthConstants.java b/oauth/src/main/java/org/openmbee/mms/oauth/constants/OAuthConstants.java new file mode 100644 index 000000000..e83e0fa45 --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/constants/OAuthConstants.java @@ -0,0 +1,22 @@ +package org.openmbee.mms.oauth.constants; + +public class OAuthConstants { + + public static final String AUTHORIZATION = "Authorization"; + public static final String ACCESS_TOKEN = "access_token"; + public static final String BEARER = "Bearer "; + public static final String SAMA_ACCOUNT_NAME = "sAMAccountName"; + public static final String TOKEN = "token"; + public static final String CLIENT_ID = "client_id"; + public static final String JKS = "jks"; + + + public static final String GRANT_LOA = "AuthnLevel"; + public static final String GRANT_TYPE = "grant_type"; + public static final String GRANT_TYPE_VALIDATE_BEARER = "urn:pingidentity.com:oauth2:grant_type:validate_bearer"; + + public static final String MEDIATYPE = "MediaType"; + public static final String TLS_VERSION = "TLSv1.2"; + public static final String MEDIATYPE_URL_ENCODED = "application/x-www-form-urlencoded"; + +} diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/constants/OAuthErrorConstants.java b/oauth/src/main/java/org/openmbee/mms/oauth/constants/OAuthErrorConstants.java new file mode 100644 index 000000000..3d1c9ccdc --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/constants/OAuthErrorConstants.java @@ -0,0 +1,15 @@ +package org.openmbee.mms.oauth.constants; + +public class OAuthErrorConstants { + public static final String HTTP_FAILED = "Http call failed"; + public static final String INSUFFICIENT_LOA = "User does not have proper LOA."; + public static final String INVALD_TOKEN = "Invalid bearer token !!"; + public static final String NO_GRANTS_PROVIDED = "No Grants provided for the user."; + public static final String NO_TOKEN_RECEIVED= "No access token received."; + public static final String PROBLEM_LOADING_CERTIFICATE = "Problem occurred when loading the application certificates : "; + public static final String UNKNOWN_LOA = "LOA value not provided therefore LOA can not be checked"; + public static final String UNKNOWN_TOKEN= "Token is either of an unknown type or not a Bearer token"; + public static final String UNKNOWN_USER = "Unknown User."; + public static final String CLIENT_ID_REQUIRED = "Client id is required."; + public static final String CLIENT_ID_NOT_WHITELISTED = "Client id %s is not in the client id whitelist."; +} \ No newline at end of file diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuth2Authentication.java b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuth2Authentication.java new file mode 100644 index 000000000..f1da0dcef --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuth2Authentication.java @@ -0,0 +1,55 @@ +package org.openmbee.mms.oauth.security; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +public class OAuth2Authentication extends AbstractAuthenticationToken { + + + private static final long serialVersionUID = 2928542619979639608L; + private String userId; + private String autherizationToken; + private UserDetails principal; + Map grants; + + public OAuth2Authentication() { + super(new ArrayList<>()); + this.setAuthenticated(false); + } + + public OAuth2Authentication(Map grants) { + super(new ArrayList<>()); + this.grants = grants; + this.setAuthenticated(false); + } + + public OAuth2Authentication(Collection authorities) { + super(authorities); + this.setAuthenticated(false); + } + + public OAuth2Authentication(String authorizationToken, Map grants, UserDetails userDetails) { + super(userDetails.getAuthorities()); + this.autherizationToken = authorizationToken; + this.grants = grants; + this.principal = userDetails; + this.setAuthenticated(false); + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return this.principal; + } + +} \ No newline at end of file diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthAuthenticationFilter.java b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthAuthenticationFilter.java new file mode 100644 index 000000000..40c11aa9e --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthAuthenticationFilter.java @@ -0,0 +1,53 @@ +package org.openmbee.mms.oauth.security; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.openmbee.mms.oauth.constants.OAuthConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +public class OAuthAuthenticationFilter extends OncePerRequestFilter { + private static Logger logger = LoggerFactory.getLogger(OAuthAuthenticationFilter.class); + OAuthProcessor oAuthProcessor; + + @Autowired + public void setOAuthProcessor(OAuthProcessor oAuthProcessor ){ + this.oAuthProcessor = oAuthProcessor; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + try { + String authHeader = request.getHeader(OAuthConstants.AUTHORIZATION); + if((authHeader == null || authHeader.isEmpty()) && request.getHeader(OAuthConstants.ACCESS_TOKEN) != null){ + authHeader = OAuthConstants.BEARER+ request.getHeader(OAuthConstants.ACCESS_TOKEN); + } + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + // Require the Authorization: Bearer format for auth header + // Skip OAuth is already authenticated + if (authHeader != null && authHeader.startsWith(OAuthConstants.BEARER) && auth == null) { + String authToken = authHeader; + OAuth2Authentication authentication = oAuthProcessor.validateAuthToken(authToken); + if (authentication != null) { + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } catch (Exception e) { + logger.error(e.getMessage()); + } finally { + filterChain.doFilter(request, response); + } + } +} diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthProcessor.java b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthProcessor.java new file mode 100644 index 000000000..3e6cf6d8b --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthProcessor.java @@ -0,0 +1,210 @@ +package org.openmbee.mms.oauth.security; + +import java.io.File; +import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.*; +import java.util.stream.Collectors; + +import javax.net.ssl.SSLContext; + + +import org.openmbee.mms.core.exceptions.UnauthorizedException; +import org.openmbee.mms.oauth.constants.OAuthErrorConstants; +import org.openmbee.mms.oauth.util.OAuthTokenUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.apache.http.HttpException; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpResponseException; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.ssl.SSLContexts; +import org.openmbee.mms.oauth.constants.OAuthConstants; +import org.springframework.security.core.userdetails.UserDetails; + +public class OAuthProcessor { + + private static Logger logger = LoggerFactory.getLogger(OAuthProcessor.class); + + @Value("${oauth.rs_id}") + private String rs_id; + + @Value("${oauth.environment}") + private String environment; + + @Value("${oauth.keystoreLocation}") + private String keystoreLocation; + + @Value("${oauth.keystorePassword}") + private char[] keystorePassword; + + @Value("${oauth.certificatePassword}") + private String certificatePassword; + + @Value("${oauth.loa}") + private String loa; + + @Value("${oauth.clients}") + private String allowedClients; + + @Value("${oauth.baseUrl}") + private String oauthBaseUrl; + + @Value("${oauth.validationEndpoint}") + private String oauthValidationEndpoint; + + @Value("#{'${oauth.clientIdWhitelist}'.split(',')}") + private Set clientIdWhitelist; + + private File jks; + Map grants = new HashMap(); + + + private OAuthUserDetailsService userDetailsService; + + public OAuthUserDetailsService getUserDetailsService() { + return userDetailsService; + } + + @Autowired + public void setUserDetailsService(OAuthUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + + OAuth2Authentication validateAuthToken(String accessToken) { + boolean isAuthenticated = true; + OAuth2Authentication oauth = null; + try { + if (accessToken == null) { + throw new Exception(OAuthErrorConstants.NO_TOKEN_RECEIVED); + } + // check that the token is a Bearer Token + if (!accessToken.contains(OAuthConstants.BEARER) && !accessToken.contains(OAuthConstants.BEARER.toLowerCase())) { + throw new Exception(OAuthErrorConstants.UNKNOWN_TOKEN); + } + + ClassLoader classLoader = OAuthProcessor.class.getClassLoader(); + KeyStore keystore = KeyStore.getInstance(OAuthConstants.JKS); + SSLConnectionSocketFactory sslsf; + try (InputStream keystoreStream = classLoader.getResourceAsStream(keystoreLocation)) { // prevent leak of keystore stream + // Load the Keystore with Application Certificate + keystore.load(keystoreStream, keystorePassword); + + try { + // check to see if keystore is empty or not + SSLContext sslcontext; + if (keystore.size() <= 0) { + sslcontext = SSLContexts.custom().loadKeyMaterial(jks, keystorePassword, certificatePassword.toCharArray()).build(); + } else { + sslcontext = SSLContexts.custom().loadKeyMaterial(keystore, certificatePassword.toCharArray()).build(); + } + // Allow TLSv1.2 protocol only + sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { OAuthConstants.TLS_VERSION }, null, + SSLConnectionSocketFactory.getDefaultHostnameVerifier()); + } catch (GeneralSecurityException gse) { + throw new GeneralSecurityException( OAuthErrorConstants.PROBLEM_LOADING_CERTIFICATE + gse.getMessage()); + } + } + + // Create the client to use for the connection for the access token validation + // HTTP Post Call + CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build(); + HttpPost post = new HttpPost(oauthBaseUrl + oauthValidationEndpoint); + // Create a list of the values to add to the Post call as an HTTP Form + List values = new ArrayList(2); + values.add(new BasicNameValuePair(OAuthConstants.GRANT_TYPE, OAuthConstants.GRANT_TYPE_VALIDATE_BEARER)); + String[] tokenValue = accessToken.split(" "); + if (tokenValue.length == 2) { + values.add(new BasicNameValuePair(OAuthConstants.TOKEN, tokenValue[1])); + } else { + throw new Exception(OAuthErrorConstants.UNKNOWN_TOKEN); + } + values.add(new BasicNameValuePair(OAuthConstants.CLIENT_ID, rs_id)); + post.setEntity(new UrlEncodedFormEntity(values)); + post.addHeader(OAuthConstants.MEDIATYPE, OAuthConstants.MEDIATYPE_URL_ENCODED); + // Execute Post + + try (CloseableHttpResponse response = client.execute(post)){ + if (response == null) + throw new HttpException(OAuthErrorConstants.HTTP_FAILED); + switch (response.getStatusLine().getStatusCode()) { + case 200: + // A 200 indicates that the token was valid + String token = extractToken(response); + + // If Post successful get the return JSON object and parse if the GSON Java Lib + grants = OAuthTokenUtil.parsesOAuthRSResponse(token); + // If grants are null or empty throw an exception + if (grants == null || grants.isEmpty()) { + isAuthenticated = false; + throw new UnauthorizedException(OAuthErrorConstants.NO_GRANTS_PROVIDED); + } else { + String userName = grants.containsKey(OAuthConstants.SAMA_ACCOUNT_NAME) ? grants.get(OAuthConstants.SAMA_ACCOUNT_NAME) : ""; + if (!userName.isEmpty()) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(userName); + oauth = new OAuth2Authentication(accessToken,grants,userDetails); + } else { + throw new UnauthorizedException(OAuthErrorConstants.UNKNOWN_USER); + } + // if we did get those check to see if we are including authnlevel or not and + // make sure the LOA value is valid + if (grants.containsKey(OAuthConstants.GRANT_LOA)) { + double grantLoa = Double.parseDouble(grants.get(OAuthConstants.GRANT_LOA)); + if (grantLoa < Double.parseDouble(loa)) { + throw new IllegalAccessException(OAuthErrorConstants.INSUFFICIENT_LOA); + } + } else { + throw new IllegalAccessException(OAuthErrorConstants.UNKNOWN_LOA); + } + + if (grants.containsKey(OAuthConstants.CLIENT_ID)) { + String clientId = grants.get(OAuthConstants.CLIENT_ID); + if(! clientIdWhitelist.contains(clientId)) { + throw new IllegalAccessException(String.format(OAuthErrorConstants.CLIENT_ID_NOT_WHITELISTED, clientId)); + } + } else { + throw new IllegalAccessException(OAuthErrorConstants.CLIENT_ID_REQUIRED); + } + } + break; + case 400: + // 400 indicates that the bearer token was not valid + throw new Exception(OAuthErrorConstants.INVALD_TOKEN); + default: + throw new HttpResponseException(response.getStatusLine().getStatusCode(),response.getStatusLine().getReasonPhrase()); + } + } catch (Exception e) { + throw new Exception(e); + } + } catch (Exception e) { + isAuthenticated = false; + logger.error(e.getMessage(),e); + } + if (oauth != null) { + oauth.setAuthenticated(isAuthenticated); + } + return oauth; + } + + private String extractToken(CloseableHttpResponse response) { + try(BufferedReader buffer = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))){ + return buffer.lines().collect(Collectors.joining("\n")); + } catch (IOException ex) { + throw new UnauthorizedException(OAuthErrorConstants.NO_TOKEN_RECEIVED); + } + } + +} \ No newline at end of file diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthUserDetails.java b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthUserDetails.java new file mode 100644 index 000000000..d1b35ab13 --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthUserDetails.java @@ -0,0 +1,72 @@ +package org.openmbee.mms.oauth.security; + +import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.UserJson; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; + +public class OAuthUserDetails implements UserDetails { + + private final UserJson user; + private final Collection groups; + + public OAuthUserDetails(UserJson user, Collection groups) { + this.user = user; + this.groups = groups; + } + + @Override + public Collection getAuthorities() { + Collection authorities = new ArrayList<>(); + if (groups != null) { + for (GroupJson group : groups) { + authorities.add(new SimpleGrantedAuthority(group.getName())); + } + } + if (Boolean.TRUE.equals(user.isAdmin())) { + authorities.add(new SimpleGrantedAuthority(AuthorizationConstants.MMSADMIN)); + } + authorities.add(new SimpleGrantedAuthority(AuthorizationConstants.EVERYONE)); + return authorities; + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getUsername(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return user.isEnabled(); + } + + public UserJson getUser() { + return user; + } + +} diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthUserDetailsService.java b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthUserDetailsService.java new file mode 100644 index 000000000..5f54b4b16 --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/security/OAuthUserDetailsService.java @@ -0,0 +1,51 @@ +package org.openmbee.mms.oauth.security; + +import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.core.dao.UserPersistence; +import org.openmbee.mms.json.UserJson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class OAuthUserDetailsService implements UserDetailsService { + + private UserPersistence userPersistence; + private UserGroupsPersistence userGroupsPersistence; + + @Autowired + public void setUserPersistence(UserPersistence userPersistence) { + this.userPersistence = userPersistence; + } + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Optional userOptional = userPersistence.findByUsername(username); + + UserJson user; + if (userOptional.isEmpty()) { + user = addUser(username); + } else { + user = userOptional.get(); + } + return new OAuthUserDetails(user, userGroupsPersistence.findGroupsAssignedToUser(username)); + } + + public UserJson addUser(String username) { + UserJson user = new UserJson(); + user.setUsername(username); + //TODO: fill in user details from TWC + user.setEnabled(true); + return userPersistence.save(user); + } + +} diff --git a/oauth/src/main/java/org/openmbee/mms/oauth/util/OAuthTokenUtil.java b/oauth/src/main/java/org/openmbee/mms/oauth/util/OAuthTokenUtil.java new file mode 100644 index 000000000..cbf084928 --- /dev/null +++ b/oauth/src/main/java/org/openmbee/mms/oauth/util/OAuthTokenUtil.java @@ -0,0 +1,82 @@ +package org.openmbee.mms.oauth.util; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +public class OAuthTokenUtil { + + /** + * Parse a json object into a map with string keys and string values + * + * @param token the json object + * @return a Map of the json object with string keys and string values + * @throws Exception if there is an error parsing the json + */ + public static Map parseToken(String token) throws Exception { + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + return gson.fromJson(token, type); + } + + /** + * Parse a JWT Token that is base64 url encoded into a map with string keys and + * string values + * + * @param token JWT base64 url encode string + * @return a Map of the json object with string keys and string values + * @throws Exception if there is an error parsing the json + */ + public static Map parseJWT(String token) throws Exception { + byte[] decodedBytes = Base64.getUrlDecoder().decode(token); + String jwt = new String(decodedBytes, "UTF-8"); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + return gson.fromJson(jwt, type); + } + + /** + * Converts token json object string into string Map that only contains the + * access token and not other info. + * + * @param token json + * @return + * @throws Exception + */ + /// + /// + /// + /// String dictionary with token contents. + public static Map parsesOAuthRSResponse(String token) throws Exception { + Map jsonToken = new HashMap(); + JsonElement jelement = new JsonParser().parse(token); + JsonObject jobject = jelement.getAsJsonObject(); + JsonObject result = jobject; + if (jobject.has("access_token")) { + result = jobject.getAsJsonObject("access_token"); + } + + if (jobject.has("client_id")) { + result.add("client_id", jobject.get("client_id")); + } + + if (jobject.has("scope")) { + result.add("scope", jobject.get("scope")); + } + + Gson gson = new Gson(); + // This gives a warning because the JSON object could contain a value that is + // not a string and has no toString method + // For our case it should be fine because we will always have string values + return (Map) gson.fromJson(result, jsonToken.getClass()); + } +} diff --git a/permissions/permissions.gradle b/permissions/permissions.gradle index 22c620275..fc847801c 100644 --- a/permissions/permissions.gradle +++ b/permissions/permissions.gradle @@ -1,5 +1,5 @@ dependencies { - implementation project(':rdb') + implementation project (":core") + implementation project (":data") implementation 'org.apache.commons:commons-lang3:3.10' - implementation commonDependencies.'spring-boot' } diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java index 12035d0ad..9790620ca 100644 --- a/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java +++ b/permissions/src/main/java/org/openmbee/mms/permissions/PermissionsController.java @@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -27,7 +26,6 @@ public PermissionsController(PermissionService permissionService) { } @PostMapping(value = "/orgs/{orgId}/permissions", consumes = MediaType.APPLICATION_JSON_VALUE) - @Transactional @PreAuthorize("@mss.hasOrgPrivilege(authentication, #orgId, 'ORG_UPDATE_PERMISSIONS', false)") public PermissionUpdatesResponse updateOrgPermissions( @PathVariable String orgId, @@ -48,7 +46,6 @@ public PermissionUpdatesResponse updateOrgPermissions( } @PostMapping(value = "/projects/{projectId}/permissions", consumes = MediaType.APPLICATION_JSON_VALUE) - @Transactional @PreAuthorize("@mss.hasProjectPrivilege(authentication, #projectId, 'PROJECT_UPDATE_PERMISSIONS', false)") public PermissionUpdatesResponse updateProjectPermissions( @PathVariable String projectId, @@ -72,7 +69,6 @@ public PermissionUpdatesResponse updateProjectPermissions( } @PostMapping(value = "/projects/{projectId}/refs/{refId}/permissions", consumes = MediaType.APPLICATION_JSON_VALUE) - @Transactional @PreAuthorize("@mss.hasBranchPrivilege(authentication, #projectId, #refId, 'BRANCH_UPDATE_PERMISSIONS', false)") public PermissionUpdatesResponse updateBranchPermissions( @PathVariable String projectId, diff --git a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java b/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java deleted file mode 100644 index 1eb2d0b93..000000000 --- a/permissions/src/main/java/org/openmbee/mms/permissions/delegation/DefaultPermissionsDelegateFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.openmbee.mms.permissions.delegation; - -import org.openmbee.mms.core.delegation.PermissionsDelegate; -import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; -import org.openmbee.mms.data.domains.global.Branch; -import org.openmbee.mms.data.domains.global.Organization; -import org.openmbee.mms.data.domains.global.Project; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; - -public class DefaultPermissionsDelegateFactory implements PermissionsDelegateFactory { - - @Autowired - ApplicationContext applicationContext; - - @Override - public PermissionsDelegate getPermissionsDelegate(Project project) { - return autowire(new DefaultProjectPermissionsDelegate(project)); - } - - @Override - public PermissionsDelegate getPermissionsDelegate(Organization organization) { - return autowire(new DefaultOrgPermissionsDelegate(organization)); - } - - @Override - public PermissionsDelegate getPermissionsDelegate(Branch branch) { - return autowire(new DefaultBranchPermissionsDelegate(branch)); - } - - private PermissionsDelegate autowire(PermissionsDelegate permissionsDelegate) { - applicationContext.getAutowireCapableBeanFactory().autowireBean(permissionsDelegate); - return permissionsDelegate; - } - -} diff --git a/rdb/rdb.gradle b/rdb/rdb.gradle index 38b68d6bd..b3eba4aa8 100644 --- a/rdb/rdb.gradle +++ b/rdb/rdb.gradle @@ -1,5 +1,6 @@ dependencies { api project(':core') + api project(':data') api commonDependencies.'spring-data-jpa' diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/config/DatabaseDefinitionService.java b/rdb/src/main/java/org/openmbee/mms/rdb/config/DatabaseDefinitionService.java index 1be5c9cc7..7fb577e75 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/config/DatabaseDefinitionService.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/config/DatabaseDefinitionService.java @@ -43,8 +43,6 @@ public class DatabaseDefinitionService { private static final String COPY_SQL = "INSERT INTO \"%s\" SELECT * FROM \"%s\""; private static final String COPY_IDX = "SELECT SETVAL('%s_id_seq', COALESCE((SELECT MAX(id) FROM \"%s\"), 1), true)"; - private static final String INITIAL_REF = "INSERT INTO branches (id, branchid, branchname, tag, deleted, timestamp) VALUES (0, 'master', 'master', false, false, NOW());"; - protected final Logger logger = LoggerFactory.getLogger(getClass()); private CrudDataSources crudDataSources; private Environment env; @@ -65,6 +63,7 @@ public boolean createProjectDatabase(Project project) throws SQLException { crudDataSources.getDataSource(ContextHolder.getContext().getKey())); List created = new ArrayList<>(); try { + logger.info("Creating database for " + project.getProjectId()); jdbcTemplate.execute("CREATE DATABASE " + databaseProjectString(project)); //lgtm[java/sql-injection] created.add("Created Database"); @@ -72,7 +71,12 @@ public boolean createProjectDatabase(Project project) throws SQLException { created.add("Created Tables"); } catch (DataAccessException e) { - if (e.getCause().getLocalizedMessage().toLowerCase().contains("exists")) { + Throwable cause = e.getCause(); + if (cause != null) { + logger.error(cause.getLocalizedMessage()); + } + logger.error(e.getLocalizedMessage()); + if (cause != null && cause.getLocalizedMessage() != null && cause.getLocalizedMessage().toLowerCase().contains("exists")) { generateProjectSchemaFromModels(project); throw (new SQLException("Database already exists")); } else { @@ -83,15 +87,16 @@ public boolean createProjectDatabase(Project project) throws SQLException { return !created.isEmpty(); } - public void deleteProjectDatabase(Project project) throws SQLException { + public void deleteProjectDatabase(String projectId) throws SQLException { try (Connection connection = crudDataSources.getDataSource(ContextObject.DEFAULT_PROJECT).getConnection(); Statement statement = connection.createStatement()) { if ("org.postgresql.Driver".equals(env.getProperty("spring.datasource.driver-class-name"))) { + statement.setQueryTimeout(60); statement.execute(connection.nativeSQL( "SELECT pid, pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '" - + databaseProjectString(project) + "';")); + + databaseProjectString(projectId) + "';")); } - statement.executeUpdate(connection.nativeSQL("DROP DATABASE " + databaseProjectString(project))); + statement.executeUpdate(connection.nativeSQL("DROP DATABASE " + databaseProjectString(projectId))); //TODO: if using PG 13, can use the following //statement.executeUpdate(connection.nativeSQL("DROP DATABASE " + databaseProjectString(project) + " WITH (FORCE)")); @@ -99,8 +104,12 @@ public void deleteProjectDatabase(Project project) throws SQLException { } private String databaseProjectString(Project project) { - String prefix = env.getProperty("rdb.project.prefix", ""); - return String.format("\"%s_%s\"", prefix, project.getProjectId()); + return databaseProjectString(project.getProjectId()); + } + + private String databaseProjectString(String projectId) { + String prefix = env.getProperty("rdb.project.prefix", ""); + return String.format("\"%s_%s\"", prefix, projectId); } public void createBranch() { @@ -132,12 +141,6 @@ public void generateProjectSchemaFromModels(Project project) throws SQLException .setDelimiter(";") .createOnly(EnumSet.of(TargetType.DATABASE), metadata.getMetadataBuilder().build()); - - try (Connection conn = crudDataSources.getDataSource(project).getConnection()) { - try (PreparedStatement ps = conn.prepareStatement(INITIAL_REF)) { - ps.execute(); - } - } } public void generateBranchSchemaFromModels() { diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/config/PersistenceJPAConfig.java b/rdb/src/main/java/org/openmbee/mms/rdb/config/PersistenceJPAConfig.java index df44030db..ff87171ef 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/config/PersistenceJPAConfig.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/config/PersistenceJPAConfig.java @@ -6,6 +6,8 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.util.Properties; import javax.sql.DataSource; + +import org.openmbee.mms.core.config.Constants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -99,7 +101,7 @@ private Properties additionalProperties() { "org.hibernate.dialect.PostgreSQLDialect")); properties.setProperty("hibernate.jdbc.lob.non_contextual_creation", env.getProperty("spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation", - "true")); + Constants.TRUE)); return properties; } diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/BaseDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/BaseDAOImpl.java index 49a8c3218..eb866cc55 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/BaseDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/BaseDAOImpl.java @@ -15,7 +15,7 @@ public abstract class BaseDAOImpl { private CrudDataSources crudDataSources; - public PlatformTransactionManager transactionManager; + private PlatformTransactionManager transactionManager; protected final Logger logger = LoggerFactory.getLogger(getClass()); @Autowired @@ -24,8 +24,7 @@ public void setCrudDataSources(CrudDataSources crudDataSources) { } @Autowired - public void setTransactionManager( - PlatformTransactionManager transactionManager) { + public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/BranchGDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/BranchGDAOImpl.java index 1f9a9ae4a..8ba974ff9 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/BranchGDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/BranchGDAOImpl.java @@ -1,7 +1,8 @@ package org.openmbee.mms.rdb.repositories; import java.util.Optional; -import org.openmbee.mms.core.dao.BranchGDAO; + +import org.openmbee.mms.data.dao.BranchGDAO; import org.openmbee.mms.data.domains.global.Branch; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/OrgDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/OrgDAOImpl.java index 0bfcb25a9..8f98541cf 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/OrgDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/OrgDAOImpl.java @@ -2,7 +2,8 @@ import java.util.List; import java.util.Optional; -import org.openmbee.mms.core.dao.OrgDAO; + +import org.openmbee.mms.data.dao.OrgDAO; import org.openmbee.mms.data.domains.global.Organization; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; 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 427cd2f81..0d7405a0f 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 @@ -3,8 +3,9 @@ import java.sql.SQLException; import java.util.List; import java.util.Optional; -import org.openmbee.mms.core.dao.ProjectDAO; + import org.openmbee.mms.core.exceptions.InternalErrorException; +import org.openmbee.mms.data.dao.ProjectDAO; import org.openmbee.mms.data.domains.global.Project; import org.openmbee.mms.rdb.config.DatabaseDefinitionService; import org.slf4j.Logger; @@ -60,13 +61,18 @@ public Project save(Project proj) { } @Override - public void delete(Project p) { - projectRepository.delete(p); + public void delete(String projectId) { + try { + projectRepository.findByProjectId(projectId).ifPresent(v -> projectRepository.delete(v)); + } catch (Exception ex) { + logger.error("Could not delete project from project Repository", ex); + throw new InternalErrorException(ex); + } try { - projectOperations.deleteProjectDatabase(p); - } catch(SQLException ex) { - logger.error("DELETE PROJECT DATABASE EXCEPTION\nPotential connection issue, query statement mishap, or unexpected RDB behavior."); + projectOperations.deleteProjectDatabase(projectId); + } catch (SQLException ex) { + logger.error("DELETE PROJECT DATABASE EXCEPTION\nPotential connection issue, query statement mishap, or unexpected RDB behavior.", ex); throw new InternalErrorException(ex); } } 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 c5bf01dd2..25e3741a9 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 @@ -2,8 +2,6 @@ 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; diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/WebhookDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/WebhookDAOImpl.java index 1cf8d1a8b..b53f874b7 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/WebhookDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/WebhookDAOImpl.java @@ -1,6 +1,6 @@ package org.openmbee.mms.rdb.repositories; -import org.openmbee.mms.core.dao.WebhookDAO; +import org.openmbee.mms.data.dao.WebhookDAO; import org.openmbee.mms.data.domains.global.Webhook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/branch/BranchDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/branch/BranchDAOImpl.java index 2a4e24230..360788db7 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/branch/BranchDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/branch/BranchDAOImpl.java @@ -1,21 +1,24 @@ package org.openmbee.mms.rdb.repositories.branch; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.util.List; -import java.util.Optional; +import org.openmbee.mms.core.config.Constants; import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.dao.BranchDAO; +import org.openmbee.mms.data.dao.BranchDAO; import org.openmbee.mms.data.domains.scoped.Branch; -import org.openmbee.mms.rdb.repositories.BaseDAOImpl; import org.openmbee.mms.rdb.config.DatabaseDefinitionService; +import org.openmbee.mms.rdb.repositories.BaseDAOImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Component; +import org.openmbee.mms.core.exceptions.NotFoundException; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; +import java.util.Optional; @Component public class BranchDAOImpl extends BaseDAOImpl implements BranchDAO { @@ -34,8 +37,10 @@ public Branch save(Branch branch) { if (branch.getId() == null) { ContextHolder.setContext(ContextHolder.getContext().getProjectId(), branch.getBranchId()); - branchesOperations.createBranch(); - branchesOperations.copyTablesFromParent(branch.getBranchId(), branch.getParentRefId(), null); + if (!Constants.MASTER_BRANCH.equals(branch.getBranchId())) { + branchesOperations.createBranch(); + branchesOperations.copyTablesFromParent(branch.getBranchId(), branch.getParentRefId(), null); + } KeyHolder keyHolder = new GeneratedKeyHolder(); @@ -46,8 +51,11 @@ public PreparedStatement createPreparedStatement(Connection connection) return prepareStatement(ps, branch); } }, keyHolder); - - branch.setId(keyHolder.getKey().longValue()); + if (keyHolder.getKey() != null) { + branch.setId(keyHolder.getKey().longValue()); + } else { + throw new NotFoundException("Key value was null"); + } } else { getConn().update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) @@ -94,7 +102,7 @@ private PreparedStatement prepareStatement(PreparedStatement ps, Branch branch) ps.setString(3, branch.getBranchId()); ps.setString(4, branch.getBranchName()); ps.setString(5, branch.getParentRefId()); - ps.setLong(6, branch.getParentCommit()); + ps.setLong(6, branch.getBranchId().equals(Constants.MASTER_BRANCH) ? 0L : branch.getParentCommit()); ps.setTimestamp(7, Timestamp.from(branch.getTimestamp())); ps.setBoolean(8, branch.isTag()); ps.setBoolean(9, branch.isDeleted()); 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 eb4e55fdc..dc15fa426 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 @@ -10,8 +10,8 @@ import java.util.List; import java.util.Optional; -import org.openmbee.mms.core.dao.BranchDAO; -import org.openmbee.mms.core.dao.CommitDAO; +import org.openmbee.mms.data.dao.BranchDAO; +import org.openmbee.mms.data.dao.CommitDAO; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.data.domains.scoped.Branch; import org.openmbee.mms.data.domains.scoped.Commit; @@ -51,7 +51,7 @@ public PreparedStatement createPreparedStatement(Connection connection) } }, keyHolder); - if (keyHolder.getKeyList().isEmpty()) { + if (keyHolder.getKeyList().isEmpty() || keyHolder.getKey() == null) { logger.error("commit db save failed"); throw new InternalErrorException("Commit db save failed"); } diff --git a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/node/NodeDAOImpl.java b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/node/NodeDAOImpl.java index d40e9ef58..b9a42fd87 100644 --- a/rdb/src/main/java/org/openmbee/mms/rdb/repositories/node/NodeDAOImpl.java +++ b/rdb/src/main/java/org/openmbee/mms/rdb/repositories/node/NodeDAOImpl.java @@ -6,7 +6,7 @@ import java.sql.SQLException; import java.util.*; -import org.openmbee.mms.core.dao.NodeDAO; +import org.openmbee.mms.data.dao.NodeDAO; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.data.domains.scoped.Node; import org.openmbee.mms.rdb.repositories.BaseDAOImpl; @@ -17,6 +17,7 @@ import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Component; +import org.openmbee.mms.core.exceptions.NotFoundException; @Component public class NodeDAOImpl extends BaseDAOImpl implements NodeDAO { @@ -37,8 +38,11 @@ public PreparedStatement createPreparedStatement(Connection connection) return prepareStatement(ps, node); } }, keyHolder); - - node.setId(keyHolder.getKey().longValue()); + if (keyHolder.getKey() != null) { + node.setId(keyHolder.getKey().longValue()); + } else { + throw new NotFoundException("Key value was null"); + } } else { getConn().update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) diff --git a/storage/storage.gradle b/storage/storage.gradle index 1d39be869..d4aac202c 100644 --- a/storage/storage.gradle +++ b/storage/storage.gradle @@ -7,3 +7,9 @@ dependencies { testImplementation commonDependencies.'spring-boot-starter-test' } + +tasks { + processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} \ No newline at end of file diff --git a/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloud.java b/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloud.java index aff958cf1..bf35702e5 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloud.java +++ b/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloud.java @@ -70,25 +70,26 @@ public void setAdminPwd(String adminPwd) { this.adminPwd = adminPwd; } - public TeamworkCloudRolesMapping getRoles() { + synchronized public TeamworkCloudRolesMapping getRoles() { return roles; } - public void setRoles(TeamworkCloudRolesMapping roles) { + synchronized public void setRoles(TeamworkCloudRolesMapping roles) { this.roles = roles; } - public void setTwcmmsRolesMap(Map> twcmmsRolesMap) { + synchronized public void setTwcmmsRolesMap(Map> twcmmsRolesMap) { this.twcmmsRolesMap = twcmmsRolesMap; } synchronized public Set getKnownNames() { if(knownNames == null){ - knownNames = new HashSet<>(); - knownNames.add(url.toLowerCase()); + HashSet tempNames = new HashSet<>(); + tempNames.add(url.toLowerCase()); if(aliases != null) { - aliases.stream().map(String::toLowerCase).forEach(v -> knownNames.add(v)); + aliases.stream().map(String::toLowerCase).forEach(v -> tempNames.add(v)); } + knownNames = tempNames; } return knownNames; } diff --git a/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloudEndpoints.java b/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloudEndpoints.java index 24c1ee045..05c84cc71 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloudEndpoints.java +++ b/twc/src/main/java/org/openmbee/mms/twc/TeamworkCloudEndpoints.java @@ -3,7 +3,7 @@ public enum TeamworkCloudEndpoints { LOGIN("login"), GETROLESID("resources/%s/roles"), - GETPROJECTUSERS("workspaces/%s/resources/%s/roles/%s/users"); + GETPROJECTUSERS("resources/%s/roles/%s/users"); private String path; diff --git a/twc/src/main/java/org/openmbee/mms/twc/config/TwcConfig.java b/twc/src/main/java/org/openmbee/mms/twc/config/TwcConfig.java index 9b573bfcd..3ddf51eb6 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/config/TwcConfig.java +++ b/twc/src/main/java/org/openmbee/mms/twc/config/TwcConfig.java @@ -42,8 +42,9 @@ public TwcAuthenticationProvider getAuthNProvider(String associatedTWC) { if(associatedTWC == null) return null; + String strippedHost = stripHost(associatedTWC); for(TeamworkCloud twc : getInstances()){ - if(twc.hasKnownName(associatedTWC)){ + if(twc.hasKnownName(strippedHost)){ return new TwcAuthenticationProvider(restUtils, twc); } } diff --git a/twc/src/main/java/org/openmbee/mms/twc/config/TwcPermissionsConfig.java b/twc/src/main/java/org/openmbee/mms/twc/config/TwcPermissionsConfig.java index 390526f3c..c7486e47d 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/config/TwcPermissionsConfig.java +++ b/twc/src/main/java/org/openmbee/mms/twc/config/TwcPermissionsConfig.java @@ -10,7 +10,7 @@ public class TwcPermissionsConfig { @Bean - @Order(-1) + @Order(-2) public PermissionsDelegateFactory getPermissionsDelegateFactory() { return new TwcPermissionsDelegateFactory(); } diff --git a/twc/src/main/java/org/openmbee/mms/twc/maintenance/TWCMaintenanceController.java b/twc/src/main/java/org/openmbee/mms/twc/maintenance/TWCMaintenanceController.java index 108cd36df..a2052465a 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/maintenance/TWCMaintenanceController.java +++ b/twc/src/main/java/org/openmbee/mms/twc/maintenance/TWCMaintenanceController.java @@ -1,16 +1,15 @@ package org.openmbee.mms.twc.maintenance; import org.openmbee.mms.core.config.AuthorizationConstants; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.exceptions.NotFoundException; -import org.openmbee.mms.data.domains.global.Project; -import org.openmbee.mms.rdb.repositories.ProjectRepository; +import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.twc.metadata.TwcMetadata; import org.openmbee.mms.twc.metadata.TwcMetadataService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.util.Optional; @@ -20,12 +19,12 @@ @RequestMapping("/adm/maintenance") public class TWCMaintenanceController { - private ProjectRepository projectRepo; + private ProjectPersistence projectPersistence; private TwcMetadataService twcMetadataService; @Autowired - public void setProjectRepo(ProjectRepository projectRepo){ - this.projectRepo = projectRepo; + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; } @Autowired @@ -33,23 +32,21 @@ public void setTwcMetadataService(TwcMetadataService twcMetadataService){ this.twcMetadataService = twcMetadataService; } - @Transactional @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) @GetMapping(value = "/project/twcmetadata/{id}") @ResponseBody - public TwcMetadata getProjectMetadata(@PathVariable String id){ - Project project = getProject(id); + public TwcMetadata getProjectMetadata(@PathVariable String id){ + ProjectJson project = getProject(id); return twcMetadataService.getTwcMetadata(project); } - @Transactional @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) @PostMapping(value = "/project/twcmetadata/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public void updateProjectMetadata(@PathVariable String id, @RequestBody TwcMetadata twcMetadata){ - Project project = getProject(id); + ProjectJson project = getProject(id); try{ twcMetadataService.updateTwcMetadata(project, twcMetadata); } @@ -58,20 +55,19 @@ public void updateProjectMetadata(@PathVariable String id, @RequestBody TwcMetad } } - @Transactional @PreAuthorize(AuthorizationConstants.IS_MMSADMIN) @DeleteMapping(value = "/project/twcmetadata/{id}") @ResponseBody public void deleteProjectMetadata(@PathVariable String id){ - Project project = getProject(id); + ProjectJson project = getProject(id); twcMetadataService.deleteTwcMetadata(project); } - private Project getProject(String projectId) { + private ProjectJson getProject(String projectId) { - Optional proj = projectRepo.findByProjectId(projectId); + Optional project = projectPersistence.findById(projectId); - return proj.orElseGet(() -> { + return project.orElseGet(() -> { String notFound = "Project id: " + projectId + " not found"; throw new NotFoundException(notFound); }); diff --git a/twc/src/main/java/org/openmbee/mms/twc/metadata/TwcMetadataService.java b/twc/src/main/java/org/openmbee/mms/twc/metadata/TwcMetadataService.java index b0894fb8c..899e64b82 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/metadata/TwcMetadataService.java +++ b/twc/src/main/java/org/openmbee/mms/twc/metadata/TwcMetadataService.java @@ -1,10 +1,8 @@ package org.openmbee.mms.twc.metadata; -import org.openmbee.mms.core.config.ContextHolder; -import org.openmbee.mms.core.dao.ProjectIndex; +import org.openmbee.mms.core.config.Constants; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.twc.constants.TwcConstants; -import org.openmbee.mms.core.exceptions.NotFoundException; -import org.openmbee.mms.data.domains.global.Project; import org.openmbee.mms.json.ProjectJson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,25 +15,21 @@ public class TwcMetadataService { private static final Logger logger = LoggerFactory.getLogger(TwcMetadataService.class); - private ProjectIndex projectIndex; + private ProjectPersistence projectPersistence; @Autowired - public void setProjectIndex(ProjectIndex projectIndex) { - this.projectIndex = projectIndex; + public void setProjectIndex(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; } - public void updateTwcMetadata(Project project, TwcMetadata metadata) { - ContextHolder.setContext(project.getProjectId()); - ProjectJson projectJson = getProjectJson(project); + public void updateTwcMetadata(ProjectJson projectJson, TwcMetadata metadata) { Map metadataMap = metadata.toMap(); - metadataMap.put(TwcConstants.ENABLED_KEY, "true"); + metadataMap.put(TwcConstants.ENABLED_KEY, Constants.TRUE); projectJson.put(TwcConstants.FOREIGN_PROJECT, metadataMap); - projectIndex.update(projectJson); + projectPersistence.update(projectJson); } - public TwcMetadata getTwcMetadata(Project project) { - ContextHolder.setContext(project.getProjectId()); - ProjectJson projectJson = getProjectJson(project); + public TwcMetadata getTwcMetadata(ProjectJson projectJson) { Map metadata = (Map)projectJson.get(TwcConstants.FOREIGN_PROJECT); if(metadata == null) { return null; @@ -48,24 +42,14 @@ public TwcMetadata getTwcMetadata(Project project) { return twcMetadata; } - public void deleteTwcMetadata(Project project) { - ContextHolder.setContext(project.getProjectId()); - ProjectJson projectJson = getProjectJson(project); + public void deleteTwcMetadata(ProjectJson projectJson) { Map metadata = (Map)projectJson.get(TwcConstants.FOREIGN_PROJECT); if(metadata == null) { return; } - metadata.put(TwcConstants.ENABLED_KEY, "false"); + metadata.put(TwcConstants.ENABLED_KEY, Constants.FALSE); projectJson.put(TwcConstants.FOREIGN_PROJECT, metadata); - projectIndex.update(projectJson); + projectPersistence.update(projectJson); } - private ProjectJson getProjectJson(Project project) { - ProjectJson projectJson = projectIndex.findById(project.getDocId()).orElse(null); - if(projectJson == null) { - logger.error("Could not locate project in project index: " + project.getDocId()); - throw new NotFoundException("Could not locate project in project index"); - } - return projectJson; - } } diff --git a/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcBranchPermissionsDelegate.java b/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcBranchPermissionsDelegate.java index c19e0fe16..795436b32 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcBranchPermissionsDelegate.java +++ b/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcBranchPermissionsDelegate.java @@ -5,7 +5,7 @@ import org.openmbee.mms.core.objects.PermissionUpdateRequest; import org.openmbee.mms.core.objects.PermissionUpdateResponse; import org.openmbee.mms.core.objects.PermissionUpdatesResponse; -import org.openmbee.mms.data.domains.global.Branch; +import org.openmbee.mms.json.RefJson; import org.openmbee.mms.twc.TeamworkCloud; import org.openmbee.mms.twc.exceptions.TwcConfigurationException; import org.openmbee.mms.twc.utilities.TwcPermissionUtils; @@ -16,7 +16,7 @@ public class TwcBranchPermissionsDelegate implements PermissionsDelegate{ - private Branch branch; + private RefJson branch; private TeamworkCloud teamworkCloud; private String workspaceId; private String resourceId; @@ -25,7 +25,7 @@ public class TwcBranchPermissionsDelegate implements PermissionsDelegate{ private TwcPermissionUtils twcPermissionUtils; - public TwcBranchPermissionsDelegate(Branch branch, TeamworkCloud teamworkCloud, String workspaceId, + public TwcBranchPermissionsDelegate(RefJson branch, TeamworkCloud teamworkCloud, String workspaceId, String resourceId) { this.branch = branch; this.teamworkCloud = teamworkCloud; @@ -46,6 +46,13 @@ public boolean hasPermission(String user, Set groups, String privilege) return hasPermission; } + @Override + public boolean hasGroupPermissions(String group, String privilege) { + throw new TwcConfigurationException(HttpStatus.BAD_REQUEST, + "Cannot Query Group Roles. Permissions for this branch are controlled by Teamwork Cloud (" + + teamworkCloud.getUrl() + ")"); + } + @Override public void initializePermissions(String creator) { //Do nothing, permissions are already initialized in TWC @@ -62,7 +69,12 @@ public boolean setInherit(boolean isInherit) { return false; } - @Override + @Override + public PermissionResponse getInherit() { + return PermissionResponse.getDefaultResponse(); + } + + @Override public void setPublic(boolean isPublic) { throw new TwcConfigurationException(HttpStatus.BAD_REQUEST, "Cannot Modify Permissions. Permissions for this branch are controlled by Teamwork Cloud (" @@ -103,7 +115,7 @@ public PermissionUpdatesResponse recalculateInheritedPerms() { return null; } - public Branch getBranch() { + public RefJson getBranch() { return branch; } diff --git a/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactory.java b/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactory.java index 176261ad0..f25d9a0f0 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactory.java +++ b/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactory.java @@ -1,12 +1,13 @@ package org.openmbee.mms.twc.permissions; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.delegation.PermissionsDelegate; import org.openmbee.mms.core.delegation.PermissionsDelegateFactory; +import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; import org.openmbee.mms.twc.TeamworkCloud; import org.openmbee.mms.core.exceptions.NotFoundException; -import org.openmbee.mms.data.domains.global.Branch; -import org.openmbee.mms.data.domains.global.Organization; -import org.openmbee.mms.data.domains.global.Project; import org.openmbee.mms.twc.config.TwcConfig; import org.openmbee.mms.twc.exceptions.TwcConfigurationException; import org.openmbee.mms.twc.metadata.TwcMetadata; @@ -15,13 +16,14 @@ import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; +import java.util.Optional; public class TwcPermissionsDelegateFactory implements PermissionsDelegateFactory { private ApplicationContext applicationContext; private TwcConfig twcConfig; private TwcMetadataService twcMetadataService; - + private ProjectPersistence projectPersistence; @Autowired public void setApplicationContext(ApplicationContext applicationContext) { @@ -38,8 +40,13 @@ public void setTwcMetadataService(TwcMetadataService twcMetadataService) { this.twcMetadataService = twcMetadataService; } + @Autowired + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; + } + @Override - public PermissionsDelegate getPermissionsDelegate(Project project) { + public PermissionsDelegate getPermissionsDelegate(ProjectJson project) { if(!twcConfig.isUseAuthDelegation()) { return null; } @@ -54,7 +61,7 @@ public PermissionsDelegate getPermissionsDelegate(Project project) { } @Override - public PermissionsDelegate getPermissionsDelegate(Organization organization) { + public PermissionsDelegate getPermissionsDelegate(OrgJson organization) { if(!twcConfig.isUseAuthDelegation()) { return null; } @@ -64,17 +71,22 @@ public PermissionsDelegate getPermissionsDelegate(Organization organization) { } @Override - public PermissionsDelegate getPermissionsDelegate(Branch branch) { + public PermissionsDelegate getPermissionsDelegate(RefJson branch) { if(!twcConfig.isUseAuthDelegation()) { return null; } - TwcProjectDetails twcProjectDetails = getTwcDetails(branch.getProject()); + Optional projectOptional = projectPersistence.findById(branch.getProjectId()); + + if(projectOptional.isEmpty()) { + throw new NotFoundException("project not found"); + } + + TwcProjectDetails twcProjectDetails = getTwcDetails(projectOptional.get()); if(twcProjectDetails != null) { return autowire(new TwcBranchPermissionsDelegate(branch, twcProjectDetails.getTeamworkCloud(), twcProjectDetails.getWorkspaceId(), twcProjectDetails.getResourceId())); } - return null; } @@ -114,7 +126,7 @@ public void setResourceId(String resourceId) { } } - private TwcProjectDetails getTwcDetails(Project project) { + private TwcProjectDetails getTwcDetails(ProjectJson project) { TwcMetadata twcMetadata = null; try { twcMetadata = twcMetadataService.getTwcMetadata(project); @@ -129,7 +141,7 @@ private TwcProjectDetails getTwcDetails(Project project) { if(teamworkCloud == null) { throw new TwcConfigurationException(HttpStatus.FAILED_DEPENDENCY, - "Project " + project.getProjectId() + " (" + project.getProjectName() + "Project " + project.getProjectId() + " (" + project.getName() + ") is associated with an untrusted TWC host (" + twcMetadata.getHost() + ")"); } diff --git a/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcProjectPermissionsDelegate.java b/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcProjectPermissionsDelegate.java index 68a59d648..df4f9a068 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcProjectPermissionsDelegate.java +++ b/twc/src/main/java/org/openmbee/mms/twc/permissions/TwcProjectPermissionsDelegate.java @@ -5,8 +5,8 @@ import org.openmbee.mms.core.objects.PermissionUpdateRequest; import org.openmbee.mms.core.objects.PermissionUpdateResponse; import org.openmbee.mms.core.objects.PermissionUpdatesResponse; -import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.twc.TeamworkCloud; import org.openmbee.mms.twc.exceptions.TwcConfigurationException; import org.openmbee.mms.twc.utilities.TwcPermissionUtils; @@ -17,7 +17,7 @@ public class TwcProjectPermissionsDelegate implements PermissionsDelegate { - private Project project; + private ProjectJson project; private TeamworkCloud teamworkCloud; private String workspaceId; private String resourceId; @@ -25,7 +25,7 @@ public class TwcProjectPermissionsDelegate implements PermissionsDelegate { @Autowired private TwcPermissionUtils twcPermissionUtils; - public TwcProjectPermissionsDelegate(Project project, TeamworkCloud teamworkCloud, String workspaceId, + public TwcProjectPermissionsDelegate(ProjectJson project, TeamworkCloud teamworkCloud, String workspaceId, String resourceId) { this.project = project; this.teamworkCloud = teamworkCloud; @@ -47,6 +47,13 @@ public boolean hasPermission(String user, Set groups, String privilege) return hasPermission; } + @Override + public boolean hasGroupPermissions(String group, String privilege) { + throw new TwcConfigurationException(HttpStatus.BAD_REQUEST, + "Cannot Query Group Roles. Permissions for this project are controlled by Teamwork Cloud (" + + teamworkCloud.getUrl() + ")"); + } + @Override public void initializePermissions(String creator) { //Do nothing, permissions are already initialized in TWC @@ -63,7 +70,12 @@ public boolean setInherit(boolean isInherit) { return false; } - @Override + @Override + public PermissionResponse getInherit() { + return PermissionResponse.getDefaultResponse(); + } + + @Override public void setPublic(boolean isPublic) { throw new TwcConfigurationException(HttpStatus.BAD_REQUEST, "Cannot Modify Permissions. Permissions for this project are controlled by Teamwork Cloud (" @@ -107,7 +119,7 @@ public PermissionUpdatesResponse recalculateInheritedPerms() { return null; } - public Project getProject() { + public ProjectJson getProject() { return project; } diff --git a/twc/src/main/java/org/openmbee/mms/twc/security/TwcAuthenticationProvider.java b/twc/src/main/java/org/openmbee/mms/twc/security/TwcAuthenticationProvider.java index 9ba5bd164..5fe6da7f7 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/security/TwcAuthenticationProvider.java +++ b/twc/src/main/java/org/openmbee/mms/twc/security/TwcAuthenticationProvider.java @@ -14,13 +14,13 @@ public class TwcAuthenticationProvider { - RestUtils restUtils; + RestUtils restUtils; private TeamworkCloud twc; - + public TwcAuthenticationProvider(RestUtils restTemplateFactory, TeamworkCloud twc) { - this.restUtils = restTemplateFactory; - this.twc = twc; + this.restUtils = restTemplateFactory; + this.twc = twc; } public String getAuthentication(String authToken) { @@ -36,11 +36,12 @@ public String getAuthentication(String authToken) { String loggedInUser = restUtils.getCookieValue(respEntity, TwcConstants.TWCCURRENTUSER); return loggedInUser; } - private ResponseEntity checkAuthentication(String authToken) { - RestTemplate restTemplate = restUtils.getRestTemplate(); + RestTemplate restTemplate = restUtils.getRestTemplate(); HttpHeaders headers = new HttpHeaders(); + //2021x R2 - found iss of handling token format + authToken = authToken.replace(":", ""); headers.set(RestUtils.AUTHORIZATION, authToken); ResponseEntity respEntity = null; diff --git a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetails.java b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetails.java index 7500988e2..1076b7fe0 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetails.java +++ b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetails.java @@ -1,8 +1,8 @@ package org.openmbee.mms.twc.security; import org.openmbee.mms.core.config.AuthorizationConstants; -import org.openmbee.mms.data.domains.global.Group; -import org.openmbee.mms.data.domains.global.User; +import org.openmbee.mms.json.GroupJson; +import org.openmbee.mms.json.UserJson; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -12,23 +12,24 @@ class TwcUserDetails implements UserDetails { - private final User user; + private final UserJson user; + private final Collection groups; - public TwcUserDetails(User user) { + public TwcUserDetails(UserJson user, Collection groups) { this.user = user; + this.groups = groups; } @Override public Collection getAuthorities() { - Collection groups = user.getGroups(); Collection authorities = new ArrayList<>(); if (groups != null) { - for (Group group : groups) { + for (GroupJson group : groups) { authorities.add(new SimpleGrantedAuthority(group.getName())); } } - if (user.isAdmin()) { + if (Boolean.TRUE.equals(user.isAdmin())) { authorities.add(new SimpleGrantedAuthority(AuthorizationConstants.MMSADMIN)); } authorities.add(new SimpleGrantedAuthority(AuthorizationConstants.EVERYONE)); @@ -62,10 +63,10 @@ public boolean isCredentialsNonExpired() { @Override public boolean isEnabled() { - return user.getEnabled(); + return user.isEnabled(); } - public User getUser() { + public UserJson getUser() { return user; } diff --git a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java index 336d0830f..f01284441 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java +++ b/twc/src/main/java/org/openmbee/mms/twc/security/TwcUserDetailsService.java @@ -1,47 +1,51 @@ package org.openmbee.mms.twc.security; -import org.openmbee.mms.data.domains.global.User; -import org.openmbee.mms.rdb.repositories.UserRepository; +import org.openmbee.mms.core.dao.UserGroupsPersistence; +import org.openmbee.mms.core.dao.UserPersistence; +import org.openmbee.mms.json.UserJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @Service public class TwcUserDetailsService implements UserDetailsService { - private UserRepository userRepository; + private UserPersistence userPersistence; + private UserGroupsPersistence userGroupsPersistence; @Autowired - public void setUserRepository(UserRepository userRepository) { - this.userRepository = userRepository; + public void setUserPersistence(UserPersistence userPersistence) { + this.userPersistence = userPersistence; + } + + @Autowired + public void setUserGroupsPersistence(UserGroupsPersistence userGroupsPersistence) { + this.userGroupsPersistence = userGroupsPersistence; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Optional user = userRepository.findByUsernameIgnoreCase(username); + Optional userOptional = userPersistence.findByUsername(username); - User u; - if (!user.isPresent()) { - u = addUser(username); + UserJson user; + if (userOptional.isEmpty()) { + user = addUser(username); } else { - u = user.get(); + user = userOptional.get(); } - return new TwcUserDetails(u); + return new TwcUserDetails(user, userGroupsPersistence.findGroupsAssignedToUser(username)); } - @Transactional - public User addUser(String username) { - User user = new User(); + public UserJson addUser(String username) { + UserJson user = new UserJson(); user.setUsername(username); //TODO: fill in user details from TWC user.setEnabled(true); - userRepository.save(user); - return user; + return userPersistence.save(user); } } 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 6361a60bd..db7373c2c 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 @@ -1,30 +1,29 @@ package org.openmbee.mms.twc.services; -import org.openmbee.mms.core.config.ContextHolder; import org.openmbee.mms.core.config.Formats; -import org.openmbee.mms.core.dao.BranchDAO; +import org.openmbee.mms.core.dao.BranchPersistence; import org.openmbee.mms.core.exceptions.InternalErrorException; import org.openmbee.mms.core.exceptions.NotFoundException; import org.openmbee.mms.core.objects.CommitsResponse; import org.openmbee.mms.core.services.NodeService; import org.openmbee.mms.crud.services.DefaultNodeService; -import org.openmbee.mms.data.domains.scoped.Branch; -import org.openmbee.mms.data.domains.scoped.Commit; import org.openmbee.mms.json.CommitJson; +import org.openmbee.mms.json.RefJson; import org.openmbee.mms.twc.constants.TwcConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.*; @Service("twcRevisionMmsCommitMapService") public class TwcRevisionMmsCommitMapService extends DefaultNodeService implements NodeService { - private BranchDAO branchRepository; + private BranchPersistence branchPersistence; @Autowired - public void setBranchRepository(BranchDAO branchRepository) { - this.branchRepository = branchRepository; + public void setBranchPersistence(BranchPersistence branchPersistence) { + this.branchPersistence = branchPersistence; } /** @@ -39,13 +38,12 @@ public CommitsResponse updateTwcRevisionID(String projectId, String commitId, St if (revisionId == null || revisionId.isEmpty()) { return commitsResponse.addMessage("Revision id can not be empty"); } - ContextHolder.setContext(projectId); - Optional commitJsonDetails = commitIndex.findById(commitId); + Optional commitJsonDetails = commitPersistence.findById(projectId, commitId); try { if (commitJsonDetails.isPresent()) { CommitJson commitObj = commitJsonDetails.get(); commitObj.put(TwcConstants.TWCREVISIONID, revisionId); - commitsResponse.getCommits().add(this.commitIndex.update(commitObj)); + commitsResponse.getCommits().add(commitPersistence.update(commitObj)); } else { throw new NotFoundException(commitsResponse); } @@ -66,18 +64,12 @@ public CommitsResponse updateTwcRevisionID(String projectId, String commitId, St */ public List getTwcRevisionList(String projectId, String refId, Boolean reverseOrder, Integer limit) { List commits = new ArrayList<>(); - ContextHolder.setContext(projectId); - Optional ref = branchRepository.findByBranchId(refId); + Optional ref = branchPersistence.findById(projectId, refId); if (!ref.isPresent()) { throw new NotFoundException("Branch not found"); } try { - List refCommits = commitRepository.findByRefAndTimestampAndLimit(ref.get(), null, 0); - Set commitIds = new HashSet<>(); - refCommits.stream().forEach(commit -> { - commitIds.add(commit.getCommitId()); - }); - List commitJsonList = commitIndex.findAllById(commitIds); + List commitJsonList = commitPersistence.findByProjectAndRefAndTimestampAndLimit(projectId, refId, null, 0); if (null != commitJsonList && commitJsonList.size() > 0) { commitJsonList.stream().forEach(commitJsonData -> { if (commitJsonData.containsKey(TwcConstants.TWCREVISIONID)) { @@ -109,8 +101,9 @@ public CommitsComparator(Boolean reverseOrder) { @Override public int compare(CommitJson o, CommitJson t1) { try { - Date d1 = Formats.SDF.parse((String) o.get(CommitJson.CREATED)); - Date d2 = Formats.SDF.parse((String) t1.get(CommitJson.CREATED)); + SimpleDateFormat dateFormat = new SimpleDateFormat(Formats.DATE_FORMAT); + Date d1 = dateFormat.parse((String) o.get(CommitJson.CREATED)); + Date d2 = dateFormat.parse((String) t1.get(CommitJson.CREATED)); return ascending ? d1.compareTo(d2) : d2.compareTo(d1); } catch (ParseException e) { logger.error("Error parsing commit dates: " + e.getMessage()); diff --git a/twc/src/main/java/org/openmbee/mms/twc/utilities/TwcPermissionUtils.java b/twc/src/main/java/org/openmbee/mms/twc/utilities/TwcPermissionUtils.java index 738dd2abc..d3dba2678 100644 --- a/twc/src/main/java/org/openmbee/mms/twc/utilities/TwcPermissionUtils.java +++ b/twc/src/main/java/org/openmbee/mms/twc/utilities/TwcPermissionUtils.java @@ -1,9 +1,7 @@ package org.openmbee.mms.twc.utilities; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import org.json.JSONArray; +import org.json.JSONObject; import org.openmbee.mms.twc.TeamworkCloud; import org.openmbee.mms.twc.TeamworkCloudEndpoints; import org.openmbee.mms.twc.constants.TwcConstants; @@ -13,10 +11,11 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; - import org.springframework.web.client.RestTemplate; -import org.json.JSONArray; -import org.json.JSONObject; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Component public class TwcPermissionUtils { @@ -69,7 +68,7 @@ public boolean hasPermissionToAccessProject(String workspaceId, String resourceI for (int inx = 0; inx < twcRoles.size(); inx++) { roleId = projectRoleIdMap.get(twcRoles.get(inx)); - users = getUsersList(workspaceId, twc, resourceId, roleId); + users = getUsersList(twc, resourceId, roleId); if (users != null && users.contains(user)) return true; } @@ -86,13 +85,13 @@ public boolean hasPermissionToAccessProject(String workspaceId, String resourceI * @param roleId * @return */ - public List getUsersList(String workspaceId, TeamworkCloud twc, String resourceId, String roleId) { + public List getUsersList(TeamworkCloud twc, String resourceId, String roleId) { // TODO:: add distributed caching for performance List users = null; ResponseEntity respEntity = getRestResponse( - TeamworkCloudEndpoints.GETPROJECTUSERS.buildUrl(twc, workspaceId, resourceId, roleId), twc); + TeamworkCloudEndpoints.GETPROJECTUSERS.buildUrl(twc, resourceId, roleId), twc); if (respEntity == null || respEntity.getBody() == null) return null; @@ -123,11 +122,12 @@ public Map getTwcRolesForGivenResourceId(String resourceId, Team return null; JSONArray rolesJsonArray = jsonUtils.parseStringToJsonArray(respEntity.getBody()); - - for (int idx = 0; idx < rolesJsonArray.length(); idx++) { - JSONObject roleJsonObj = rolesJsonArray.getJSONObject(idx); - roleNameIDMap.put(roleJsonObj.getString(TwcConstants.NAME_JSONOBJECT), - roleJsonObj.getString(TwcConstants.ID_JSONOBJECT)); + if (rolesJsonArray != null) { + for (int idx = 0; idx < rolesJsonArray.length(); idx++) { + JSONObject roleJsonObj = rolesJsonArray.getJSONObject(idx); + roleNameIDMap.put(roleJsonObj.getString(TwcConstants.NAME_JSONOBJECT), + roleJsonObj.getString(TwcConstants.ID_JSONOBJECT)); + } } return roleNameIDMap; diff --git a/twc/src/test/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactoryTest.java b/twc/src/test/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactoryTest.java index cbe38dec3..e74973177 100644 --- a/twc/src/test/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactoryTest.java +++ b/twc/src/test/java/org/openmbee/mms/twc/permissions/TwcPermissionsDelegateFactoryTest.java @@ -1,10 +1,11 @@ package org.openmbee.mms.twc.permissions; import org.junit.Test; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.delegation.PermissionsDelegate; -import org.openmbee.mms.data.domains.global.Branch; -import org.openmbee.mms.data.domains.global.Organization; -import org.openmbee.mms.data.domains.global.Project; +import org.openmbee.mms.json.OrgJson; +import org.openmbee.mms.json.ProjectJson; +import org.openmbee.mms.json.RefJson; import org.openmbee.mms.twc.TeamworkCloud; import org.openmbee.mms.twc.config.TwcConfig; import org.openmbee.mms.twc.exceptions.TwcConfigurationException; @@ -13,6 +14,8 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; +import java.util.Optional; + import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -23,6 +26,7 @@ public class TwcPermissionsDelegateFactoryTest { public void testTwcOrg() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); + when(twcConfig.isUseAuthDelegation()).thenReturn(true); TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); when(twcConfig.isUseAuthDelegation()).thenReturn(true); @@ -33,7 +37,7 @@ public void testTwcOrg() { twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); //These are not supported - assertNull( twcPermissionsDelegateFactory.getPermissionsDelegate(new Organization())); + assertNull( twcPermissionsDelegateFactory.getPermissionsDelegate(new OrgJson())); } @Test @@ -41,16 +45,15 @@ public void testTwcProject() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); - TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); - when(twcConfig.isUseAuthDelegation()).thenReturn(true); + TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); TwcPermissionsDelegateFactory twcPermissionsDelegateFactory = new TwcPermissionsDelegateFactory(); twcPermissionsDelegateFactory.setApplicationContext(applicationContext); twcPermissionsDelegateFactory.setTwcConfig(twcConfig); twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); - Project project = new Project(); + ProjectJson project = new ProjectJson(); TwcMetadata twcMetadata = new TwcMetadata(); twcMetadata.setHost("host"); twcMetadata.setWorkspaceId("workspace"); @@ -81,6 +84,7 @@ public void testTwcProject() { public void testTwcProjectDoesNotMatchTwc() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); + when(twcConfig.isUseAuthDelegation()).thenReturn(true); TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); when(twcConfig.isUseAuthDelegation()).thenReturn(true); @@ -90,7 +94,7 @@ public void testTwcProjectDoesNotMatchTwc() { twcPermissionsDelegateFactory.setTwcConfig(twcConfig); twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); - Project project = new Project(); + ProjectJson project = new ProjectJson(); TwcMetadata twcMetadata = new TwcMetadata(); twcMetadata.setHost("host"); twcMetadata.setWorkspaceId("workspace"); @@ -110,6 +114,7 @@ public void testTwcProjectDoesNotMatchTwc() { public void testTwcProjectIncompleteMetadata() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); + when(twcConfig.isUseAuthDelegation()).thenReturn(true); TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); TwcPermissionsDelegateFactory twcPermissionsDelegateFactory = new TwcPermissionsDelegateFactory(); @@ -117,7 +122,7 @@ public void testTwcProjectIncompleteMetadata() { twcPermissionsDelegateFactory.setTwcConfig(twcConfig); twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); - Project project = new Project(); + ProjectJson project = new ProjectJson(); TwcMetadata twcMetadata = new TwcMetadata(); twcMetadata.setHost("host"); twcMetadata.setWorkspaceId("workspace"); @@ -134,6 +139,7 @@ public void testTwcProjectIncompleteMetadata() { public void testTwcProjectNullMetadata() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); + when(twcConfig.isUseAuthDelegation()).thenReturn(true); TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); TwcPermissionsDelegateFactory twcPermissionsDelegateFactory = new TwcPermissionsDelegateFactory(); @@ -141,7 +147,7 @@ public void testTwcProjectNullMetadata() { twcPermissionsDelegateFactory.setTwcConfig(twcConfig); twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); - Project project = new Project(); + ProjectJson project = new ProjectJson(); when(twcMetadataService.getTwcMetadata(project)).thenReturn(null); @@ -156,7 +162,9 @@ public void testTwcBranch() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); + when(twcConfig.isUseAuthDelegation()).thenReturn(true); TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); + ProjectPersistence projectPersistence = mock(ProjectPersistence.class); when(twcConfig.isUseAuthDelegation()).thenReturn(true); @@ -164,16 +172,19 @@ public void testTwcBranch() { twcPermissionsDelegateFactory.setApplicationContext(applicationContext); twcPermissionsDelegateFactory.setTwcConfig(twcConfig); twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); + twcPermissionsDelegateFactory.setProjectPersistence(projectPersistence); - Project project = new Project(); - Branch branch = new Branch(); - branch.setProject(project); + ProjectJson project = new ProjectJson(); + RefJson branch = new RefJson(); + String projectId = "projectid"; + branch.setProjectId(projectId); TwcMetadata twcMetadata = new TwcMetadata(); twcMetadata.setHost("host"); twcMetadata.setWorkspaceId("workspace"); twcMetadata.setResourceId("resource"); + when(projectPersistence.findById(projectId)).thenReturn(Optional.of(project)); when(twcMetadataService.getTwcMetadata(project)).thenReturn(twcMetadata); TeamworkCloud teamworkCloud = new TeamworkCloud(); @@ -199,7 +210,9 @@ public void testTwcBranch() { public void testTwcBranchDoesNotMatchTwc() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); + when(twcConfig.isUseAuthDelegation()).thenReturn(true); TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); + ProjectPersistence projectPersistence = mock(ProjectPersistence.class); when(twcConfig.isUseAuthDelegation()).thenReturn(true); @@ -207,16 +220,19 @@ public void testTwcBranchDoesNotMatchTwc() { twcPermissionsDelegateFactory.setApplicationContext(applicationContext); twcPermissionsDelegateFactory.setTwcConfig(twcConfig); twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); + twcPermissionsDelegateFactory.setProjectPersistence(projectPersistence); - Project project = new Project(); - Branch branch = new Branch(); - branch.setProject(project); + ProjectJson project = new ProjectJson(); + RefJson branch = new RefJson(); + String projectId = "projectid"; + branch.setProjectId(projectId); TwcMetadata twcMetadata = new TwcMetadata(); twcMetadata.setHost("host"); twcMetadata.setWorkspaceId("workspace"); twcMetadata.setResourceId("resource"); + when(projectPersistence.findById(projectId)).thenReturn(Optional.of(project)); when(twcMetadataService.getTwcMetadata(project)).thenReturn(twcMetadata); when(twcConfig.getTeamworkCloud("host")).thenReturn(null); @@ -231,22 +247,27 @@ public void testTwcBranchDoesNotMatchTwc() { public void testTwcBranchIncompleteMetadata() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); + when(twcConfig.isUseAuthDelegation()).thenReturn(true); TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); + ProjectPersistence projectPersistence = mock(ProjectPersistence.class); TwcPermissionsDelegateFactory twcPermissionsDelegateFactory = new TwcPermissionsDelegateFactory(); twcPermissionsDelegateFactory.setApplicationContext(applicationContext); twcPermissionsDelegateFactory.setTwcConfig(twcConfig); twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); + twcPermissionsDelegateFactory.setProjectPersistence(projectPersistence); - Project project = new Project(); - Branch branch = new Branch(); - branch.setProject(project); + ProjectJson project = new ProjectJson(); + RefJson branch = new RefJson(); + String projectId = "projectid"; + branch.setProjectId(projectId); TwcMetadata twcMetadata = new TwcMetadata(); twcMetadata.setHost("host"); twcMetadata.setWorkspaceId("workspace"); //twcMetadata.setResourceId("resource"); //Resource is missing + when(projectPersistence.findById(projectId)).thenReturn(Optional.of(project)); when(twcMetadataService.getTwcMetadata(project)).thenReturn(twcMetadata); PermissionsDelegate delegate = twcPermissionsDelegateFactory.getPermissionsDelegate(branch); @@ -258,17 +279,22 @@ public void testTwcBranchIncompleteMetadata() { public void testTwcBranchNullMetadata() { ApplicationContext applicationContext = mock(ApplicationContext.class); TwcConfig twcConfig = mock(TwcConfig.class); + when(twcConfig.isUseAuthDelegation()).thenReturn(true); TwcMetadataService twcMetadataService = mock(TwcMetadataService.class); + ProjectPersistence projectPersistence = mock(ProjectPersistence.class); TwcPermissionsDelegateFactory twcPermissionsDelegateFactory = new TwcPermissionsDelegateFactory(); twcPermissionsDelegateFactory.setApplicationContext(applicationContext); twcPermissionsDelegateFactory.setTwcConfig(twcConfig); twcPermissionsDelegateFactory.setTwcMetadataService(twcMetadataService); + twcPermissionsDelegateFactory.setProjectPersistence(projectPersistence); - Project project = new Project(); - Branch branch = new Branch(); - branch.setProject(project); + ProjectJson project = new ProjectJson(); + RefJson branch = new RefJson(); + String projectId = "projectid"; + branch.setProjectId(projectId); + when(projectPersistence.findById(projectId)).thenReturn(Optional.of(project)); when(twcMetadataService.getTwcMetadata(project)).thenReturn(null); PermissionsDelegate delegate = twcPermissionsDelegateFactory.getPermissionsDelegate(branch); diff --git a/twc/src/test/java/org/openmbee/mms/twc/security/TwcAuthenticationFilterTest.java b/twc/src/test/java/org/openmbee/mms/twc/security/TwcAuthenticationFilterTest.java index ed214ad5c..20a727fef 100644 --- a/twc/src/test/java/org/openmbee/mms/twc/security/TwcAuthenticationFilterTest.java +++ b/twc/src/test/java/org/openmbee/mms/twc/security/TwcAuthenticationFilterTest.java @@ -23,7 +23,7 @@ public class TwcAuthenticationFilterTest { TwcConfig twcConfig; @Mock - TwcUserDetailsService userDetailsService; + TwcUserDetailsService userDetailsService; @Mock TwcAuthenticationProvider twcAuthProvider; diff --git a/twc/src/test/java/org/openmbee/mms/twc/utilities/TwcPermissionUtilsTest.java b/twc/src/test/java/org/openmbee/mms/twc/utilities/TwcPermissionUtilsTest.java index ed3c26f57..157db4bf3 100644 --- a/twc/src/test/java/org/openmbee/mms/twc/utilities/TwcPermissionUtilsTest.java +++ b/twc/src/test/java/org/openmbee/mms/twc/utilities/TwcPermissionUtilsTest.java @@ -12,17 +12,10 @@ import org.json.JSONArray; import org.json.JSONObject; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import static junit.framework.TestCase.assertNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -282,13 +275,13 @@ public void getUsersListTestSuccesful() { .thenAnswer((invocation) -> { Object[] args = invocation.getArguments(); - assertEquals("http://twc.domain.com:8111/osmc/workspaces/workspaceID1/" + assertEquals("http://twc.domain.com:8111/osmc/" + "resources/resourceID1/roles/roleID1/users", args[0]); return responseEntity; }); - actualUsers = twcPermUtils.getUsersList(workspaceId, twc, resourceId, roleId); + actualUsers = twcPermUtils.getUsersList(twc, resourceId, roleId); assertTrue(expectedUsers.equals(actualUsers)); } @@ -344,13 +337,13 @@ public void nullResponseEntityBodyForUsersList() { .thenAnswer((invocation) -> { Object[] args = invocation.getArguments(); - assertEquals("http://twc.domain.com:8111/osmc/workspaces/workspaceID1/" + assertEquals("http://twc.domain.com:8111/osmc/" + "resources/resourceID1/roles/roleID1/users", args[0]); return responseEntity; }); - actualUsers = twcPermUtils.getUsersList(workspaceId, twc, resourceId, roleId); + actualUsers = twcPermUtils.getUsersList(twc, resourceId, roleId); assertNull(actualUsers); } @@ -395,7 +388,7 @@ public void nullResponseEntityForUserLists() { throw new RuntimeException("Test Exception -- should be caught"); }); - actualUsers = twcPermUtils.getUsersList(workspaceId, twc, resourceId, roleId); + actualUsers = twcPermUtils.getUsersList(twc, resourceId, roleId); assertNull(actualUsers); } diff --git a/twc/twc.gradle b/twc/twc.gradle index 9490ed461..8b03a4615 100644 --- a/twc/twc.gradle +++ b/twc/twc.gradle @@ -1,5 +1,4 @@ dependencies { - implementation project(':rdb') implementation project(':crud') @@ -12,3 +11,8 @@ dependencies { testImplementation commonDependencies.'spring-boot-starter-test' } +tasks { + processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} \ No newline at end of file diff --git a/view/README.md b/view/README.md new file mode 100644 index 000000000..680342455 --- /dev/null +++ b/view/README.md @@ -0,0 +1 @@ +## VIEW \ No newline at end of file diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/controllers/DocumentsResponse.java b/view/src/main/java/org/openmbee/mms/view/controllers/DocumentsResponse.java similarity index 92% rename from cameo/src/main/java/org/openmbee/mms/cameo/controllers/DocumentsResponse.java rename to view/src/main/java/org/openmbee/mms/view/controllers/DocumentsResponse.java index ad46b479f..b9a089862 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/controllers/DocumentsResponse.java +++ b/view/src/main/java/org/openmbee/mms/view/controllers/DocumentsResponse.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.cameo.controllers; +package org.openmbee.mms.view.controllers; import java.util.ArrayList; import java.util.List; diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/controllers/GroupsResponse.java b/view/src/main/java/org/openmbee/mms/view/controllers/GroupsResponse.java similarity index 92% rename from cameo/src/main/java/org/openmbee/mms/cameo/controllers/GroupsResponse.java rename to view/src/main/java/org/openmbee/mms/view/controllers/GroupsResponse.java index a7c90d175..12202f559 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/controllers/GroupsResponse.java +++ b/view/src/main/java/org/openmbee/mms/view/controllers/GroupsResponse.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.cameo.controllers; +package org.openmbee.mms.view.controllers; import java.util.ArrayList; import java.util.List; diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/controllers/MountsResponse.java b/view/src/main/java/org/openmbee/mms/view/controllers/MountsResponse.java similarity index 92% rename from cameo/src/main/java/org/openmbee/mms/cameo/controllers/MountsResponse.java rename to view/src/main/java/org/openmbee/mms/view/controllers/MountsResponse.java index d6de649e0..756ab1122 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/controllers/MountsResponse.java +++ b/view/src/main/java/org/openmbee/mms/view/controllers/MountsResponse.java @@ -1,4 +1,4 @@ -package org.openmbee.mms.cameo.controllers; +package org.openmbee.mms.view.controllers; import java.util.ArrayList; import java.util.List; diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/controllers/VeController.java b/view/src/main/java/org/openmbee/mms/view/controllers/VeController.java similarity index 72% rename from cameo/src/main/java/org/openmbee/mms/cameo/controllers/VeController.java rename to view/src/main/java/org/openmbee/mms/view/controllers/VeController.java index 914cd54bb..6e54ae507 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/controllers/VeController.java +++ b/view/src/main/java/org/openmbee/mms/view/controllers/VeController.java @@ -1,12 +1,14 @@ -package org.openmbee.mms.cameo.controllers; +package org.openmbee.mms.view.controllers; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.ArrayList; import java.util.Map; -import org.openmbee.mms.cameo.services.CameoViewService; + +import org.openmbee.mms.view.services.ViewService; import org.openmbee.mms.core.objects.ElementsRequest; import org.openmbee.mms.core.objects.ElementsResponse; +import org.openmbee.mms.core.services.GenericServiceFactory; import org.openmbee.mms.crud.controllers.BaseController; import org.openmbee.mms.json.MountJson; import org.springframework.beans.factory.annotation.Autowired; @@ -21,16 +23,17 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; + @RestController @RequestMapping("/projects/{projectId}/refs/{refId}") @Tag(name = "Views") public class VeController extends BaseController { - private CameoViewService cameoViewService; + private GenericServiceFactory genericServiceFactory; @Autowired - public VeController(CameoViewService cameoViewService) { - this.cameoViewService = cameoViewService; + public void setGenericServiceFactory( GenericServiceFactory serviceFactory){ + this.genericServiceFactory = serviceFactory; } @GetMapping("/mounts") @@ -40,9 +43,7 @@ public MountsResponse getMounts( @PathVariable String refId, @RequestParam(required = false) String commitId, @RequestParam(required = false) Map params) { - - MountJson json = cameoViewService - .getProjectUsages(projectId, refId, params.get("commitId"), new ArrayList<>()); + MountJson json = genericServiceFactory.getServiceForSchema( ViewService.class ,getProjectType(projectId)).getProjectUsages(projectId, refId, params.get("commitId"), new ArrayList<>(), true); MountsResponse res = new MountsResponse(); res.getProjects().add(json); return res; @@ -56,7 +57,7 @@ public DocumentsResponse getDocuments( @RequestParam(required = false) String commitId, @RequestParam(required = false) Map params) { - ElementsResponse docs = cameoViewService.getDocuments(projectId, refId, params); + ElementsResponse docs = genericServiceFactory.getServiceForSchema( ViewService.class , getProjectType(projectId)).getDocuments(projectId, refId, params); return (new DocumentsResponse()).setDocuments(docs.getElements()); } @@ -69,7 +70,7 @@ public ElementsResponse getView( @RequestParam(required = false) String commitId, @RequestParam(required = false) Map params) { - ElementsResponse res = cameoViewService.getView(projectId, refId, viewId, params); + ElementsResponse res = genericServiceFactory.getServiceForSchema( ViewService.class , getProjectType(projectId)).getView(projectId, refId, viewId, params); handleSingleResponse(res); return res; } @@ -83,7 +84,7 @@ public ElementsResponse getViews( @RequestParam(required = false) String commitId, @RequestParam(required = false) Map params) { - return cameoViewService.getViews(projectId, refId, req, params); + return genericServiceFactory.getServiceForSchema( ViewService.class , getProjectType(projectId)).getViews(projectId, refId, req, params); } @PostMapping("/views") @@ -96,8 +97,9 @@ public ElementsResponse createOrUpdateViews( @RequestParam(required = false) Map params, @Parameter(hidden = true) Authentication auth) { - ElementsResponse res = cameoViewService.createOrUpdate(projectId, refId, req, params, auth.getName()); - cameoViewService.addChildViews(res, params); + ViewService viewService = genericServiceFactory.getServiceForSchema( ViewService.class , getProjectType(projectId)); + ElementsResponse res = viewService.createOrUpdate(projectId, refId, req, params, auth.getName()); + viewService.addChildViews(res, params); return res; } @@ -108,7 +110,7 @@ public GroupsResponse getGroups( @PathVariable String refId, @RequestParam(required = false) Map params) { - ElementsResponse groups = cameoViewService.getGroups(projectId, refId, params); + ElementsResponse groups = genericServiceFactory.getServiceForSchema( ViewService.class , getProjectType(projectId)).getGroups(projectId, refId, params); return (new GroupsResponse()).setGroups(groups.getElements()); } } diff --git a/cameo/src/main/java/org/openmbee/mms/cameo/services/PropertyData.java b/view/src/main/java/org/openmbee/mms/view/services/PropertyData.java similarity index 61% rename from cameo/src/main/java/org/openmbee/mms/cameo/services/PropertyData.java rename to view/src/main/java/org/openmbee/mms/view/services/PropertyData.java index e9f0847a4..f654d1de0 100644 --- a/cameo/src/main/java/org/openmbee/mms/cameo/services/PropertyData.java +++ b/view/src/main/java/org/openmbee/mms/view/services/PropertyData.java @@ -1,15 +1,9 @@ -package org.openmbee.mms.cameo.services; +package org.openmbee.mms.view.services; -import org.openmbee.mms.data.domains.scoped.Node; import org.openmbee.mms.json.ElementJson; public class PropertyData { - private Node propertyNode; - - private Node assocNode; - - private Node assocPropertyNode; private ElementJson typeJson; @@ -21,13 +15,6 @@ public class PropertyData { private boolean isView; - public Node getPropertyNode() { - return propertyNode; - } - - public void setPropertyNode(Node propertyNode) { - this.propertyNode = propertyNode; - } public ElementJson getTypeJson() { return typeJson; @@ -53,14 +40,6 @@ public void setView(boolean view) { isView = view; } - public Node getAssocNode() { - return assocNode; - } - - public void setAssocNode(Node assocNode) { - this.assocNode = assocNode; - } - public ElementJson getAssocJson() { return assocJson; } @@ -69,13 +48,6 @@ public void setAssocJson(ElementJson assocJson) { this.assocJson = assocJson; } - public Node getAssocPropertyNode() { - return assocPropertyNode; - } - - public void setAssocPropertyNode(Node assocPropertyNode) { - this.assocPropertyNode = assocPropertyNode; - } public ElementJson getAssocPropertyJson() { return assocPropertyJson; diff --git a/view/src/main/java/org/openmbee/mms/view/services/ViewService.java b/view/src/main/java/org/openmbee/mms/view/services/ViewService.java new file mode 100644 index 000000000..a22dfdb32 --- /dev/null +++ b/view/src/main/java/org/openmbee/mms/view/services/ViewService.java @@ -0,0 +1,19 @@ +package org.openmbee.mms.view.services; + +import java.util.Map; +import org.openmbee.mms.core.objects.ElementsRequest; +import org.openmbee.mms.core.objects.ElementsResponse; +import org.openmbee.mms.core.services.HierarchicalNodeService; + +public interface ViewService extends HierarchicalNodeService { + + ElementsResponse getDocuments(String projectId, String refId, Map params); + + ElementsResponse getView(String projectId, String refId, String elementId, Map params); + + ElementsResponse getViews(String projectId, String refId, ElementsRequest req, Map params); + + void addChildViews(ElementsResponse res, Map params); + + ElementsResponse getGroups(String projectId, String refId, Map params); +} diff --git a/view/view.gradle b/view/view.gradle new file mode 100644 index 000000000..21102b288 --- /dev/null +++ b/view/view.gradle @@ -0,0 +1,5 @@ +dependencies { + implementation project(':crud') + + testImplementation commonDependencies.'spring-boot-starter-test' +} diff --git a/webhooks/src/main/java/org/openmbee/mms/webhooks/components/EventListener.java b/webhooks/src/main/java/org/openmbee/mms/webhooks/components/EventListener.java index 91835432f..afd377bba 100644 --- a/webhooks/src/main/java/org/openmbee/mms/webhooks/components/EventListener.java +++ b/webhooks/src/main/java/org/openmbee/mms/webhooks/components/EventListener.java @@ -1,8 +1,8 @@ package org.openmbee.mms.webhooks.components; -import org.openmbee.mms.core.dao.WebhookDAO; -import org.openmbee.mms.data.domains.global.Webhook; import org.openmbee.mms.core.objects.EventObject; +import org.openmbee.mms.webhooks.json.WebhookJson; +import org.openmbee.mms.webhooks.persistence.WebhookPersistence; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -27,18 +27,18 @@ public class EventListener implements ApplicationListener { @Value("${webhook.default.all:#{null}}") private Optional allEvents; - private WebhookDAO eventRepository; + private WebhookPersistence eventRepository; @Autowired - public void setEventRepository(WebhookDAO eventRepository) { + public void setEventRepository(WebhookPersistence eventRepository) { this.eventRepository = eventRepository; } @Override public void onApplicationEvent(EventObject eventObject) { - List webhooks = eventRepository.findAllByProject_ProjectId(eventObject.getProjectId()); + List webhooks = eventRepository.findAllByProjectId(eventObject.getProjectId()); - for (Webhook webhook : webhooks) { + for (WebhookJson webhook : webhooks) { sendWebhook(webhook.getUrl(), eventObject.getSource()); } diff --git a/webhooks/src/main/java/org/openmbee/mms/webhooks/controllers/WebhooksController.java b/webhooks/src/main/java/org/openmbee/mms/webhooks/controllers/WebhooksController.java index f2e5a26c4..fbb09c45d 100644 --- a/webhooks/src/main/java/org/openmbee/mms/webhooks/controllers/WebhooksController.java +++ b/webhooks/src/main/java/org/openmbee/mms/webhooks/controllers/WebhooksController.java @@ -3,19 +3,17 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.tags.Tag; -import org.openmbee.mms.core.dao.ProjectDAO; -import org.openmbee.mms.core.dao.WebhookDAO; +import org.openmbee.mms.core.dao.ProjectPersistence; import org.openmbee.mms.core.exceptions.BadRequestException; import org.openmbee.mms.core.exceptions.NotFoundException; -import org.openmbee.mms.data.domains.global.Project; -import org.openmbee.mms.data.domains.global.Webhook; +import org.openmbee.mms.json.ProjectJson; import org.openmbee.mms.webhooks.json.WebhookJson; import org.openmbee.mms.webhooks.objects.WebhookRequest; import org.openmbee.mms.webhooks.objects.WebhookResponse; +import org.openmbee.mms.webhooks.persistence.WebhookPersistence; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.util.*; @@ -25,18 +23,18 @@ @Tag(name = "Webhooks") public class WebhooksController { - private WebhookDAO webhookRepository; - private ProjectDAO projectRepository; + private ProjectPersistence projectPersistence; + private WebhookPersistence webhookPersistence; private ObjectMapper om; @Autowired - public void setWebhookRepository(WebhookDAO webhookRepository) { - this.webhookRepository = webhookRepository; + public void setProjectPersistence(ProjectPersistence projectPersistence) { + this.projectPersistence = projectPersistence; } @Autowired - public void setProjectRepository(ProjectDAO projectRepository) { - this.projectRepository = projectRepository; + public void setWebhookPersistence(WebhookPersistence webhookPersistence) { + this.webhookPersistence = webhookPersistence; } @Autowired @@ -49,20 +47,12 @@ public void setObjectMapper(ObjectMapper om) { public WebhookResponse getAllWebhooks(@PathVariable String projectId) { WebhookResponse response = new WebhookResponse(); - List webhooks = webhookRepository.findAllByProject_ProjectId(projectId); - - for (Webhook webhook : webhooks) { - WebhookJson webhookJson = new WebhookJson(); - webhookJson.merge(convertToMap(webhook)); - webhookJson.setId(webhook.getId().toString()); - webhookJson.setProjectId(projectId); - response.getWebhooks().add(webhookJson); - } + List webhooks = webhookPersistence.findAllByProjectId(projectId); + response.getWebhooks().addAll(webhooks); return response; } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - @Transactional @PreAuthorize("@mss.hasProjectPrivilege(authentication, #projectId, 'PROJECT_CREATE_WEBHOOKS', false)") public WebhookResponse createOrUpdateWebhooks(@PathVariable String projectId, @RequestBody WebhookRequest webhooksPost) { @@ -71,28 +61,29 @@ public WebhookResponse createOrUpdateWebhooks(@PathVariable String projectId, @R } WebhookResponse response = new WebhookResponse(); - Optional project = projectRepository.findByProjectId(projectId); + Optional project = projectPersistence.findById(projectId); + if(project.isEmpty()) { + throw new NotFoundException("Project not found"); + } for (WebhookJson json: webhooksPost.getWebhooks()) { - Optional existing = webhookExists(json, projectId); - Webhook hook; - if (!existing.isPresent()) { - hook = new Webhook(); - hook.setProject(project.get()); - } else { - hook = existing.get(); + Optional existing = webhookExists(json, projectId); + if (existing.isPresent()) { + if (!projectId.equals(existing.get().getProjectId())) { + throw new BadRequestException("Cannot move webhooks between projects"); + } + json.merge(existing.get()); } - hook.setUrl(json.getUrl()); - webhookRepository.save(hook); - json.setId(hook.getId().toString()); json.setProjectId(projectId); - response.getWebhooks().add(json); + if(json.getId() == null) { + json.setId(UUID.randomUUID().toString()); + } + response.getWebhooks().add(webhookPersistence.save(json)); } return response; } @DeleteMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - @Transactional @PreAuthorize("@mss.hasProjectPrivilege(authentication, #projectId, 'PROJECT_CREATE_WEBHOOKS', false)") public WebhookResponse deleteWebhooks(@PathVariable String projectId, @RequestBody WebhookRequest webhookRequest) { @@ -102,13 +93,13 @@ public WebhookResponse deleteWebhooks(@PathVariable String projectId, @RequestBo } WebhookResponse response = new WebhookResponse(); - List webhooks = webhookRepository.findAllByProject_ProjectId(projectId); + List webhooks = webhookPersistence.findAllByProjectId(projectId); if (webhooks.isEmpty()) { throw new NotFoundException(response.addMessage("No web hooks found for project")); } - for (Webhook webhook : webhooks) { + for (WebhookJson webhook : webhooks) { if (uris.contains(webhook.getUrl())) { - webhookRepository.delete(webhook); + webhookPersistence.delete(webhook); response.addMessage(String.format("Web hook for project %s to %s deleted", projectId, webhook.getUrl())); } } @@ -119,13 +110,16 @@ public Map convertToMap(Object obj) { return om.convertValue(obj, new TypeReference>() {}); } - private Optional webhookExists(WebhookJson json, String projectId) { + private Optional webhookExists(WebhookJson json, String projectId) { if (json.getId() != null) { - Optional hook = webhookRepository.findById(Long.parseLong(json.getId())); - if (hook.isPresent() && hook.get().getProject().getProjectId().equals(projectId)) { + Optional hook = webhookPersistence.findById(json.getId()); + if(hook.isEmpty()) { + hook = webhookPersistence.findByProjectIdAndUrl(projectId, json.getUrl()); + } + if (hook.isPresent() && hook.get().getProjectId().equals(projectId)) { return hook; //ensure hook by id matches the project being requested } } - return webhookRepository.findByProject_ProjectIdAndUrl(projectId, json.getUrl()); + return webhookPersistence.findByProjectIdAndUrl(projectId, json.getUrl()); } } diff --git a/webhooks/src/main/java/org/openmbee/mms/webhooks/persistence/WebhookPersistence.java b/webhooks/src/main/java/org/openmbee/mms/webhooks/persistence/WebhookPersistence.java new file mode 100644 index 000000000..adaead0eb --- /dev/null +++ b/webhooks/src/main/java/org/openmbee/mms/webhooks/persistence/WebhookPersistence.java @@ -0,0 +1,19 @@ +package org.openmbee.mms.webhooks.persistence; + +import org.openmbee.mms.webhooks.json.WebhookJson; + +import java.util.List; +import java.util.Optional; + +public interface WebhookPersistence { + + WebhookJson save(WebhookJson webhook); + + Optional findById(String id); + + List findAllByProjectId(String projectId); + + Optional findByProjectIdAndUrl(String projectId, String url); + + void delete(WebhookJson webhook); +} diff --git a/webhooks/webhooks.gradle b/webhooks/webhooks.gradle index 378848af7..eefabc146 100644 --- a/webhooks/webhooks.gradle +++ b/webhooks/webhooks.gradle @@ -1,3 +1,9 @@ dependencies { implementation project(':core') +} + +tasks { + processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } } \ No newline at end of file