package org.gcube.data_catalogue.grsf_publish_ws.services; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; 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.PathParam; 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.Common; import org.gcube.data_catalogue.grsf_publish_ws.json.input.DeleteProductBean; import org.gcube.data_catalogue.grsf_publish_ws.json.input.RefersToBean; 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.groups.Product_Type; import org.gcube.data_catalogue.grsf_publish_ws.utils.groups.Record_Type; import org.gcube.data_catalogue.grsf_publish_ws.utils.groups.Sources; 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("{source}/stock/") public class GrsfPublisherStockService { private static final String DEFAULT_STOCK_LICENSE = "CC-BY-SA-4.0"; // 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(){ Status status = Status.OK; Map licenses = CommonServiceUtils.getLicenses(); if(licenses == null) 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, @PathParam("source") String source) 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{ // Cast the source to the accepted ones Sources sourceInPath = Sources.onDeserialize(source); if(sourceInPath == null) throw new Exception("The specified source in the path is unrecogized. Values accepted are [rams, firms, fishsource, grsf]"); logger.info("The request is to create a stock object of source " + sourceInPath); 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(context); 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{ // validate the record if it is a GRSF one and set the record type if(sourceInPath.equals(Sources.GRSF)){ record.setRecordType(Record_Type.AGGREGATED); CommonServiceUtils.validateAggregatedRecord(record); }else record.setRecordType(Record_Type.ORIGINAL); // set the type record.setProductType(Product_Type.STOCK.getOrigName()); // evaluate the custom fields/tags, resources and groups Map> customFields = record.getExtrasFields(); Set tags = new HashSet(); Set groups = new HashSet(); List resources = record.getExtrasResources(); boolean skipTags = !sourceInPath.equals(Sources.GRSF); // no tags for the Original records CommonServiceUtils.getTagsGroupsResourcesExtrasByRecord(tags, skipTags, groups, resources, customFields, record, username, sourceInPath.getOrigName().toLowerCase()); // manage the refers to if(sourceInPath.equals(Sources.GRSF)){ List refersTo = record.getRefersTo(); for (RefersToBean refersToBean : refersTo) { resources.add(new ResourceBean(refersToBean.getUrl(), "Source of product " + futureTitle + " in the catalogue has id: " + refersToBean.getId(), "Information of a source of the product " + futureTitle, null, username, null, null)); } } // 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{ // 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(); logger.info("Invoking creation method.."); // create the product String id = catalogue.createCKanDatasetMultipleCustomFields( catalogue.getApiKeyFromUsername(username), futureTitle, futureName, organization,//"grsf", //TO DELETE TODO authorFullname, authorMail, record.getMaintainer(), record.getMaintainerContact(), version, HelperMethods.removeHTML(record.getDescription()), license, new ArrayList(tags), customFields, resources, true); 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(new ArrayList(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, context, token).start(); }else throw new Exception("There was an error during the product generation, sorry"); } } } } // } }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, @PathParam("source") String source) 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"); } // Cast the source to the accepted ones Sources sourceInPath = Sources.onDeserialize(source); if(sourceInPath == null) throw new Exception("The specified source in the path is unrecognized. Values accepted are [rams, firms, fishsource, grsf]"); logger.info("The request is to create a stock object of source " + sourceInPath); // 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 product type if(stockInCkan.getExtrasAsHashMap().get(Common.PRODUCT_TYPE_KEY).equals(Product_Type.STOCK.getOrigName())){ 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(); } }