diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Project.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Project.java index b9e5092..c93927f 100644 --- a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Project.java +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Project.java @@ -8,7 +8,9 @@ import org.bson.Document; import org.gcube.application.geoportal.common.model.document.accounting.PublicationInfo; import org.gcube.application.geoportal.common.model.document.identification.IdentificationReference; import org.gcube.application.geoportal.common.model.document.lifecycle.LifecycleInformation; +import org.gcube.application.geoportal.common.model.document.relationships.Relationship; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -88,4 +90,18 @@ public class Project { .filter(item -> item.getType().equals(type)).collect(Collectors.toList()); }; + @JsonIgnore + public List getRelationshipsByName(String relation){ + if(relationships==null)return Collections.emptyList(); + else return relationships.stream().filter(relationship -> relationship. + getRelationshipName().equals(relation)).collect(Collectors.toList()); + }; + + + @JsonIgnore + public Project addRelation(Relationship rel){ + if(relationships==null) relationships = new ArrayList<>(); + relationships.add(rel); + return this; + } } diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Relationship.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/relationships/Relationship.java similarity index 87% rename from geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Relationship.java rename to geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/relationships/Relationship.java index 5bc17fd..2d8241e 100644 --- a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Relationship.java +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/relationships/Relationship.java @@ -1,4 +1,4 @@ -package org.gcube.application.geoportal.common.model.document; +package org.gcube.application.geoportal.common.model.document.relationships; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/relationships/RelationshipNavigationObject.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/relationships/RelationshipNavigationObject.java new file mode 100644 index 0000000..4da2092 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/relationships/RelationshipNavigationObject.java @@ -0,0 +1,24 @@ +package org.gcube.application.geoportal.common.model.document.relationships; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import org.gcube.application.geoportal.common.model.document.Project; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@ToString +public class RelationshipNavigationObject { + public static final String CHILDREN="_children"; + public static final String TARGET="_target"; + + @JsonProperty(CHILDREN) + List children; + + @NonNull + @JsonProperty(TARGET) + Project target; +} diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/InterfaceConstants.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/InterfaceConstants.java index a7fb115..b70ecb7 100644 --- a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/InterfaceConstants.java +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/rest/InterfaceConstants.java @@ -15,8 +15,6 @@ public class InterfaceConstants { public static final String PLUGINS="plugins"; - - public static final String CONCESSIONI="concessioni"; public static final String MONGO_CONCESSIONI="mongo-concessioni"; @@ -29,6 +27,9 @@ public class InterfaceConstants { public static final String QUERY_PATH="query"; public static final String STEP="step"; + + public static final String RELATIONSHIP="relationship"; + } public static final class Parameters{ @@ -38,6 +39,10 @@ public class InterfaceConstants { public static final String FORCE="force"; + public static final String RELATIONSHIP_ID="relationship_id"; + public static final String DEEP="deep"; + public static final String TARGET_UCD="target_id"; + public static final String TARGET_ID="target_ucd"; } diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManagerI.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManagerI.java index a9c0dc3..e13adfc 100644 --- a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManagerI.java +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManagerI.java @@ -25,6 +25,11 @@ public interface MongoManagerI { // update public T update(String id,Document toSetDocument) throws IOException, StepException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess; + public T setRelation(String id,String relation, String targetUCD, String targetId) throws IOException, StepException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess, RegistrationException, ConfigurationException; + public T deleteRelation(String id,String relation, String targetUCD, String targetId) throws IOException, StepException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess, RegistrationException, ConfigurationException; + + + // delete public void delete(String id,boolean force) throws DeletionException, InvalidUserRoleException, ProjectLockedException, ProjectNotFoundException, UnauthorizedAccess, JsonProcessingException, InvalidLockException; diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ProfiledMongoManager.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ProfiledMongoManager.java index 0bbe411..e0da858 100644 --- a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ProfiledMongoManager.java +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ProfiledMongoManager.java @@ -16,6 +16,7 @@ import org.gcube.application.cms.plugins.faults.EventException; import org.gcube.application.cms.plugins.faults.InsufficientPrivileges; import org.gcube.application.cms.plugins.faults.StepException; import org.gcube.application.cms.plugins.faults.UnrecognizedStepException; +import org.gcube.application.geoportal.common.model.document.relationships.Relationship; import org.gcube.application.geoportal.common.model.plugins.LifecycleManagerDescriptor; import org.gcube.application.cms.plugins.reports.DocumentHandlingReport; import org.gcube.application.cms.plugins.reports.StepExecutionReport; @@ -66,6 +67,7 @@ import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Consumer; +import java.util.stream.Collectors; import static org.gcube.application.cms.serialization.Serialization.*; @@ -296,6 +298,58 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< } } + @Override + public Project setRelation(String id, String relation, String targetUCD, String targetId) throws IOException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess, RegistrationException, ConfigurationException { + Project toUpdate=lock(id,"Set Relation"); + try{ + // check if relation existing + List relations = toUpdate.getRelationshipsByName(relation); + if(!relations.isEmpty()){ + // check if targetUCD+targetID already present + List matching = relations.stream().filter(r -> + r.getTargetID().equals(targetId)&&r.getTargetUCD().equals(targetUCD)).collect(Collectors.toList()); + if(matching.size()>0) throw new WebApplicationException("Relationship "+relation+" -> "+targetUCD+" : "+targetId+" already set", Response.Status.EXPECTATION_FAILED); + } + // check if target exists + ProfiledMongoManager otherManager = (targetUCD.equals(this.useCaseDescriptor.getId()))?this:new ProfiledMongoManager(targetUCD); + Project other = getByID(targetId); + // add relation + Relationship rel = new Relationship(); + rel.setRelationshipName(relation); + rel.setTargetID(targetId); + rel.setTargetUCD(targetUCD); + + return onUpdate(toUpdate.addRelation(rel)); + }catch(Throwable t){ + log.error("Unexpected exception ",t); + unlock(toUpdate); + throw t; + } + } + + @Override + public Project deleteRelation(String id, String relation, String targetUCD, String targetId) throws IOException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException, InvalidUserRoleException, UnauthorizedAccess, RegistrationException, ConfigurationException { + Project toUpdate=lock(id,"Set Relation"); + try{ + // check if relation existing + List relations = toUpdate.getRelationships(); + if(relations!=null && !relations.isEmpty()){ + int beforeSize = relations.size(); + toUpdate.getRelationships().removeIf(r -> + r.getRelationshipName().equals(relation)&& + r.getTargetUCD().equals(targetUCD)&& + r.getTargetID().equals(targetId)); + // update only if something changed + if(toUpdate.getRelationships().size()!=beforeSize) return onUpdate(toUpdate); + } + return toUpdate; + }catch(Throwable t){ + log.error("Unexpected exception ",t); + unlock(toUpdate); + throw t; + } + } + private Project onUpdate(Project toUpdate) throws EventException { UserUtils.AuthenticatedUser u = UserUtils.getCurrent(); diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/ProfiledDocuments.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/ProfiledDocuments.java index 376ddea..b0b5e63 100644 --- a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/ProfiledDocuments.java +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/ProfiledDocuments.java @@ -5,6 +5,8 @@ import org.bson.Document; import org.gcube.application.cms.implementations.ImplementationProvider; import org.gcube.application.geoportal.common.model.document.Project; import org.gcube.application.geoportal.common.model.configuration.Configuration; +import org.gcube.application.geoportal.common.model.document.relationships.Relationship; +import org.gcube.application.geoportal.common.model.document.relationships.RelationshipNavigationObject; import org.gcube.application.geoportal.common.model.rest.QueryRequest; import org.gcube.application.geoportal.common.rest.InterfaceConstants; import org.gcube.application.geoportal.common.model.rest.RegisterFileSetRequest; @@ -16,6 +18,8 @@ import org.gcube.application.geoportal.service.engine.providers.ConfigurationCac import javax.ws.rs.*; import javax.ws.rs.core.MediaType; +import java.util.ArrayList; +import java.util.List; @Path(InterfaceConstants.Methods.PROJECTS+"/{"+InterfaceConstants.Parameters.UCID +"}") @Slf4j @@ -207,5 +211,88 @@ public class ProfiledDocuments { }.execute().getResult(); } + // Relationships + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Methods.RELATIONSHIP+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}" + + "/{"+InterfaceConstants.Parameters.RELATIONSHIP_ID+"}") + public String getRelationshipChain(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id, + @PathParam(InterfaceConstants.Parameters.RELATIONSHIP_ID) String relationshipId, + @DefaultValue("false") + @QueryParam(InterfaceConstants.Parameters.DEEP) Boolean deep) { + return new GuardedMethod(){ + @Override + protected String run() throws Exception, WebApplicationException { + // recursive + log.info("UCD {} : Getting Relationships List for {} [rel : {}, recurse {}]", + manager.getUseCaseDescriptor().getId(),id,relationshipId,deep); + Project current = manager.getByID(id); + long startTime=System.currentTimeMillis(); + List toReturn = getLinked(current,relationshipId,deep); + log.info("Got {} relationship elements in {}ms",toReturn.size(),(System.currentTimeMillis()-startTime)); + return Serialization.write(toReturn); + } + }.execute().getResult(); + } + + + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Methods.RELATIONSHIP+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}" + + "/{"+InterfaceConstants.Parameters.RELATIONSHIP_ID+"}") + public Project setRelation(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id, + @PathParam(InterfaceConstants.Parameters.RELATIONSHIP_ID) String relationshipId, + @QueryParam(InterfaceConstants.Parameters.TARGET_ID) String targetId, + @QueryParam(InterfaceConstants.Parameters.TARGET_UCD) String targetUCD) { + return new GuardedMethod() { + @Override + protected Project run() throws Exception, WebApplicationException { + log.info("Set relation from Project ({} : {}) [{}]-> ({} : {})", + manager.getUseCaseDescriptor().getId(), id,relationshipId,targetUCD,targetId); + return manager.setRelation(id,relationshipId,targetUCD,targetId); + } + }.execute().getResult(); + } + + + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Methods.RELATIONSHIP+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}" + + "/{"+InterfaceConstants.Parameters.RELATIONSHIP_ID+"}") + public Project deleteRelation(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id, + @PathParam(InterfaceConstants.Parameters.RELATIONSHIP_ID) String relationshipId, + @QueryParam(InterfaceConstants.Parameters.TARGET_ID) String targetId, + @QueryParam(InterfaceConstants.Parameters.TARGET_UCD) String targetUCD) { + return new GuardedMethod() { + @Override + protected Project run() throws Exception, WebApplicationException { + log.info("Deleting relation from Project ({} : {}) [{}]-> ({} : {})", + manager.getUseCaseDescriptor().getId(), id,relationshipId,targetUCD,targetId); + return manager.deleteRelation(id,relationshipId,targetUCD,targetId); + } + }.execute().getResult(); + } + + + + private static List getLinked(Project current, String relationName, Boolean recurse)throws Exception{ + log.debug("Getting Relationships Lists for {} [rel : {}, recurse {}]",current.getId(),relationName,recurse); + ArrayList toReturn = new ArrayList<>(); + List existing = current.getRelationshipsByName(relationName); + for (Relationship relationship : existing) { + try{ + log.trace("Navigating from {} : {} to[rel {} ] {} : {}",relationship.getTargetUCD(), + relationship.getTargetID(),relationship.getRelationshipName(),current.getProfileID(),current.getId()); + RelationshipNavigationObject linkedProject = new RelationshipNavigationObject(); + linkedProject.setTarget(new ProfiledMongoManager(relationship.getTargetUCD()).getByID(relationship.getTargetID())); + if(recurse) linkedProject.setChildren(getLinked(linkedProject.getTarget(),relationName,recurse)); + toReturn.add(linkedProject); + } catch (Exception e) { + log.warn("Unable to navigate from {} : {} to[rel {} ] {} : {}",relationship.getTargetUCD(), + relationship.getTargetID(),relationship.getRelationshipName(),current.getProfileID(),current.getId(),e); + } + } + return toReturn; + } }