diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Lock.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Lock.java new file mode 100644 index 0000000..bd99604 --- /dev/null +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/Lock.java @@ -0,0 +1,28 @@ +package org.gcube.application.geoportal.common.model.document; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import org.gcube.application.geoportal.common.model.document.accounting.AccountingInfo; +import org.gcube.application.geoportal.common.model.document.accounting.User; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@ToString +public class Lock { + + public static final String ID="_id"; + public static final String INFO="_info"; + public static final String OPERATION="_operation"; + + @JsonProperty(INFO) + private AccountingInfo info; + @JsonProperty(ID) + private String id; + @JsonProperty(OPERATION) + private String operation; + +} 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 979949e..bad242c 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,6 +8,9 @@ import org.gcube.application.geoportal.common.model.document.accounting.Publicat import org.gcube.application.geoportal.common.model.document.lifecycle.LifecycleInformation; import org.gcube.application.geoportal.common.model.document.temporal.TemporalReference; +import java.util.Arrays; +import java.util.Objects; + @NoArgsConstructor @AllArgsConstructor @@ -26,6 +29,7 @@ public class Project { public static final String SPATIAL_REFERENCE="_spatialReference"; public static final String TEMPORAL_REFERENCE="_temporalReference"; public static final String THE_DOCUMENT="_theDocument"; + public static final String LOCK="_lock"; // CORE METADATA @@ -59,4 +63,22 @@ public class Project { @JsonProperty(THE_DOCUMENT) private Document theDocument; + + @JsonProperty(LOCK) + private Lock lock; + + + /** + * Similar to equals but without checking following fields + * + * lock + * @param o + * @return + */ + public boolean isEquivalent(Object o) { + if (this == o) return true; + if (!(o instanceof Project)) return false; + Project project = (Project) o; + return Objects.equals(getId(), project.getId()) && Objects.equals(getVersion(), project.getVersion()) && Objects.equals(getInfo(), project.getInfo()) && Objects.equals(getProfileID(), project.getProfileID()) && Objects.equals(getProfileVersion(), project.getProfileVersion()) && Objects.equals(getLifecycleInformation(), project.getLifecycleInformation()) && Arrays.equals(getRelationships(), project.getRelationships()) && Objects.equals(getSpatialReference(), project.getSpatialReference()) && Objects.equals(getTemporalReference(), project.getTemporalReference()) && Objects.equals(getTheDocument(), project.getTheDocument()); + } } 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 2f9496c..55eb763 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 @@ -10,6 +10,9 @@ import org.gcube.application.geoportal.common.model.rest.QueryRequest; import org.gcube.application.geoportal.common.model.rest.RegisterFileSetRequest; import org.gcube.application.geoportal.common.model.rest.ConfigurationException; import org.gcube.application.geoportal.service.model.internal.faults.DeletionException; +import org.gcube.application.geoportal.service.model.internal.faults.InvalidLockException; +import org.gcube.application.geoportal.service.model.internal.faults.ProjectLockedException; +import org.gcube.application.geoportal.service.model.internal.faults.ProjectNotFoundException; import org.gcube.common.storagehub.model.exceptions.StorageHubException; import java.io.IOException; @@ -22,7 +25,7 @@ public interface MongoManagerI { public T registerNew(Document toRegister) throws IOException, StepException, EventException; // update - public T update(String id,Document toSetDocument) throws IOException, StepException, EventException; + public T update(String id,Document toSetDocument) throws IOException, StepException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException; // delete @@ -30,17 +33,17 @@ public interface MongoManagerI { // get By ID - public T getByID(String id) throws IOException; + public T getByID(String id) throws IOException, ProjectNotFoundException; // query public Iterable query(QueryRequest request); public Iterable filter(QueryRequest request); - public T performStep(String id, String step, Document options) throws IOException, StepException; + public T performStep(String id, String step, Document options) throws IOException, StepException, ProjectLockedException, ProjectNotFoundException, InvalidLockException; - public T registerFileSet(String id, RegisterFileSetRequest request) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException; - public T deleteFileSet(String id, String destination, Boolean force) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException; + public T registerFileSet(String id, RegisterFileSetRequest request) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException; + public T deleteFileSet(String id, String destination, Boolean force) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException; public Configuration getConfiguration()throws ConfigurationException; } 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 297fac0..a1b3547 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 @@ -3,8 +3,12 @@ package org.gcube.application.geoportal.service.engine.mongo; import com.fasterxml.jackson.core.JsonProcessingException; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.FindOneAndReplaceOptions; +import com.mongodb.client.model.FindOneAndUpdateOptions; +import com.mongodb.client.model.ReturnDocument; import com.vdurmont.semver4j.Semver; import lombok.Getter; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.ArrayStack; import org.apache.commons.io.IOUtils; @@ -45,9 +49,8 @@ import org.gcube.application.geoportal.service.engine.WorkspaceManager; import org.gcube.application.geoportal.common.model.rest.ConfigurationException; import org.gcube.application.geoportal.service.engine.providers.PluginManager; import org.gcube.application.geoportal.service.engine.providers.ProfileMapCache; -import org.gcube.application.geoportal.service.model.internal.faults.DeletionException; +import org.gcube.application.geoportal.service.model.internal.faults.*; import org.gcube.application.cms.serialization.Serialization; -import org.gcube.application.geoportal.service.model.internal.faults.RegistrationException; import org.gcube.application.geoportal.service.utils.UserUtils; import org.gcube.common.storagehub.client.dsl.FolderContainer; import org.gcube.common.storagehub.model.exceptions.StorageHubException; @@ -56,6 +59,7 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import java.io.IOException; import java.io.InputStream; +import java.lang.invoke.SerializedLambda; import java.net.URL; import java.security.InvalidParameterException; import java.time.LocalDateTime; @@ -97,6 +101,71 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< return useCaseDescriptor.getId(); } + + protected Project lock(String id,String op) throws ProjectNotFoundException, ProjectLockedException, JsonProcessingException { + log.trace("Locking {} cause {} ",id,op); + + Lock lock = new Lock(); + lock.setId(UUID.randomUUID().toString()); + lock.setInfo(UserUtils.getCurrent().asInfo()); + lock.setOperation(op); + + // find one and update + // filter : id, lock == null + // update with new Lock object + Document filter = new Document(mongoIDFieldName(),asId(id)).append("$or",Arrays.asList( + new Document(Project.LOCK,new Document("$exists",false)), + new Document(Project.LOCK,new Document("$type","null")))); + log.debug("Lock filter is {} ",filter.toJson()); + Object obj = getCollection().findOneAndUpdate( + // filter by id and missing lock + filter, + // update lock info + new Document("$set",new Document(Project.LOCK, Serialization.asDocument(lock))), + new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER) + ); + + if(obj == null){ + // unable to lock, verify cause + Project p=getByID(id); + if(p.getLock()!=null) throw new ProjectLockedException("Project already locked : "+p.getLock()); + else { + log.error("Unable to lock {} ",id); + log.debug("Existing project is {} ",p); + throw new RuntimeException("Unable to lock unlocked project "+id); + } + } else return Serialization.convert(obj,Project.class); + } + + protected Project unlockAndUpdate(Project proj) throws InvalidLockException, ProjectNotFoundException, JsonProcessingException { + log.trace("Unlocking {} lock is {} ",proj.getId(),proj.getLock()); + // find one and update + Lock oldLock = proj.getLock(); + proj.setLock(null); + + Document filter = new Document(mongoIDFieldName(),asId(proj.getId())).append(Project.LOCK+"."+Lock.ID,oldLock.getId()); + + Object obj = getCollection().findOneAndReplace( + // filter by id and missing lock + filter, + // update lock info + asDocumentWithId(proj), + new FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER)); + + if(obj== null){ + // can-t unlock, check cause + Project p = getByID(proj.getId()); + throw new InvalidLockException("Found lock for "+p.getId()+" is "+p.getLock()+", expected is "+oldLock); + }return Serialization.convert(obj,Project.class); + + // filter : id, lock id + // update with project (NB without lock) + // if none matched + // not found if proj id non existent + // else invalid lock +// return null; + } + @Getter(lazy = true) private final LifecycleManager manager=getLCManager(); @@ -163,20 +232,24 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< ObjectId id =insertDoc(asDocumentWithId(toRegister)); log.info("Obtained id {} ",id); - return getByID(id.toHexString()); + try { + return getByID(id.toHexString()); + }catch (ProjectNotFoundException e){ + throw new WebApplicationException("Unexpected exception while registering project ",e); + } } @Override - public Project update(String id, Document toSet) throws IOException, EventException { + public Project update(String id, Document toSet) throws IOException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException { log.trace("Replacing {} ",toSet); - Project toUpdate=getByID(id); - toUpdate.setTheDocument(toSet); - toUpdate.getLifecycleInformation().cleanState(); - - toUpdate=onUpdate(toUpdate); - Project toReturn =convert(replaceDoc(asDocumentWithId(toUpdate),new ObjectId(id)), Project.class); - log.debug("Updated Project is {}",toReturn); - return toReturn; + Project toUpdate=lock(id,"Manual update"); + try { + toUpdate.setTheDocument(toSet); + toUpdate.getLifecycleInformation().cleanState(); + toUpdate = onUpdate(toUpdate); + }finally{ + return unlockAndUpdate(toUpdate); + } } @@ -192,7 +265,7 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< public void delete(String id,boolean force) throws DeletionException { log.debug("Deleting by ID {}, force {}",id,force); try{ - Project doc =getByID(id); + Project doc =lock(id,"Deletion { force : "+force+"}"); // TODO INVOKE LIFECYCLE @@ -218,9 +291,9 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< } @Override - public Project getByID(String id) throws WebApplicationException{ + public Project getByID(String id) throws ProjectNotFoundException{ Document doc=getDocById(asId(id)); - if(doc==null) throw new WebApplicationException("No document with ID "+id); + if(doc==null) throw new ProjectNotFoundException("No document with ID "+id); return convert(doc, Project.class); } @@ -250,8 +323,8 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< @Override - public Project performStep(String id, String step, Document options) throws StepException, JsonProcessingException { - Project document = getByID(id); + public Project performStep(String id, String step, Document options) throws StepException, JsonProcessingException, ProjectLockedException, ProjectNotFoundException, InvalidLockException { + Project document = lock(id,"Step "+step+" execution"); try{ document.getLifecycleInformation().cleanState(); document = step(document, step, options); @@ -268,7 +341,7 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< step,document.getLifecycleInformation().getLastOperationStatus()); log.debug("LifecycleInformation is {} ",document.getLifecycleInformation()); if(log.isTraceEnabled())log.trace("Document is {} ",Serialization.write(document)); - return convert(replaceDoc(asDocumentWithId(document),new ObjectId(id)), Project.class); + return unlockAndUpdate(document); } } @@ -287,103 +360,119 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< * */ @Override - public Project registerFileSet(String id, RegisterFileSetRequest request) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException { + public Project registerFileSet(String id, RegisterFileSetRequest request) throws ConfigurationException, StorageHubException, StorageException, StepException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException { log.info("Registering Fileset for {} [useCaseDescriptor ID {}], Request is {} ",id, useCaseDescriptor.getId(),request); List files=request.getStreams(); Document attributes =request.getAttributes(); - Project doc=getByID(id); - doc.getLifecycleInformation().cleanState(); - doc.getLifecycleInformation().setLastOperationStatus(LifecycleInformation.Status.OK); + Project doc=lock(id,"Register Fileset"); + try { + doc.getLifecycleInformation().cleanState(); + doc.getLifecycleInformation().setLastOperationStatus(LifecycleInformation.Status.OK); - WorkspaceManager ws=new WorkspaceManager(); - StorageUtils storage=ImplementationProvider.get().getEngineByManagedClass(StorageUtils.class); + WorkspaceManager ws = new WorkspaceManager(); + StorageUtils storage = ImplementationProvider.get().getEngineByManagedClass(StorageUtils.class); - log.debug("Checking field {} definition in {}",request.getFieldDefinitionPath(), useCaseDescriptor.getId()); - Field fieldDefinition=getFieldDefinition(useCaseDescriptor,request.getFieldDefinitionPath()); + log.debug("Checking field {} definition in {}", request.getFieldDefinitionPath(), useCaseDescriptor.getId()); + Field fieldDefinition = getFieldDefinition(useCaseDescriptor, request.getFieldDefinitionPath()); - JSONPathWrapper docWrapper=new JSONPathWrapper(doc.getTheDocument().toJson()); + JSONPathWrapper docWrapper = new JSONPathWrapper(doc.getTheDocument().toJson()); - List matchingPaths = docWrapper.getMatchingPaths(request.getParentPath()); - if(matchingPaths.size()>1) throw new WebApplicationException("Multiple Destination matching parent path "+request.getParentPath(),Response.Status.BAD_REQUEST); - if(matchingPaths.isEmpty()) throw new WebApplicationException("PArent path not found at "+request.getParentPath(),Response.Status.BAD_REQUEST); + List matchingPaths = docWrapper.getMatchingPaths(request.getParentPath()); + if (matchingPaths.size() > 1) + throw new WebApplicationException("Multiple Destination matching parent path " + request.getParentPath(), Response.Status.BAD_REQUEST); + if (matchingPaths.isEmpty()) + throw new WebApplicationException("PArent path not found at " + request.getParentPath(), Response.Status.BAD_REQUEST); - String parentMatchingPath = matchingPaths.get(0); - List foundElementsByMatchingPaths = docWrapper.getByPath(parentMatchingPath); - if(foundElementsByMatchingPaths == null || foundElementsByMatchingPaths.isEmpty()) - throw new WebApplicationException("No element found at "+ parentMatchingPath,Response.Status.BAD_REQUEST); + String parentMatchingPath = matchingPaths.get(0); + List foundElementsByMatchingPaths = docWrapper.getByPath(parentMatchingPath); + if (foundElementsByMatchingPaths == null || foundElementsByMatchingPaths.isEmpty()) + throw new WebApplicationException("No element found at " + parentMatchingPath, Response.Status.BAD_REQUEST); - Document parent = Serialization.asDocument(foundElementsByMatchingPaths.get(0)); + Document parent = Serialization.asDocument(foundElementsByMatchingPaths.get(0)); - // PREPARE REGISTERED FS + // PREPARE REGISTERED FS // MANAGE CLASH - switch (request.getClashOption()){ + switch (request.getClashOption()) { case REPLACE_EXISTING: { - if(fieldDefinition.isCollection()) - throw new WebApplicationException("Cannot replace repeatable field "+request.getFieldDefinitionPath()+".",Response.Status.BAD_REQUEST); + if (fieldDefinition.isCollection()) + throw new WebApplicationException("Cannot replace repeatable field " + request.getFieldDefinitionPath() + ".", Response.Status.BAD_REQUEST); // DELETE EXISTING AND PUT - RegisteredFileSet toDelete = Serialization.convert(parent.get(request.getFieldName()),RegisteredFileSet.class); - if(!(toDelete == null)&&!(toDelete.isEmpty())) - deleteFileSetRoutine(toDelete,false,ws); + RegisteredFileSet toDelete = Serialization.convert(parent.get(request.getFieldName()), RegisteredFileSet.class); + if (!(toDelete == null) && !(toDelete.isEmpty())) + deleteFileSetRoutine(toDelete, false, ws); - RegisteredFileSet fs = prepareRegisteredFileSet(doc.getInfo(),doc.getId(), useCaseDescriptor.getId(), request.getAttributes(),files,storage,ws); - log.debug("Registered Fileset for [ID {} useCaseDescriptor {}] is {} ",fs,doc.getId(),doc.getProfileID()); - docWrapper.putElement(parentMatchingPath,request.getFieldName(),fs); - break;} + RegisteredFileSet fs = prepareRegisteredFileSet(doc.getInfo(), doc.getId(), useCaseDescriptor.getId(), request.getAttributes(), files, storage, ws); + log.debug("Registered Fileset for [ID {} useCaseDescriptor {}] is {} ", fs, doc.getId(), doc.getProfileID()); + docWrapper.putElement(parentMatchingPath, request.getFieldName(), fs); + break; + } case MERGE_EXISTING: { - if(fieldDefinition.isCollection()) - throw new WebApplicationException("Cannot merge repeatable field "+request.getFieldDefinitionPath()+".",Response.Status.BAD_REQUEST); - RegisteredFileSet original = Serialization.convert(parent.get(request.getFieldName()),RegisteredFileSet.class); + if (fieldDefinition.isCollection()) + throw new WebApplicationException("Cannot merge repeatable field " + request.getFieldDefinitionPath() + ".", Response.Status.BAD_REQUEST); + RegisteredFileSet original = Serialization.convert(parent.get(request.getFieldName()), RegisteredFileSet.class); // MERGE ATTRIBUTES AND PUT - Document toUseAttributes=request.getAttributes(); - if(original!=null) toUseAttributes.putAll(original); - RegisteredFileSet fs = prepareRegisteredFileSet(doc.getInfo(),doc.getId(), useCaseDescriptor.getId(), toUseAttributes,files,storage,ws); - log.debug("Registered Fileset for [ID {} useCaseDescriptor {}] is {} ",fs,doc.getId(),doc.getProfileID()); - docWrapper.putElement(parentMatchingPath,request.getFieldName(),fs); - break;} + Document toUseAttributes = request.getAttributes(); + if (original != null) toUseAttributes.putAll(original); + RegisteredFileSet fs = prepareRegisteredFileSet(doc.getInfo(), doc.getId(), useCaseDescriptor.getId(), toUseAttributes, files, storage, ws); + log.debug("Registered Fileset for [ID {} useCaseDescriptor {}] is {} ", fs, doc.getId(), doc.getProfileID()); + docWrapper.putElement(parentMatchingPath, request.getFieldName(), fs); + break; + } case APPEND: { - if(!fieldDefinition.isCollection()) - throw new WebApplicationException("Cannot add to single field "+request.getFieldDefinitionPath()+".",Response.Status.BAD_REQUEST); - RegisteredFileSet fs = prepareRegisteredFileSet(doc.getInfo(),doc.getId(), useCaseDescriptor.getId(), request.getAttributes(),files,storage,ws); - log.debug("Registered Fileset for [ID {} useCaseDescriptor {}] is {} ",fs,doc.getId(),doc.getProfileID()); + if (!fieldDefinition.isCollection()) + throw new WebApplicationException("Cannot add to single field " + request.getFieldDefinitionPath() + ".", Response.Status.BAD_REQUEST); + RegisteredFileSet fs = prepareRegisteredFileSet(doc.getInfo(), doc.getId(), useCaseDescriptor.getId(), request.getAttributes(), files, storage, ws); + log.debug("Registered Fileset for [ID {} useCaseDescriptor {}] is {} ", fs, doc.getId(), doc.getProfileID()); - docWrapper.addElementToArray(String.format("%1ds['%2$s']",parentMatchingPath,request.getFieldName()),fs); - break;} - default: {throw new WebApplicationException("Unexpected clash policy "+request.getClashOption(),Response.Status.BAD_REQUEST);} + docWrapper.addElementToArray(String.format("%1ds['%2$s']", parentMatchingPath, request.getFieldName()), fs); + break; + } + default: { + throw new WebApplicationException("Unexpected clash policy " + request.getClashOption(), Response.Status.BAD_REQUEST); + } } - log.debug("Setting result on profiled document"); - doc.setTheDocument(Document.parse(docWrapper.getValueCTX().jsonString())); + log.debug("Setting result on profiled document"); + doc.setTheDocument(Document.parse(docWrapper.getValueCTX().jsonString())); - doc=onUpdate(doc); - - return convert(replaceDoc(asDocumentWithId(doc),new ObjectId(id)), Project.class); + doc = onUpdate(doc); + }finally { + return unlockAndUpdate(doc); + } } + @Override - public Project deleteFileSet(String id, String path, Boolean force) throws ConfigurationException, StorageHubException, JsonProcessingException, DeletionException, EventException { + public Project deleteFileSet(String id, String path, Boolean force) throws ConfigurationException, StorageHubException, JsonProcessingException, DeletionException, EventException, ProjectLockedException, ProjectNotFoundException, InvalidLockException { log.info("Deleting Fileset for {} [useCaseDescriptor ID {}], at {} [force {} ]",id, useCaseDescriptor.getId(),path,force); - Project doc = getByID(id); - doc.getLifecycleInformation().cleanState(); - doc.getLifecycleInformation().cleanState().setLastOperationStatus(LifecycleInformation.Status.OK); + Project doc = lock(id,"Fileset Deletion"); + + try { + doc.getLifecycleInformation().cleanState(); + doc.getLifecycleInformation().cleanState().setLastOperationStatus(LifecycleInformation.Status.OK); - JSONPathWrapper wrapper = new JSONPathWrapper(doc.getTheDocument().toJson()); - List matchingPaths=wrapper.getMatchingPaths(path); - if(matchingPaths.isEmpty()) throw new WebApplicationException("No Registered FileSet found at "+path,Response.Status.BAD_REQUEST); - if(matchingPaths.size()>1) throw new WebApplicationException("Multiple Fileset ("+matchingPaths.size()+") matching "+path,Response.Status.BAD_REQUEST); - RegisteredFileSet fs = Serialization.convert(wrapper.getByPath(path),RegisteredFileSet.class); - log.debug("Going to delete {}",fs); - deleteFileSetRoutine(fs,force,new WorkspaceManager()); - log.debug("Removing FS from document [ID : ] by path {}",id,path); - wrapper.setElement(path,null); - doc=onUpdate(doc); - return convert(replaceDoc(asDocumentWithId(doc),new ObjectId(id)), Project.class); + JSONPathWrapper wrapper = new JSONPathWrapper(doc.getTheDocument().toJson()); + List matchingPaths = wrapper.getMatchingPaths(path); + if (matchingPaths.isEmpty()) + throw new WebApplicationException("No Registered FileSet found at " + path, Response.Status.BAD_REQUEST); + if (matchingPaths.size() > 1) + throw new WebApplicationException("Multiple Fileset (" + matchingPaths.size() + ") matching " + path, Response.Status.BAD_REQUEST); + RegisteredFileSet fs = Serialization.convert(wrapper.getByPath(path), RegisteredFileSet.class); + log.debug("Going to delete {}", fs); + deleteFileSetRoutine(fs, force, new WorkspaceManager()); + log.debug("Removing FS from document [ID : ] by path {}", id, path); + wrapper.setElement(path, null); + doc = onUpdate(doc); + }finally { + return unlockAndUpdate(doc); + } } @Override @@ -477,19 +566,19 @@ public class ProfiledMongoManager extends MongoManager implements MongoManagerI< } } - private Project triggerEvent(Project theDocument, String event, Document parameters) { + private Project triggerEvent(Project project, String event, Document parameters) { try{ log.info("[UseCaseDescriptor {}] triggering event {} on {}" , useCaseDescriptor.getId(),event,getManager().getDescriptor()); AccountingInfo user= UserUtils.getCurrent().asInfo(); - EventExecutionRequest request= new EventExecutionRequest(useCaseDescriptor,user.getUser(),user.getContext(),theDocument,event); + EventExecutionRequest request= new EventExecutionRequest(useCaseDescriptor,user.getUser(),user.getContext(),project,event); log.debug("Triggering {}",request); DocumentHandlingReport report = getManager().onEvent(request); return report.prepareResult(); } catch (Throwable t){ log.error("Unable to trigger event "+event,t); - theDocument.getLifecycleInformation().addErrorMessage("Unable to trigger "+event+" cause : "+ t.getMessage()); - theDocument.getLifecycleInformation().setLastOperationStatus(LifecycleInformation.Status.ERROR); - return theDocument; + project.getLifecycleInformation().addErrorMessage("Unable to trigger "+event+" cause : "+ t.getMessage()); + project.getLifecycleInformation().setLastOperationStatus(LifecycleInformation.Status.ERROR); + return project; } } diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidLockException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidLockException.java new file mode 100644 index 0000000..634a893 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidLockException.java @@ -0,0 +1,22 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class InvalidLockException extends Exception{ + public InvalidLockException() { + } + + public InvalidLockException(String message) { + super(message); + } + + public InvalidLockException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidLockException(Throwable cause) { + super(cause); + } + + public InvalidLockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ProjectLockedException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ProjectLockedException.java new file mode 100644 index 0000000..55f4e84 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ProjectLockedException.java @@ -0,0 +1,22 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class ProjectLockedException extends Exception{ + public ProjectLockedException() { + } + + public ProjectLockedException(String message) { + super(message); + } + + public ProjectLockedException(String message, Throwable cause) { + super(message, cause); + } + + public ProjectLockedException(Throwable cause) { + super(cause); + } + + public ProjectLockedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ProjectNotFoundException.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ProjectNotFoundException.java new file mode 100644 index 0000000..cea1e08 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/ProjectNotFoundException.java @@ -0,0 +1,22 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class ProjectNotFoundException extends Exception { + public ProjectNotFoundException() { + } + + public ProjectNotFoundException(String message) { + super(message); + } + + public ProjectNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public ProjectNotFoundException(Throwable cause) { + super(cause); + } + + public ProjectNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java index 423a90a..bf0b6ee 100644 --- a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java @@ -1,6 +1,9 @@ package org.gcube.application.geoportal.service.rest; import lombok.extern.slf4j.Slf4j; +import org.gcube.application.geoportal.service.model.internal.faults.InvalidLockException; +import org.gcube.application.geoportal.service.model.internal.faults.ProjectLockedException; +import org.gcube.application.geoportal.service.model.internal.faults.ProjectNotFoundException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response.Status; @@ -32,8 +35,15 @@ public abstract class GuardedMethod { result=run(); return this; }catch(WebApplicationException e) { - log.error("Throwing Web Application Exception ",e); + log.error("Throwing Web Application Exception ", e); throw e; + }catch(ProjectNotFoundException e){ + throw new WebApplicationException("Project not found", e,Status.NOT_FOUND); + }catch(ProjectLockedException e){ + throw new WebApplicationException("Project is currently locked", e,Status.PRECONDITION_FAILED); + }catch(InvalidLockException e){ + log.error("Lock exception ",e); + throw new WebApplicationException("Conflicts found in locks", e,Status.CONFLICT); }catch(Throwable t) { log.error("Unexpected error ",t); throw new WebApplicationException("Unexpected internal error", t,Status.INTERNAL_SERVER_ERROR);