package org.gcube.data_catalogue.grsf_publish_ws.services; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletContext; import javax.validation.Valid; import javax.validation.ValidationException; import javax.validation.constraints.NotNull; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.gcube.common.authorization.library.provider.AuthorizationProvider; import org.gcube.common.authorization.library.provider.SecurityTokenProvider; import org.gcube.common.authorization.library.utils.Caller; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.data_catalogue.grsf_publish_ws.json.input.DeleteProductBean; import org.gcube.data_catalogue.grsf_publish_ws.json.input.FisheryRecord; import org.gcube.data_catalogue.grsf_publish_ws.json.output.ResponseCreationBean; import org.gcube.data_catalogue.grsf_publish_ws.utils.AssociationToGroupThread; import org.gcube.data_catalogue.grsf_publish_ws.utils.HelperMethods; import org.gcube.datacatalogue.ckanutillibrary.DataCatalogue; import org.gcube.datacatalogue.ckanutillibrary.models.ResourceBean; import org.gcube.datacatalogue.ckanutillibrary.models.RolesCkanGroupOrOrg; import org.slf4j.LoggerFactory; import eu.trentorise.opendata.jackan.model.CkanDataset; /** * Fishery web service methods * @author Costantino Perciante at ISTI-CNR */ @Path("fishery/") public class GrsfPublisherFisheryService { private static final String DEFAULT_FISHERY_LICENSE = "CC-BY-SA-4.0"; private static final String THIS_TYPE = "Fishery"; // the context @Context ServletContext contextServlet; // Logger private static final org.slf4j.Logger logger = LoggerFactory.getLogger(GrsfPublisherFisheryService.class); @GET @Path("hello") @Produces(MediaType.TEXT_PLAIN) public Response hello(){ return Response.ok("Hello.. Fishery service is here").build(); } @GET @Path("get-licenses") @Produces(MediaType.APPLICATION_JSON) public Response getLicenses(){ // since are equals for stock and fishery, we just retrieve them all Map licenses = new HashMap(); Status status; try{ licenses = HelperMethods.getLicenses(HelperMethods.getDataCatalogueRunningInstance(ScopeProvider.instance.get())); status = Status.OK; }catch(Exception e){ logger.error("Failed to retrieve the list of licenses"); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(licenses).build(); } @POST @Path("publish-product") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response publishFishery( @NotNull(message="record cannot be null") @Valid FisheryRecord record) throws ValidationException{ // retrieve context and username Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); String context = ScopeProvider.instance.get(); String token = SecurityTokenProvider.instance.get(); logger.info("Incoming request for creating a fishery record = " + record); logger.info("Request comes from user " + username + " in context " + context); ResponseCreationBean responseBean = new ResponseCreationBean(); Status status = Status.INTERNAL_SERVER_ERROR; String id = ""; try{ // determine the organization in which this product should be put String contextInWhichPublish = HelperMethods.getContextFromStatus(record.getStatus(), contextServlet); if(contextInWhichPublish == null || !contextInWhichPublish.equals(context)){ // stop, this value must be defined status = Status.BAD_REQUEST; throw new IllegalArgumentException("Status attribute is not defined or the Token you are using is not correct to perform such request!"); }else{ DataCatalogue catalogue = HelperMethods.getDataCatalogueRunningInstance(context); if(catalogue == null){ status = Status.INTERNAL_SERVER_ERROR; throw new Exception("There was a problem while serving your request"); }else{ // check the user has editor/admin role into the org String organization = HelperMethods.retrieveOrgNameFromScope(contextInWhichPublish); if(!catalogue.getRoleOfUserInOrganization(username, organization, catalogue.getApiKeyFromUsername(username)).equalsIgnoreCase(RolesCkanGroupOrOrg.ADMIN.toString())){ status = Status.FORBIDDEN; throw new Exception("You are not authorized to create a product. Please check you have the Catalogue-admin role!"); } // The name of the product will be the uuid of the kb. The title will be the fishery's fishery_name. Fishery has also the constraint that // fishing area and jurisdiction area cannot be empty at the same time String futureName = record.getUuid(); String futureTitle = record.getFisheryName(); String fishingArea = record.getFishingArea(); String jurisdictionArea = record.getJurisdictionArea(); if(!HelperMethods.isNameValid(futureName)){ status = Status.BAD_REQUEST; throw new Exception("The name requested for the product is not correct! It should contain only alphanumeric characters, and symbols like '.' or '_', '-'"); }else if((fishingArea == null || fishingArea.isEmpty()) && (jurisdictionArea == null || jurisdictionArea.isEmpty())){ status = Status.BAD_REQUEST; throw new Exception("fishing_area and jurisdiction_area cannot be null/empty at the same time!"); }else{ logger.debug("Checking if such name [" + futureName + "] doesn't exist yet..."); boolean alreadyExist = catalogue.existProductWithNameOrId(futureName); if(alreadyExist){ logger.debug("A product with name " + futureName + " already exists"); status = Status.CONFLICT; throw new Exception("A product with name " + futureName + " already exists"); }else{ // set the type record.setProductType(THIS_TYPE); // evaluate the tags of the product List tags = new ArrayList(); HelperMethods.getTags(tags, record); // evaluate the groups List groups = new ArrayList(); HelperMethods.getGroups(groups, record); // evaluate the custom fields Map> customFields = new HashMap>(); if(record.getExtras() != null) customFields = record.getExtras(); // automatically retrieve the other ones HelperMethods.getExtras(customFields, record); // retrieve the user's email and fullname String authorMail = HelperMethods.getUserEmail(context, token); String authorFullname = HelperMethods.getUserFullname(context, token); if(authorMail == null || authorFullname == null){ logger.debug("Author fullname or mail missing, cannot continue"); status = Status.INTERNAL_SERVER_ERROR; throw new Exception("Sorry but there was not possible to retrieve your fullname/email!"); }else{ // evaluate the resources List resources = HelperMethods.getResourcesFromBean(record, username, tags, groups); // check the license id String license = null; if(record.getLicense() == null || record.getLicense().isEmpty()) license = DEFAULT_FISHERY_LICENSE; else if(HelperMethods.existsLicenseId(record.getLicense(), catalogue)) license = record.getLicense(); else throw new Exception("Please check the license id!"); long version = record.getVersion() == null ? 1 : record.getVersion(); // create the product id = catalogue.createCKanDatasetMultipleCustomFields( catalogue.getApiKeyFromUsername(username), futureTitle, futureName, organization, authorFullname, authorMail, record.getMaintainer(), record.getMaintainerContact(), version, record.getDescription(), license, tags, customFields, resources, false); if(id != null){ logger.info("Product created! Id is " + id); responseBean.setId(id); status = Status.CREATED; responseBean.setError(null); responseBean.setProductUrl(catalogue.getPortletUrl() + "?" + URLEncoder.encode("path=/dataset/" + futureName, "UTF-8")); responseBean.setKbUuid(record.getUuid()); if(!groups.isEmpty()){ logger.info("Launching thread for association to the list of groups " + groups); new AssociationToGroupThread(groups, id, organization, username, catalogue).start(); } } } } } } } }catch(Exception e){ logger.error("Failed to create fishery record", e); responseBean.setError(e.getMessage()); } return Response.status(status).entity(responseBean).build(); } @DELETE @Path("delete-product") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response deleteFishery( @NotNull(message="input value is missing") @Valid DeleteProductBean recordToDelete) throws ValidationException{ // retrieve context and username Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); String context = ScopeProvider.instance.get(); ResponseCreationBean responseBean = new ResponseCreationBean(); Status status = Status.INTERNAL_SERVER_ERROR; // check it is a fishery ... logger.info("Received call to delete product with id " + recordToDelete.getId() + ", checking if it is a fishery"); try{ DataCatalogue catalogue = HelperMethods.getDataCatalogueRunningInstance(context); if(catalogue == null){ status = Status.INTERNAL_SERVER_ERROR; throw new Exception("There was a problem while serving your request"); } // retrieve the catalogue instance CkanDataset fisheryInCkan = catalogue.getDataset(recordToDelete.getId(), catalogue.getApiKeyFromUsername(username)); if(fisheryInCkan == null){ status = Status.NOT_FOUND; throw new Exception("There was a problem while serving your request. This product was not found"); } // get extras and check there is the field Fishery Name that is mandatory for fishery if(fisheryInCkan.getExtrasAsHashMap().containsKey("Fishery Name")){ logger.warn("Ok, this is a fishery, removing it"); boolean deleted = catalogue.deleteProduct(fisheryInCkan.getId(), catalogue.getApiKeyFromUsername(username), true); if(deleted){ logger.info("Stock DELETED AND PURGED!"); status = Status.OK; responseBean.setId(fisheryInCkan.getId()); } else{ status = Status.INTERNAL_SERVER_ERROR; throw new Exception("Request failed, sorry"); } }else{ status = Status.BAD_REQUEST; throw new Exception("The id you are using doesn't belong to a Fishery product!"); } }catch(Exception e){ logger.error("Failed to delete this "); status = Status.INTERNAL_SERVER_ERROR; responseBean.setError(e.getMessage()); } return Response.status(status).entity(responseBean).build(); } }