package org.gcube.gcat.persistence.ckan; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.BadRequestException; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HEAD; import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.NotAllowedException; import javax.ws.rs.OPTIONS; import javax.ws.rs.PUT; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response.Status; import org.gcube.common.scope.impl.ScopeBean; import org.gcube.common.scope.impl.ScopeBean.Type; import org.gcube.gcat.annotation.PURGE; import org.gcube.gcat.oldutils.Validator; import org.gcube.gcat.social.SocialService; import org.gcube.gcat.utils.ContextUtility; import org.gcube.gcat.utils.URIResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; /** * @author Luca Frosini (ISTI - CNR) */ public class CKANPackage extends CKAN { private static final Logger logger = LoggerFactory.getLogger(CKANPackage.class); // see https://docs.ckan.org/en/latest/api/#ckan.logic.action.get.package_list public static final String ITEM_LIST = CKAN.CKAN_API_PATH + "package_list"; // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.package_create public static final String ITEM_CREATE = CKAN.CKAN_API_PATH + "package_create"; // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.package_show public static final String ITEM_SHOW = CKAN.CKAN_API_PATH + "package_show"; // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.package_update public static final String ITEM_UPDATE = CKAN.CKAN_API_PATH + "package_update"; // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.patch.package_patch public static final String ITEM_PATCH = CKAN.CKAN_API_PATH + "package_patch"; // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.package_delete public static final String ITEM_DELETE = CKAN.CKAN_API_PATH + "package_delete"; // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.dataset_purge public static final String ITEM_PURGE = CKAN.CKAN_API_PATH + "dataset_purge"; protected static final String LICENSE_KEY = "license_id"; protected static final String ITEM_URL_KEY = "item_url"; protected static final String AUTHOR_KEY = "author"; protected static final String AUTHOR_EMAIL_KEY = "author_email"; protected static final String OWNER_ORG_KEY = "owner_org"; protected static final String RESOURCES_KEY = "resources"; protected static final String TITLE_KEY = "title"; public static final String EXTRA_TYPES_KEY = "extras"; public static final String EXTRA_TYPES_KEY_KEY = "key"; public static final String EXTRA_TYPES_KEY_VALUE_SYSTEM_TYPE = "system:type"; public static final String EXTRA_TYPES_VALUE_KEY = "value"; public static final String GROUPS_KEY = "groups"; public static final String TAGS_KEY = "tags"; protected final List managedResources; protected String itemID; public CKANPackage() { super(); LIST = ITEM_LIST; CREATE = ITEM_CREATE; READ = ITEM_SHOW; UPDATE = ITEM_UPDATE; PATCH = ITEM_PATCH; DELETE = ITEM_DELETE; PURGE = ITEM_PURGE; managedResources = new ArrayList(); } public ObjectNode checkBaseInformation(String json) throws Exception { ObjectNode objectNode = (ObjectNode) mapper.readTree(json); objectNode = (ObjectNode) checkName(objectNode); // We need to enforce the itemID to properly manage resource persistence if(objectNode.has(ID_KEY)) { itemID = objectNode.get(ID_KEY).asText(); } // check license String licenseId = null; if(objectNode.has(LICENSE_KEY)) { licenseId = objectNode.get(LICENSE_KEY).asText(); } if(licenseId == null || licenseId.isEmpty()) { throw new BadRequestException( "You must specify a license identifier for the item. License list can be retrieved using licence collection"); } JsonNode userJsonNode = CKANUtility.getCKANUser(); objectNode.put(AUTHOR_KEY, userJsonNode.get(CKANUser.NAME).asText()); objectNode.put(AUTHOR_EMAIL_KEY, userJsonNode.get(CKANUser.EMAIL).asText()); // owner organization must be specified if the token belongs to a VRE ScopeBean scopeBean = new ScopeBean(ContextUtility.getCurrentContext()); String contextName = scopeBean.name(); String gotOrganization = null; if(objectNode.has(OWNER_ORG_KEY)) { gotOrganization = objectNode.get(OWNER_ORG_KEY).asText(); } if(scopeBean.is(Type.VRE)) { String organizationFromContext = contextName.toLowerCase().replace(" ", "_"); if(gotOrganization != null) { if(gotOrganization.compareTo(organizationFromContext) != 0) { CKANOrganization ckanOrganization = new CKANOrganization(); ckanOrganization.setName(organizationFromContext); ckanOrganization.read(); String organizationID = null; if(ckanOrganization.result.has(ID_KEY)) { organizationID = ckanOrganization.result.get(ID_KEY).asText(); } if(organizationID == null || gotOrganization.compareTo(organizationID) != 0) { throw new BadRequestException( "You can only publish in the Organization associate to the current VRE"); } } } else { objectNode.put(OWNER_ORG_KEY, organizationFromContext); } } else { // TODO check if the requested organization context is a sub context of current context // TODO check if the check is correct for PARTHENOS if(gotOrganization == null) { throw new BadRequestException("You must specify an Organization usign " + OWNER_ORG_KEY + " field"); } } return objectNode; } protected JsonNode validateJson(String json) { try { // check base information (and set them if needed) ObjectNode objectNode = checkBaseInformation(json); // Validating against profiles if any // List profiles = Profile.getProfilesNames(); TODO RESTORE THIS LINE List profiles = null; // TODO REMOVE THIS LINE if(profiles != null && !profiles.isEmpty()) { Validator.validateAgainstProfile(getAsString(objectNode), profiles); } return objectNode; } catch(BadRequestException e) { throw e; } catch(Exception e) { throw new BadRequestException(e); } } // see https://docs.ckan.org/en/latest/api/#ckan.logic.action.get.package_list @Override public String list() { return super.list(); } protected void rollbackManagedResources() { for(CKANResource ckanResource : managedResources) { try { ckanResource.rollback(); }catch (Exception e) { logger.error("Unable to rollback resource {} to the original value", ckanResource.getResourceID()); } } } protected ArrayNode createResources(ArrayNode resourcesToBeCreated) { ArrayNode created = mapper.createArrayNode(); for(JsonNode resourceNode : resourcesToBeCreated) { CKANResource ckanResource = new CKANResource(itemID); String json = ckanResource.create(getAsString(resourceNode)); created.add(getAsJsonNode(json)); managedResources.add(ckanResource); } return created; } // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.package_create @Override public String create(String json) { try { logger.debug("Going to create Item {}", json); JsonNode jsonNode = validateJson(json); ArrayNode resourcesToBeCreated = mapper.createArrayNode(); if(jsonNode.has(RESOURCES_KEY)) { resourcesToBeCreated = (ArrayNode) jsonNode.get(RESOURCES_KEY); ((ObjectNode) jsonNode).remove(RESOURCES_KEY); } super.create(getAsString(jsonNode)); this.itemID = result.get(ID_KEY).asText(); ArrayNode created = createResources(resourcesToBeCreated); ((ObjectNode) result).replace(RESOURCES_KEY, created); // Adding Item URL via Resolver URIResolver uriResolver = new URIResolver(); String catalogueItemURL = uriResolver.getCatalogueItemURL(name); ((ObjectNode) result).put(ITEM_URL_KEY, catalogueItemURL); // Actions performed after a package has been correctly created on ckan. String title = result.get(TITLE_KEY).asText(); ArrayNode arrayNode = (ArrayNode) result.get(TAGS_KEY); SocialService packagePostActions = new SocialService(catalogueItemURL, name, arrayNode, title); packagePostActions.start(); return getAsString(result); } catch(WebApplicationException e) { rollbackManagedResources(); throw e; } catch(Exception e) { rollbackManagedResources(); throw new InternalServerErrorException(e); } } // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.package_update @Override public String update(String json) { try { JsonNode jsonNode = validateJson(json); this.itemID = jsonNode.get(ID_KEY).asText(); read(); Map originalResources = new HashMap<>(); ArrayNode originalResourcesarrayNode = (ArrayNode) result.get(RESOURCES_KEY); if(originalResources!=null) { for(JsonNode resourceNode : originalResourcesarrayNode) { CKANResource ckanResource = new CKANResource(itemID); ckanResource.setPreviousRepresentation(resourceNode); String resourceID = ckanResource.getResourceID(); originalResources.put(resourceID, ckanResource); } } if(jsonNode.has(RESOURCES_KEY)) { ArrayNode resourcesToBeSend = mapper.createArrayNode(); ArrayNode receivedResources = (ArrayNode) jsonNode.get(RESOURCES_KEY); for(JsonNode resourceNode : receivedResources) { CKANResource ckanResource = new CKANResource(itemID); String resourceId = CKANResource.extractResourceID(resourceNode); if(resourceId!=null) { if(originalResources.containsKey(resourceId)) { ckanResource = originalResources.get(resourceId); originalResources.remove(resourceId); }else { throw new BadRequestException("The content cotains a resource with id " + resourceId + " which does not exists") ; } } resourceNode = ckanResource.createOrUpdate(resourceNode); resourcesToBeSend.add(resourceNode); managedResources.add(ckanResource); } ((ObjectNode) jsonNode).replace(RESOURCES_KEY, resourcesToBeSend); } sendPostRequest(ITEM_UPDATE, getAsString(jsonNode)); for(String resourceId : originalResources.keySet()) { CKANResource ckanResource = originalResources.get(resourceId); ckanResource.deleteFile(); } // Adding Item URL via Resolver URIResolver uriResolver = new URIResolver(); String catalogueItemURL = uriResolver.getCatalogueItemURL(name); ((ObjectNode) result).put(ITEM_URL_KEY, catalogueItemURL); return getAsString(result); } catch(WebApplicationException e) { rollbackManagedResources(); throw e; } catch(Exception e) { rollbackManagedResources(); throw new InternalServerErrorException(e); } } // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.patch.package_patch @Override public String patch(String json) { String[] moreAllowed = new String[] {HEAD.class.getSimpleName(), GET.class.getSimpleName(), PUT.class.getSimpleName(), DELETE.class.getSimpleName(), PURGE.class.getSimpleName()}; throw new NotAllowedException(OPTIONS.class.getSimpleName(), moreAllowed); } @Override protected void delete() { super.delete(); } @Override public void purge() { try { delete(); } catch(WebApplicationException e) { // If the item was deleted but not purged we obtain Not Found. This is accepted. The item has to be purged // with SysAdmin right. Status status = Status.fromStatusCode(e.getResponse().getStatusInfo().getStatusCode()); if(status != Status.NOT_FOUND) { throw e; } } setApiKey(CKANUtility.getSysAdminAPI()); read(); if(result.has(RESOURCES_KEY)) { itemID = result.get(ID_KEY).asText(); ArrayNode arrayNode = (ArrayNode) result.get(RESOURCES_KEY); for(JsonNode jsonNode : arrayNode) { CKANResource ckanResource = new CKANResource(itemID); ckanResource.setPreviousRepresentation(jsonNode); ckanResource.deleteFile(); } } super.purge(); } }