diff --git a/src/main/java/org/gcube/data/access/storagehub/services/ACLManager.java b/src/main/java/org/gcube/data/access/storagehub/services/ACLManager.java index 481b97e..8b769f7 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/ACLManager.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/ACLManager.java @@ -35,14 +35,19 @@ import org.glassfish.jersey.media.multipart.FormDataParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.webcohesion.enunciate.metadata.DocumentationExample; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResourceMethodSignature; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.servlet.ServletContext; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.FormParam; import jakarta.ws.rs.GET; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; @@ -53,6 +58,9 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +/** + * Manage the Access Control List of shared folders + */ @Path("items") @ManagedBy(StorageHubApplicationManager.class) @RequestHeaders({ @@ -91,9 +99,13 @@ public class ACLManager extends Impersonable { /** * returns the AccessType for all the users in a shared folder * - * @exception {@link RepositoryException} when a generic jcr error occurs - * @exception {@link UserNotAuthorizedException} when the caller is not authorized to access to the shared folder + * @param id id of the shared folder */ + @ResourceMethodSignature(output = ACLList.class, pathParams = { @PathParam("id") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Shared folder found."), + @ResponseCode ( code = 500, condition = "This item does not exist."), + }) @GET @Path("{id}/acls") @Produces(MediaType.APPLICATION_JSON) @@ -121,16 +133,27 @@ public class ACLManager extends Impersonable { /** * Set a new AccessType for a user in a shared folder or VRE folder - * - * - * @param String user - * @param accessType accessType - * - * @exception {@link RepositoryException} when a generic jcr error occurs - * @exception {@link UserNotAuthorizedException} when the caller is not ADMINISTRATOR of the shared folder - * @exception {@link InvalidCallParameters} when the folder is not shared with the specified user - * @exception {@link InvalidItemException} when the folder is not share + * + * @param id id of the shared folder + * @param user user id + * @param access access type
+ * Possible values: READ_ONLY, WRITE_OWNER, WRITE_ALL, ADMINISTRATOR */ + @ResourceMethodSignature(output = void.class, pathParams = { @PathParam("id") }, formParams = { + @FormParam("user"), @FormParam("access") }) + @StatusCodes({ + @ResponseCode ( code = 204, condition = "Access type updated."), + @ResponseCode ( code = 400, condition = "User does not exist."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + @ResponseCode ( code = 500, condition = "This shared item does not exist or wrong access type."), + }) + @DocumentationExample(value = "...\n\n--------boundaryString\n" + + "Content-Disposition: form-data; name=\"user\"\n\n" + + "user2\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"accessType\"\n\n" + + "WRITE_OWNER\n" + + "--------boundaryString--") @PUT @Consumes(MediaType.MULTIPART_FORM_DATA) @Path("{id}/acls") @@ -186,18 +209,17 @@ public class ACLManager extends Impersonable { } /** - * remove right for a user only on Shared folder - * - * - * @param String user - * - * - * @exception {@link RepositoryException} when a generic jcr error occurs - * @exception {@link UserNotAuthorizedException} when the caller is not ADMINISTRATOR of the shared folder - * @exception {@link InvalidCallParameters} when the folder is not shared with the specified user - * @exception {@link InvalidItemException} when the folder is not share + * Remove a user from the shared folder + * + * @param id id of the shared folder + * @param user user id */ - //TODO: is this method correct? can ACL be removed, is correct that this means an unshare operation? + @ResourceMethodSignature(output = void.class, pathParams = { @PathParam("id"), @PathParam("user") }) + @StatusCodes({ + @ResponseCode ( code = 204, condition = "User removed."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + @ResponseCode ( code = 500, condition = "This shared item does not exist."), + }) @DELETE @Consumes(MediaType.TEXT_PLAIN) @Path("{id}/acls/{user}") @@ -233,7 +255,20 @@ public class ACLManager extends Impersonable { } } + /** + * Check if the current user can write on the shared folder + * + * @param id id of the shared folder + * @return true if the current user can write on the shared folder, false otherwise + * @responseExample text/plain true + */ + @ResourceMethodSignature(output = Boolean.class, pathParams = { @PathParam("id") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Shared folder found."), + @ResponseCode ( code = 406, condition = "This shared folder does not exist."), + }) @GET + @Produces(MediaType.TEXT_PLAIN) @Path("{id}/acls/write") public Boolean canWriteInto() { InnerMethodName.set("canWriteIntoFolder"); diff --git a/src/main/java/org/gcube/data/access/storagehub/services/DocsGenerator.java b/src/main/java/org/gcube/data/access/storagehub/services/DocsGenerator.java index 406e114..b7f84bd 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/DocsGenerator.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/DocsGenerator.java @@ -14,11 +14,14 @@ import jakarta.ws.rs.core.Response.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.webcohesion.enunciate.metadata.Ignore; + @Path("api-docs") public class DocsGenerator { private static Logger logger = LoggerFactory.getLogger(DocsGenerator.class); + @Ignore @GET @Path("/{any: .*}") public InputStream toDoc(@Context HttpServletRequest req) throws WebApplicationException { diff --git a/src/main/java/org/gcube/data/access/storagehub/services/GroupManager.java b/src/main/java/org/gcube/data/access/storagehub/services/GroupManager.java index f95d392..3833e40 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/GroupManager.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/GroupManager.java @@ -28,8 +28,12 @@ import org.glassfish.jersey.media.multipart.FormDataParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.webcohesion.enunciate.metadata.DocumentationExample; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResourceMethodSignature; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -46,6 +50,9 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +/** + * Manages groups + */ @Path("groups") @Singleton @ManagedBy(StorageHubApplicationManager.class) @@ -66,6 +73,15 @@ public class GroupManager { PathUtil pathUtil; + /** + * Get list of the groups for the current user + * + * @return list of groups + * @responseExample text/plain ["group1", "group2", "group3"] + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + }) @GET @Path("") @Produces(MediaType.APPLICATION_JSON) @@ -87,9 +103,43 @@ public class GroupManager { return groups; } + /** + * Create a new group + * + * @param group group name + * @param accessType access type
Possible values: READ_ONLY, WRITE_OWNER, WRITE_ALL, ADMINISTRATOR + * @param folderOwner folder owner + * @param useDefaultStorage use default storage if true
+ * Possible values: true, false
+ * Optional default: true + * @return group name + * @responseExample text/plain "group" + */ + @ResourceMethodSignature(output = String.class, formParams = { + @FormParam("group"), @FormParam("accessType"), @FormParam("folderOwner"), @FormParam("useDefaultStorage") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + @ResponseCode ( code = 403, condition = "You're not allowed to create groups."), + @ResponseCode ( code = 406, condition = "Error creating group."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + }) + @DocumentationExample(value = "...\n\n--------boundaryString\n" + + "Content-Disposition: form-data; name=\"group\"\n\n" + + "my_group\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"accessType\"\n\n" + + "ADMINISTRATOR\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"folderOwner\"\n\n" + + "user1\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"useDefaultStorage\"\n\n" + + "true\n" + + "--------boundaryString--") @POST @Path("") @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.TEXT_PLAIN) @AuthorizationControl(allowedRoles={INFRASTRUCTURE_MANAGER_ROLE}) public String createGroup(@FormDataParam("group") String group, @FormDataParam("accessType") AccessType accessType, @FormDataParam("folderOwner") String folderOwner, @FormDataParam("useDefaultStorage") @DefaultValue("true") boolean useDefaultStorage){ @@ -112,8 +162,21 @@ public class GroupManager { return group; } + /** + * Delete a group + * + * @param group group name + * @return group name + * @responseExample text/plain "group" + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + @ResponseCode ( code = 403, condition = "You're not allowed to delete groups."), + @ResponseCode ( code = 406, condition = "Error deleting group."), + }) @DELETE @Path("{group}") + @Produces(MediaType.TEXT_PLAIN) @AuthorizationControl(allowedRoles={INFRASTRUCTURE_MANAGER_ROLE}) public String deleteGroup(@PathParam("group") String group){ @@ -138,6 +201,18 @@ public class GroupManager { public boolean isVREManager() { return SecretManagerProvider.get().getOwner().getRoles().contains(VREMANAGER_ROLE); } + /** + * Add an administrator to a group + * + * @param id group id + * @param userId user id + */ + @StatusCodes({ + @ResponseCode ( code = 204, condition = "Success."), + @ResponseCode ( code = 406, condition = "Error adding an admin."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + }) + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id")}, formParams = { @FormParam("userId") }) @PUT @Path("{id}/admins") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @@ -175,6 +250,18 @@ public class GroupManager { } + /** + * Remove an administrator from a group + * + * @param id group id + * @param userId user id + */ + @StatusCodes({ + @ResponseCode ( code = 204, condition = "Success."), + @ResponseCode ( code = 406, condition = "Error removing an admin."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + }) + @ResourceMethodSignature(output = void.class, pathParams = { @PathParam("id")}, formParams = { @FormParam("userId") }) @DELETE @Path("{id}/admins/{userId}") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @@ -210,6 +297,16 @@ public class GroupManager { } } + /** + * Get the list of administrators of a group + * + * @param groupId group id + * @return list of administrators + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + @ResponseCode ( code = 406, condition = "This group does not exist."), + }) @GET @Path("{groupId}/admins") @Produces(MediaType.APPLICATION_JSON) @@ -236,6 +333,22 @@ public class GroupManager { } + /** + * Add a user to a group + * + * @param id group id + * @param userId user id + * @return true if the user has been added to the group + * @responseExample text/plain true + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + @ResponseCode ( code = 403, condition = "You're not allowed to add users to groups."), + @ResponseCode ( code = 406, condition = "Group or user does not exist."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + }) + @ResourceMethodSignature(output = boolean.class, pathParams = { @PathParam("id")}, formParams = { @FormParam("userId") }) + @DocumentationExample(value = "...\n\nuserId=user1\n") @PUT @Path("{id}/users") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @@ -271,6 +384,18 @@ public class GroupManager { + /** + * Remove a user from a group + * + * @param groupId group id + * @param userId user id + * @return true if the user has been removed from the group + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + @ResponseCode ( code = 403, condition = "You're not allowed to add users to groups."), + @ResponseCode ( code = 406, condition = "Group or user does not exist."), + }) @DELETE @Path("{groupId}/users/{userId}") @AuthorizationControl(allowedRoles={VREMANAGER_ROLE, INFRASTRUCTURE_MANAGER_ROLE}) @@ -304,6 +429,16 @@ public class GroupManager { return success; } + /** + * Get the list of users of a group + * + * @param groupId group id + * @return list of users + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + @ResponseCode ( code = 406, condition = "Group does not exist."), + }) @GET @Path("{groupId}/users") @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/org/gcube/data/access/storagehub/services/ItemSharing.java b/src/main/java/org/gcube/data/access/storagehub/services/ItemSharing.java index bcf5399..2e3348c 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/ItemSharing.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/ItemSharing.java @@ -42,13 +42,17 @@ import org.glassfish.jersey.media.multipart.FormDataParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.webcohesion.enunciate.metadata.DocumentationExample; +import com.webcohesion.enunciate.metadata.Ignore; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResourceMethodSignature; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.servlet.ServletContext; import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.FormParam; import jakarta.ws.rs.POST; import jakarta.ws.rs.PUT; @@ -59,7 +63,9 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; - +/** + * Manage item sharing. + */ @Path("items") @RequestHeaders({ @RequestHeader( name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources"), @@ -92,10 +98,12 @@ public class ItemSharing extends Impersonable{ @Inject Node2ItemConverter node2Item; @Inject Item2NodeConverter item2Node; - + // TODO: Remove this method - not used by anyone + @Ignore + @POST @SuppressWarnings("unchecked") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - @POST + @Produces(MediaType.TEXT_PLAIN) @Path("{id}/share") public String shareWithMap(@FormParam("mapUserPermission") String mapUserPermissionString, @FormParam("defaultAccessType") String defaultAccessTypeString){ InnerMethodName.set("shareFolder"); @@ -197,9 +205,32 @@ public class ItemSharing extends Impersonable{ + /** + * Share an item with some users and set its access type. + * + * @param id id of the item + * @param users set of users + * @param defaultAccessType default access type
+ * Possible values: READ_ONLY, WRITE_OWNER, WRITE_ALL, ADMINISTRATOR + * @return id of the shared item + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }, formParams = { + @FormParam("users"), @FormParam("defaultAccessType") }) + @DocumentationExample("...\n\n--------boundaryString\n" + + "Content-Disposition: form-data; name=\"users\"\n\n" + + "user1\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"users\"\n\n" + + "user2\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"defaultAccessType\"\n\n" + + "READ_ONLY\n" + + "------boundaryString--") @PUT @Path("{id}/share") @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.TEXT_PLAIN) public String share(@FormDataParam("users") Set users, @FormDataParam("defaultAccessType") AccessType accessType){ InnerMethodName.set("shareFolder"); Session ses = null; @@ -339,9 +370,24 @@ public class ItemSharing extends Impersonable{ } + /** + * Unshare an item with some users. + * + * @param id id of the item + * @param users set of users + * @return id of the unshared item + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }, formParams = { + @FormParam("users") }) + @DocumentationExample("...\n\n--------boundaryString\n" + + "Content-Disposition: form-data; name=\"users\"\n\n" + + "user1,user2,user3\n" + + "--------boundaryString--") @PUT @Path("{id}/unshare") @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.TEXT_PLAIN) public String unshare(@FormDataParam("users") Set users){ InnerMethodName.set("unshareFolder"); Session ses = null; diff --git a/src/main/java/org/gcube/data/access/storagehub/services/ItemsCreator.java b/src/main/java/org/gcube/data/access/storagehub/services/ItemsCreator.java index f86fe8e..6fb16be 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/ItemsCreator.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/ItemsCreator.java @@ -60,6 +60,9 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +/** + * Manage item creation. + */ @Path("items") @ManagedBy(StorageHubApplicationManager.class) @RequestHeaders({ @@ -83,10 +86,14 @@ public class ItemsCreator extends Impersonable { * @param id destination parent folder id * @param name destination folder name * @param description description meta-info for the created folder - * @param hidden Optional: default false hidden folder if true + * @param hidden hidden folder if true
+ * Possible values: true, false
+ * Optional default: false * @return id of the created folder * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }, formParams = { + @FormParam("name"), @FormParam("description"), @FormParam("hidden") }) @DocumentationExample(" ...\n\nname=sampleFolder&description=This+is+a+sample+folder&hidden=false") @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) diff --git a/src/main/java/org/gcube/data/access/storagehub/services/ItemsManager.java b/src/main/java/org/gcube/data/access/storagehub/services/ItemsManager.java index 6c52548..b5c26fa 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/ItemsManager.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/ItemsManager.java @@ -63,8 +63,13 @@ import org.gcube.smartgears.utils.InnerMethodName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.webcohesion.enunciate.metadata.DocumentationExample; +import com.webcohesion.enunciate.metadata.Ignore; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResourceMethodSignature; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import jakarta.inject.Inject; import jakarta.servlet.ServletContext; @@ -82,7 +87,9 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; - +/** + * Manage item retrieval, deletion, and update. + */ @Path("items") @ManagedBy(StorageHubApplicationManager.class) @RequestHeaders({ @@ -130,7 +137,22 @@ public class ItemsManager extends Impersonable{ PublicLinkHandler publicLinkHandler; + /** + * Retrieve an item by its id. + * + * @param id item id + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return a JSON representation of the item + * @pathExample /{id}?exclude=content + */ + @ResourceMethodSignature(output = ItemWrapper.class, pathParams = @PathParam("id"), queryParams = @QueryParam("exclude")) @GET + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Unable to find this item."), + }) @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public ItemWrapper getById(@QueryParam("exclude") List excludes){ @@ -159,6 +181,18 @@ public class ItemsManager extends Impersonable{ return new ItemWrapper(toReturn); } + /** + * Retrieve an item by its relative path. + * + * @param id item id + * @param path the relative path of the item to retrieve + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return a JSON representation of the item + * @pathExample /{id}/path?path=folder1&exclude=content + */ + @ResourceMethodSignature(output = ItemWrapper.class, pathParams = @PathParam("id"), queryParams = {@QueryParam("path"), @QueryParam("exclude")}) @GET @Path("{id}/path") @Produces(MediaType.APPLICATION_JSON) @@ -220,6 +254,7 @@ public class ItemsManager extends Impersonable{ return null; } + @Ignore @Deprecated @GET @Path("{id}/items/{name}") @@ -229,6 +264,22 @@ public class ItemsManager extends Impersonable{ return _findChildrenByNamePattern(excludes, name); } + /** + * Retrieve a child item by its name. + * + * @param id parent item id + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @param name the name of the child item to retrieve + * @return a JSON representation of the item list + * @pathExample /{id}/items?name=doc.pdf&exclude=content + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }, queryParams = { @QueryParam("exclude"), @QueryParam("name") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Parent item does not exist."), + }) @GET @Path("{id}/items") @Produces(MediaType.APPLICATION_JSON) @@ -278,9 +329,26 @@ public class ItemsManager extends Impersonable{ } + /** + * Retrieve a child item by its name. + * + * @param id parent item id + * @param showHidden if true, hidden items are shown
+ * Possible values: true, false
+ * Optional default: false + * @param onlyType only items of the specified type are counted
+ * Optional + * @return the number of children + * @pathExample /{id}/children/count?showHidden=true&onlyType=File + */ + @ResourceMethodSignature(output = Long.class, pathParams = @PathParam("id"), queryParams = {@QueryParam("showHidden"), @QueryParam("onlyType")}) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Parent item does not exist"), + }) @GET @Path("{id}/children/count") - public Long countById(@QueryParam("showHidden") Boolean showHidden, @QueryParam("exclude") List excludes, @QueryParam("onlyType") String nodeType){ + public Long countById(@QueryParam("showHidden") Boolean showHidden, @QueryParam("onlyType") String nodeType){ InnerMethodName.set("countById"); Session ses = null; Long toReturn = null; @@ -307,6 +375,26 @@ public class ItemsManager extends Impersonable{ return toReturn ; } + /** + * Retrieve a list of children items. + * + * @param id parent item id + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @param showHidden if true, hidden items are shown
+ * Possible values: true, false
+ * Optional default: false + * @param onlyType only items of the specified type are counted
+ * Optional + * @return a JSON representation of the item list + * @pathExample /{id}/children?showHidden=true&onlyType=File&exclude=content + */ + @ResourceMethodSignature(output = ItemList.class, pathParams = @PathParam("id"), queryParams = {@QueryParam("showHidden"), @QueryParam("exclude"), @QueryParam("onlyType")}) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Parent item does not exist."), + }) @GET @Path("{id}/children") @Produces(MediaType.APPLICATION_JSON) @@ -337,6 +425,9 @@ public class ItemsManager extends Impersonable{ return new ItemList(toReturn); } + + // tipo di nodo ROOT - per ora non funziona + @Ignore @GET @Path("{id}/search") @Produces(MediaType.APPLICATION_JSON) @@ -369,6 +460,32 @@ public class ItemsManager extends Impersonable{ return new ItemList(toReturn); } + /** + * Retrieve a paged list of children items. + * + * @param id parent item id + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @param showHidden if true, hidden items are shown
+ * Possible values: true, false
+ * Optional default: false + * @param onlyType only items of the specified type are counted
+ * Optional + * @param start start index, counting from 0
+ * Possible values: integers + * @param limit maximum number of items returned
+ * Possible values: integers + * @return a JSON representation of the item list + * @pathExample /{id}/children/paged?start=1&limit=100&showHidden=true&onlyType=File&exclude=content + */ + @ResourceMethodSignature(output = ItemList.class, pathParams = @PathParam("id"), + queryParams = {@QueryParam("showHidden"), @QueryParam("exclude"), @QueryParam("onlyType"), @QueryParam("start"), @QueryParam("limit")}) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Parent item does not exist."), + @ResponseCode ( code = 500, condition = "Missing 'start' or 'limit' parameter."), + }) @GET @Path("{id}/children/paged") @Produces(MediaType.APPLICATION_JSON) @@ -399,6 +516,7 @@ public class ItemsManager extends Impersonable{ return new ItemList(toReturn); } + @Ignore @GET @Path("publiclink/{id}") @AuthorizationControl(allowedUsers={"URIResolver"}) @@ -434,6 +552,21 @@ public class ItemsManager extends Impersonable{ return Response.serverError().build(); } + /** + * Retrieves the public link for a specified item. + * + * @param id item id + * @param version version of the item for which the public link is requested
+ * Optional default: last version + * @return URL of the public link for the specified item + * @pathExample /{id}/publiclink?version=1.0 + */ + @ResourceMethodSignature(output = URL.class, pathParams = @PathParam("id"), queryParams = @QueryParam("version")) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 400, condition = "This item is not a file."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) @GET @Produces(MediaType.APPLICATION_JSON) @Path("{id}/publiclink") @@ -497,6 +630,21 @@ public class ItemsManager extends Impersonable{ + /** + * Set the access level of the specified folder. + * + * @param id item id + * @param publish if true, the folder is made public
+ * Possible values: true, false + * @return the id of the folder + */ + @ResourceMethodSignature(output = String.class, pathParams = @PathParam("id"), formParams = @FormParam("publish")) + @DocumentationExample("...\n\npublish=true") + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 400, condition = "This item is not a file."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) @PUT @Path("{id}/publish") @Produces(MediaType.APPLICATION_JSON) @@ -533,6 +681,21 @@ public class ItemsManager extends Impersonable{ } + /** + * Retrieves the root shared folder for the specified item. + * + * @param id item id + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return the id of the root folder + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 400, condition = "This item is not a shared."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) + @ResourceMethodSignature(output = ItemWrapper.class, pathParams = @PathParam("id"), queryParams = @QueryParam("exclude")) @GET @Path("{id}/rootSharedFolder") @Produces(MediaType.APPLICATION_JSON) @@ -576,6 +739,17 @@ public class ItemsManager extends Impersonable{ return currentNode; } + /** + * Retrieves the list of versions for the specified item. + * + * @param id item id + * @return the version list + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) + @ResourceMethodSignature(output = VersionList.class, pathParams = @PathParam("id")) @GET @Path("{id}/versions") @Produces(MediaType.APPLICATION_JSON) @@ -613,6 +787,20 @@ public class ItemsManager extends Impersonable{ return new VersionList(versions); } + /** + * Download a specific version of a given item. + * + * @param id item id + * @param version the requested version + * @return download the item + * @pathExample /{id}/versions/1.0/download + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + @ResponseCode ( code = 500, condition = "Invalid version."), + }) @GET @Path("{id}/versions/{version}/download") public Response downloadVersion(@PathParam("version") String versionName){ @@ -641,6 +829,21 @@ public class ItemsManager extends Impersonable{ return Response.serverError().build(); } + /** + * Delete a specific version of a given item. + * + * @param id item id + * @param version the requested version + * @return download the item + * @pathExample /{id}/versions/1.0 + */ + @ResourceMethodSignature(output = void.class, pathParams = { @PathParam("id"), @PathParam("version") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item deleted."), + @ResponseCode ( code = 400, condition = "This item's version cannot be removed."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + @ResponseCode ( code = 500, condition = "Invalid version."), + }) @DELETE @Path("{id}/versions/{version}") public void deleteVersion(@PathParam("version") String versionName){ @@ -684,6 +887,21 @@ public class ItemsManager extends Impersonable{ } + /** + * Retrieve the anchestors of a given item. + * + * @param id item id + * @param exclude a list of fields to exclude from the returned item list
+ * Multivalued
+ * Optional + * @return the list of anchestors + * @pathExample /{id}/anchestors?exclude=content + */ + @ResourceMethodSignature(output = ItemList.class, pathParams = { @PathParam("id")}, queryParams = { @QueryParam("exclude")}) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) @GET @Path("{id}/anchestors") @Produces(MediaType.APPLICATION_JSON) @@ -741,9 +959,21 @@ public class ItemsManager extends Impersonable{ + /** + * Download a given item. + * + * @param id item id + * @return download the item + * @pathExample /{id}/download + */ + @ResourceMethodSignature(output = Response.class, pathParams = { @PathParam("id")}) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) @GET @Path("{id}/download") - public Response download(@QueryParam("exclude") List excludes){ + public Response download(){ InnerMethodName.set("downloadById"); Session ses = null; try{ @@ -770,6 +1000,22 @@ public class ItemsManager extends Impersonable{ } + /** + * Move a given item. + * + * @param id item id + * @param destinationId destination folder id + * @return id of the moved item + * @pathExample /{id}/move + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }, formParams = { @FormParam("destinationId")}) + @DocumentationExample("...\n\ndestinationId=19863b4e-b33f- ... -5b6d2e0e1eee") + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item moved."), + @ResponseCode ( code = 406, condition = "Source or destination item does not exist."), + @ResponseCode ( code = 409, condition = "Source and destination are the same item."), + }) @PUT @Path("{id}/move") public String move(@FormParam("destinationId") String destinationId){ @@ -848,6 +1094,23 @@ public class ItemsManager extends Impersonable{ return id; } + /** + * Copy a given item. + * + * @param id item id + * @param destinationId destination folder id + * @param fileName name of the copied item + * @return id of the copied item + * @pathExample /{id}/copy + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, pathParams = @PathParam("id"), + formParams = { @FormParam("destinationId"), @FormParam("fileName")}) + @DocumentationExample("...\n\ndestinationId=19863b4e-b33f- ... -5b6d2e0e1eee&fileName=myCopy.jpg") + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item copied."), + @ResponseCode ( code = 406, condition = "Source or destination item does not exist."), + }) @PUT @Path("{id}/copy") public String copy(@FormParam("destinationId") String destinationId, @FormParam("fileName") String newFileName){ @@ -923,6 +1186,21 @@ public class ItemsManager extends Impersonable{ return newFileIdentifier; } + /** + * Rename a given item. + * + * @param id item id + * @param newName new name for the item + * @return id of the renamed item + * @pathExample /{id}/rename + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, pathParams = @PathParam("id"), formParams = @FormParam("newName")) + @DocumentationExample("...\n\nnewName=newFileName.txt") + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item renamed."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) @PUT @Path("{id}/rename") public Response rename(@FormParam("newName") String newName){ @@ -979,9 +1257,25 @@ public class ItemsManager extends Impersonable{ return Response.ok(id).build(); } - //TODO: transform this and setMetadata in a generic method for all properties + /** + * Set the item as hidden or visible. The body request is interpreted as the boolean + * value for the visibility. + * + * @param id item id + * @return id of the item + * @pathExample /{id}/hidden + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }) + @DocumentationExample("...\n\ntrue") + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item visibility set."), + @ResponseCode ( code = 400, condition = "Unable to parse request body."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) @PUT @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) @Path("/{id}/hidden") public Response setItemAsHidden(Boolean hidden){ InnerMethodName.set("setHidden"); @@ -1024,9 +1318,24 @@ public class ItemsManager extends Impersonable{ return Response.ok(id).build(); } - //TODO: transform this and setMetadata in a generic method for all properties + /** + * Set description for the specified item. The body request is interpreted as the description string. + * + * @param id item id + * @return id of the item + * @pathExample /{id}/description + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }) + @DocumentationExample("...\n\nThis is a sample description") + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item description set."), + @ResponseCode ( code = 400, condition = "Unable to parse request body."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) @PUT @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) @Path("/{id}/description") public Response setDescription(String description){ InnerMethodName.set("setDescription"); @@ -1069,8 +1378,29 @@ public class ItemsManager extends Impersonable{ return Response.ok(id).build(); } + /** + * Set metadata for the specified item. The body request is interpreted as the metadata JSON. + * + * @param id item id + * @return id of the item + * @pathExample /{id}/metadata + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }) + @DocumentationExample("...\n\n{\n" + + " \"map\": {\n" + + " \"meta1\": \"value1\",\n" + + " \"meta2\": \"value2\"\n" + + " }\n" + + "}") + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item metadata set."), + @ResponseCode ( code = 400, condition = "Unable to parse request body."), + @ResponseCode ( code = 406, condition = "Item does not exist."), + }) @PUT @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) @Path("/{id}/metadata") public Response setMetadata(org.gcube.common.storagehub.model.Metadata metadata){ InnerMethodName.set("updateMetadata"); @@ -1115,7 +1445,22 @@ public class ItemsManager extends Impersonable{ + /** + * Move the specified item to the trash or delete it permanently if it is already trashed. + * + * @param id item id + * @param force permanently delete
+ * Possible values: true, false
+ * Optional default: false + * @pathExample /{id}?force=true + */ + @ResourceMethodSignature (output = String.class, pathParams = { @PathParam("id") }, queryParams = { @QueryParam("force") }) @DELETE + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item deleted."), + @ResponseCode ( code = 406, condition = "Unable to find this item."), + @ResponseCode ( code = 500, condition = "Unable to delete this item."), + }) @Path("{id}") public Response deleteItem(@QueryParam("force") boolean force){ InnerMethodName.set("deleteItem("+force+")"); @@ -1163,6 +1508,18 @@ public class ItemsManager extends Impersonable{ return Response.ok().build(); } + /** + * Get info of the specified folder. + * + * @param id item id + * @return folder info + */ + @ResourceMethodSignature(output = String.class, pathParams = { @PathParam("id") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item found."), + @ResponseCode ( code = 400, condition = "This item is not a folder."), + @ResponseCode ( code = 406, condition = "Unable to find this folder."), + }) @Path("{id}/info") @GET @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/org/gcube/data/access/storagehub/services/MessageManager.java b/src/main/java/org/gcube/data/access/storagehub/services/MessageManager.java index 5022ced..c067711 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/MessageManager.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/MessageManager.java @@ -52,6 +52,7 @@ import org.gcube.smartgears.utils.InnerMethodName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.webcohesion.enunciate.metadata.Ignore; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; @@ -71,6 +72,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; +@Ignore @Path("messages") @ManagedBy(StorageHubApplicationManager.class) @RequestHeaders({ diff --git a/src/main/java/org/gcube/data/access/storagehub/services/StorageManager.java b/src/main/java/org/gcube/data/access/storagehub/services/StorageManager.java index 58e0dcf..71c0c65 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/StorageManager.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/StorageManager.java @@ -11,6 +11,8 @@ import org.gcube.smartgears.utils.InnerMethodName; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import jakarta.inject.Inject; import jakarta.ws.rs.GET; @@ -18,6 +20,9 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +/** + * Manage underlying storage backends. + */ @Path("storages") @ManagedBy(StorageHubApplicationManager.class) @RequestHeaders({ @@ -28,7 +33,15 @@ public class StorageManager { @Inject StorageBackendHandler storageBackendHandler; + /** + * Get a list of available storages + * + * @return list of available storages + */ @GET + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + }) @Path("/") @Produces(MediaType.APPLICATION_JSON) public List getStorages(){ diff --git a/src/main/java/org/gcube/data/access/storagehub/services/UserManager.java b/src/main/java/org/gcube/data/access/storagehub/services/UserManager.java index e5760e6..0fa9b20 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/UserManager.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/UserManager.java @@ -22,8 +22,11 @@ import org.gcube.smartgears.utils.InnerMethodName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.webcohesion.enunciate.metadata.DocumentationExample; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import jakarta.inject.Inject; import jakarta.ws.rs.Consumes; @@ -38,6 +41,9 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +/** + * Manage users + */ @Path("users") @ManagedBy(StorageHubApplicationManager.class) @RequestHeaders({ @@ -53,7 +59,15 @@ public class UserManager { @Inject UserManagerDelegate userHandler; + /** + * Get a list of users + * + * @return list of users + */ @GET + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + }) @Path("") @Produces(MediaType.APPLICATION_JSON) public UsersList getUsers() { @@ -72,7 +86,17 @@ public class UserManager { return null; } + /** + * Get user details + * + * @param user user name + * @return user detail + */ @GET + @StatusCodes({ + @ResponseCode ( code = 200, condition = "User found."), + @ResponseCode ( code = 406, condition = "User does not exist."), + }) @Path("{user}") @Produces(MediaType.APPLICATION_JSON) public SHUBUser getUser(@PathParam("user") String user) { @@ -100,9 +124,24 @@ public class UserManager { return null; } + /** + * Create a new user + * + * @param user user name + * @param password user password + * @return user id + */ + @DocumentationExample(" ...\n\nuser=nome.utente&password=passw0rd") @POST + @StatusCodes({ + @ResponseCode ( code = 200, condition = "User created."), + @ResponseCode ( code = 400, condition = "Wrong set of parameters."), + @ResponseCode ( code = 403, condition = "You're not allowed to create users."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + }) @Path("") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.TEXT_PLAIN) @AuthorizationControl(allowedRoles = { INFRASTRUCTURE_MANAGER_ROLE }) public String createUser(@FormParam("user") String user, @FormParam("password") String password) { @@ -130,9 +169,20 @@ public class UserManager { return userId; } + /** + * Update user to the last 'home' version + * + * @param user user name + * @return user id + */ @PUT @Path("{user}") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Home update done."), + @ResponseCode ( code = 403, condition = "You're not allowed to create users."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + }) @AuthorizationControl(allowedRoles = { INFRASTRUCTURE_MANAGER_ROLE }) public String updateHomeUserToLatestVersion(@PathParam("user") String user) { @@ -160,8 +210,20 @@ public class UserManager { return userId; } + /** + * Delete a user + * + * @param user user name + * @return user name + */ @DELETE + @StatusCodes({ + @ResponseCode ( code = 200, condition = "User deleted."), + @ResponseCode ( code = 403, condition = "You're not allowed to delete users."), + @ResponseCode ( code = 406, condition = "User does not exist."), + }) @Path("{user}") + @Produces(MediaType.TEXT_PLAIN) @AuthorizationControl(allowedRoles = { INFRASTRUCTURE_MANAGER_ROLE }) public String deleteUser(@PathParam("user") final String user) { @@ -189,7 +251,17 @@ public class UserManager { return user; } + /** + * Get a list of groups for the specified user + * + * @param user user name + * @return List of groups + */ @GET + @StatusCodes({ + @ResponseCode ( code = 200, condition = "User found."), + @ResponseCode ( code = 500, condition = "User does not exist."), + }) @Path("{user}/groups") @Produces(MediaType.APPLICATION_JSON) public List getGroupsPerUser(@PathParam("user") final String user) { diff --git a/src/main/java/org/gcube/data/access/storagehub/services/WorkspaceManager.java b/src/main/java/org/gcube/data/access/storagehub/services/WorkspaceManager.java index dca2e55..2556819 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/WorkspaceManager.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/WorkspaceManager.java @@ -56,8 +56,13 @@ import org.glassfish.jersey.media.multipart.FormDataParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.webcohesion.enunciate.metadata.DocumentationExample; +import com.webcohesion.enunciate.metadata.Ignore; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResourceMethodSignature; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; @@ -75,6 +80,9 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +/** + * Manage the workspace + */ @Path("/") @ManagedBy(StorageHubApplicationManager.class) @RequestHeaders({ @@ -121,6 +129,21 @@ public class WorkspaceManager extends Impersonable { @Inject Item2NodeConverter item2Node; + /** + * Get info of an item referred through its path + * + * @param relPath relative path + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return workspace item + * @pathExample /?relPath=folder1/folder2/file.txt&exclude=owner + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item retrieved."), + @ResponseCode ( code = 406, condition = "Item not found."), + }) + @ResourceMethodSignature(output = ItemWrapper.class, queryParams = { @QueryParam("relPath"), @QueryParam("exclude") }) @Path("/") @GET @Produces(MediaType.APPLICATION_JSON) @@ -164,19 +187,33 @@ public class WorkspaceManager extends Impersonable { } /** - * Uploads a file in the volatile area returning a public link + * Upload a file in the volatile area * - * @param id - * @param name - * @param description - * @param stream - * @param fileDetail - * @return + * @param file multipart/form-data file parameter, with optional + * 'filename' and 'size' (see example below) + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return public link to the created file + * @responseExample https://data.dev.d4science.org/shub/VLT_V1...9PQ== */ + @ResourceMethodSignature(output = String.class, formParams = { @FormParam("file") }, queryParams = { @QueryParam("exclude") }) + @DocumentationExample(value = "...\n\n--------boundaryString\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"doc.pdf\"; size=426018;\n" + + "Content-Type: application/pdf\n\n" + + "(data)\n" + + "--------boundaryString--") @POST @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.TEXT_PLAIN) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "File created."), + @ResponseCode ( code = 400, condition = "File not provided."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + @ResponseCode ( code = 500, condition = "Wrong set of parameters."), + }) @Path("volatile") - public String uploadVolatileFile(@FormDataParam("file") InputStream stream, + public String uploadVolatileFile(@FormDataParam("file") InputStream file, @FormDataParam("file") FormDataContentDisposition fileDetail) { InnerMethodName.set("uploadToVolatileArea"); @@ -193,7 +230,7 @@ public class WorkspaceManager extends Impersonable { StorageBackend sb = sbf.create(payloadBackend); log.info("UPLOAD: call started with file size {}", size); - MetaInfo info = sb.upload(stream, null, fileDetail.getFileName(), currentUser); + MetaInfo info = sb.upload(file, null, fileDetail.getFileName(), currentUser); log.debug("UPLOAD: call finished"); toReturn = publicLinkHandler.getForVolatile(info.getStorageId(), GCubeVolatileStorageBackendFactory.NAME, @@ -208,11 +245,23 @@ public class WorkspaceManager extends Impersonable { return toReturn; } + /** + * Get info on the VRE folder associated to the current token + * + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return VRE folder info + */ + @ResourceMethodSignature(output = ItemWrapper.class, queryParams = { @QueryParam("exclude") }) @Path("vrefolder") @GET + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + }) @Produces(MediaType.APPLICATION_JSON) - public ItemWrapper getVreRootFolder() { - InnerMethodName.set("getVreRootFolder"); + public ItemWrapper getCurrentVreFolder() { + InnerMethodName.set("getCurrentVreFolder"); JackrabbitSession ses = null; Item vreItem = null; try { @@ -239,6 +288,18 @@ public class WorkspaceManager extends Impersonable { return new ItemWrapper(vreItem); } + /** + * Get a list of recent documents from VRE folders + * + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return list of recent documents + */ + @ResourceMethodSignature(output = ItemList.class, queryParams = { @QueryParam("exclude") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + }) @Path("vrefolder/recents") @GET @Produces(MediaType.APPLICATION_JSON) @@ -277,6 +338,18 @@ public class WorkspaceManager extends Impersonable { } + /** + * Get info on the trash folder + * + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return info on the trash folder + */ + @ResourceMethodSignature(output = ItemWrapper.class, queryParams = { @QueryParam("exclude") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Success."), + }) @Path("trash") @GET @Produces(MediaType.APPLICATION_JSON) @@ -307,7 +380,21 @@ public class WorkspaceManager extends Impersonable { return new ItemWrapper(item); } + /** + * Empty the trash folder + * + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return trash folder id + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, queryParams = { @QueryParam("exclude") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Trash emptied."), + }) @Path("trash/empty") + @Produces(MediaType.TEXT_PLAIN) @DELETE public String emptyTrash() { InnerMethodName.set("emptyTrash"); @@ -334,6 +421,25 @@ public class WorkspaceManager extends Impersonable { return toReturn; } + /** + * Restore a trashed item. + * + * @param trashedItemId item id + * @param destinationId destination folder id + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return id of the restored item + * @responseExample text/plain 5f4b3b4e-4b3b- ... -4b3b4e4b3b4e + */ + @ResourceMethodSignature(output = String.class, queryParams = { @QueryParam("exclude")}, formParams = { @FormParam("trashedItemId"), @FormParam("destinationId") }) + @DocumentationExample("...\n\ntrashedId=17dae181-f33c- ... - 3fa22198dd30&destinationId=19863b4e-b33f- ... -5b6d2e0e1eee") + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Item restored."), + @ResponseCode ( code = 406, condition = "Source or destination item does not exist."), + @ResponseCode ( code = 409, condition = "Source and destination are the same item."), + @ResponseCode ( code = 415, condition = "Wrong content type."), + }) @PUT @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Path("trash/restore") @@ -383,6 +489,15 @@ public class WorkspaceManager extends Impersonable { return toReturn; } + /** + * Get a list of VRE folders for which the user is member + * + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return list of VRE folders + */ + @ResourceMethodSignature(output = ItemList.class, queryParams = { @QueryParam("exclude") }) @Path("vrefolders") @GET @Produces(MediaType.APPLICATION_JSON) @@ -411,6 +526,20 @@ public class WorkspaceManager extends Impersonable { return new ItemList(toReturn); } + /** + * Get a paged list of VRE folders for which the user is member + * + * @param start start index, counting from 0
+ * Possible values: integers + * @param limit maximum number of items returned
+ * Possible values: integers + * @param exclude a list of fields to exclude from the returned item
+ * Multivalued
+ * Optional + * @return list of VRE folders + * @pathExample /vrefolders/paged?start=0&limit=10 + */ + @ResourceMethodSignature(output = ItemList.class, queryParams = { @QueryParam("start"), @QueryParam("limit"), @QueryParam("exclude") }) @Path("vrefolders/paged") @GET @Produces(MediaType.APPLICATION_JSON) @@ -437,30 +566,13 @@ public class WorkspaceManager extends Impersonable { return new ItemList(toReturn); } - /* - * @Path("shared-by-me") - * - * @GET - * - * @Produces(MediaType.APPLICATION_JSON) public ItemList getMySharedFolders(){ - * InnerMethodName.set("getMySharedFolders"); Session ses = null; List toReturn = null; org.gcube.common.storagehub.model.Path sharedPath = - * null; try{ ses = repository.getRepository().login(Constants.JCR_CREDENTIALS); - * sharedPath = pathUtil.getMySharedPath(currentUser); - * log.info("my shared folder path is folder path is {}",sharedPath.toPath()); - * - * toReturn = Utils.getItemList(ses.getNode(sharedPath.toPath()) , excludes, - * null, false, SharedFolder.class); }catch(RepositoryException re ){ - * log.error("error reading my shared folder ({})",sharedPath, re); - * GXOutboundErrorResponse.throwException(new BackendGenericError(re)); - * }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); - * GXOutboundErrorResponse.throwException(she, - * Response.Status.fromStatusCode(she.getStatus())); }finally{ if (ses!=null) - * ses.logout(); } - * - * return new ItemList(toReturn); } - */ + // TODO: boh! Sviluppo a metà. Valutare se finirlo o ammazzarlo male + @Ignore + @StatusCodes({ + @ResponseCode ( code = 200, condition = "List retrieved."), + @ResponseCode ( code = 406, condition = "Error retrieving shared-with-me folder."), + }) @Path("shared-with-me") @GET @Produces(MediaType.APPLICATION_JSON) @@ -489,6 +601,7 @@ public class WorkspaceManager extends Impersonable { return new ItemList(toReturn); } + @Ignore @Path("count") @GET @Deprecated @@ -513,6 +626,7 @@ public class WorkspaceManager extends Impersonable { return "0"; } + @Ignore @Path("size") @GET @Deprecated diff --git a/src/main/java/org/gcube/data/access/storagehub/services/admin/ScriptManager.java b/src/main/java/org/gcube/data/access/storagehub/services/admin/ScriptManager.java index bc3483e..db8ab0d 100644 --- a/src/main/java/org/gcube/data/access/storagehub/services/admin/ScriptManager.java +++ b/src/main/java/org/gcube/data/access/storagehub/services/admin/ScriptManager.java @@ -41,12 +41,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; +import com.webcohesion.enunciate.metadata.DocumentationExample; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; +import com.webcohesion.enunciate.metadata.rs.ResourceMethodSignature; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import jakarta.inject.Inject; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.FormParam; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -55,6 +60,9 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; +/** + * Manage "script" classes + */ @Path("admin/script") @RequestHeaders({ @RequestHeader( name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources"), @@ -84,21 +92,61 @@ public class ScriptManager { protected static HashMap scriptStatusMap = new HashMap(); + /** + * Execute a "script" class + * + * @param name name of the script + * @param asynch if true, execute script asynchronously
+ * Possible values: true, false
+ * Optional default: false + * @param writeResult if true, write the result in the workspace
+ * Possible values: true, false
+ * Optional default: false + * @param destinationFolderId id of the destination folder + * @param file multipart/form-data file parameter, with optional + * 'filename' and 'size' (see example below) + * @return outcome of the script execution + */ + @ResourceMethodSignature(output = ScriptStatus.class, formParams = { @FormParam("name"), + @FormParam("asynch"), @FormParam("writeResult"), @FormParam("destinationFolderId"), + @FormParam("file") }) + @DocumentationExample(value = "...\n\n--------boundaryString\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"MoveFiles.class\"; size=171018;\n" + + "Content-Type: application/octet-stream\n\n" + + "(binary data)\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"name\"\n\n" + + "MoveFile\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"async\"\n\n" + + "false\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"writeResult\"\n\n" + + "true\n" + + "--------boundaryString\n" + + "Content-Disposition: form-data; name=\"destinationFolderId=\"\n\n" + + "5f4b3b4e-4b3b- ... -4b3b4e4b3b4e\n" + + "--------boundaryString--") @POST @Path("execute") @AuthorizationControl(allowedRoles = {INFRASTRUCTURE_MANAGER_ROLE}) @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Script correctly loaded."), + @ResponseCode ( code = 403, condition = "You're not allowed to run scripts."), + @ResponseCode ( code = 500, condition = "Error loading the script."), + }) public ScriptStatus run( @FormDataParam("name") String name, @FormDataParam("asynch") @DefaultValue("false") Boolean asynch, @FormDataParam("writeResult") @DefaultValue("false") Boolean writeResult , @FormDataParam("destinationFolderId") String destinationFolderId, - @FormDataParam("file") InputStream stream, + @FormDataParam("file") InputStream file, @FormDataParam("file") FormDataContentDisposition fileDetail) { try { InnerMethodName.set("executeScript"); ScriptClassLoader scriptClassLoader = new ScriptClassLoader(Thread.currentThread().getContextClassLoader()); - Class scriptClass = uploadClass(stream, scriptClassLoader, fileDetail.getFileName().replace(".class", "")); + Class scriptClass = uploadClass(file, scriptClassLoader, fileDetail.getFileName().replace(".class", "")); return internalRun(scriptClass, name, destinationFolderId, asynch, writeResult); }catch(Throwable e) { log.error("error executing script {}", name,e); @@ -106,6 +154,18 @@ public class ScriptManager { } } + /** + * Get the status of a script + * + * @param id id of the script + * @return outcome of the script execution + */ + @ResourceMethodSignature(output = ScriptStatus.class, pathParams = { @PathParam("id") }) + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Script status retrieved."), + @ResponseCode ( code = 403, condition = "You're not allowed to inquire script status."), + @ResponseCode ( code = 404, condition = "Script not found."), + }) @GET @Path("{id}/status") @AuthorizationControl(allowedRoles = {INFRASTRUCTURE_MANAGER_ROLE}) @@ -122,7 +182,17 @@ public class ScriptManager { return status; } - + + /** + * Export the data + * + * @return outcome of the export + */ + @StatusCodes({ + @ResponseCode ( code = 200, condition = "Script status retrieved."), + @ResponseCode ( code = 403, condition = "You're not allowed to inquire script status."), + @ResponseCode ( code = 404, condition = "Script not found."), + }) @GET @Path("export") @AuthorizationControl(allowedRoles = {INFRASTRUCTURE_MANAGER_ROLE})