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.StockRecord; import org.gcube.data_catalogue.grsf_publish_ws.json.output.ResponseCreationBean; import org.gcube.data_catalogue.grsf_publish_ws.utils.HelperMethods; import org.gcube.data_catalogue.grsf_publish_ws.utils.threads.AssociationToGroupThread; import org.gcube.data_catalogue.grsf_publish_ws.utils.threads.ManageTimeSeriesThread; 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; /** * Stock web service methods * @author Costantino Perciante at ISTI-CNR */ @Path("stock/") public class GrsfPublisherStockService { private static final String DEFAULT_STOCK_LICENSE = "CC-BY-SA-4.0"; private static final String THIS_TYPE = "Stock"; // the context @Context ServletContext contextServlet; // Logger private static final org.slf4j.Logger logger = LoggerFactory.getLogger(GrsfPublisherStockService.class); @GET @Path("hello") @Produces(MediaType.TEXT_PLAIN) public Response hello(){ return Response.ok("Hello.. Stock 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 publishStock( @NotNull(message="record cannot be null") @Valid StockRecord 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 stock record = " + record); logger.info("Request coming from user " + username + " in context " + context); ResponseCreationBean responseBean = new ResponseCreationBean(); Status status = Status.INTERNAL_SERVER_ERROR; 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); String role = catalogue.getRoleOfUserInOrganization(username, organization, catalogue.getApiKeyFromUsername(username)); logger.info("***************************Role of the user " + username + " is " + role); if(!role.equalsIgnoreCase(RolesCkanGroupOrOrg.ADMIN.toString())){ status = Status.FORBIDDEN; throw new Exception("You are not authorized to create a product. Please check you have the Catalogue-Administrator role!"); } // check the record has a name, at least String futureName = record.getUuid(); String futureTitle = record.getStockName(); 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{ 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("Sorry but a product with such name 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"); responseBean.setId(null); status = Status.INTERNAL_SERVER_ERROR; throw new Exception("Sorry but 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_STOCK_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 String 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.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); AssociationToGroupThread threadGroups = new AssociationToGroupThread(groups, id, organization, username, catalogue); threadGroups.start(); logger.info("Waiting association thread to die.."); threadGroups.join(); logger.debug("Ok, it died"); } // manage time series logger.info("Launching thread for time series handling"); new ManageTimeSeriesThread(record, futureName, username, catalogue, ScopeProvider.instance.get()).start(); } } } } } } }catch(Exception e){ logger.error("Failed to create stock 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 deleteStock( @NotNull(message="missing input value") @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 stock ... logger.info("Received call to delete product with id " + recordToDelete.getId() + ", checking if it is a stock"); 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 stockInCkan = catalogue.getDataset(recordToDelete.getId(), catalogue.getApiKeyFromUsername(username)); if(stockInCkan == 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 Assessment distribution area that is mandatory for stock if(stockInCkan.getExtrasAsHashMap().containsKey("Stock Name")){ logger.warn("Ok, this is a stock, removing it"); boolean deleted = catalogue.deleteProduct(stockInCkan.getId(), catalogue.getApiKeyFromUsername(username), true); if(deleted){ logger.info("Stock DELETED AND PURGED!"); status = Status.OK; responseBean.setId(stockInCkan.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 Stock product!"); } }catch(Exception e){ logger.error("Failed to delete this ", e); status = Status.INTERNAL_SERVER_ERROR; responseBean.setError(e.getMessage()); } return Response.status(status).entity(responseBean).build(); } }