410 lines
12 KiB
Java
410 lines
12 KiB
Java
|
package org.gcube.gcat.persistence.ckan;
|
||
|
|
||
|
import java.net.MalformedURLException;
|
||
|
import java.net.URL;
|
||
|
import java.util.UUID;
|
||
|
|
||
|
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 org.gcube.gcat.utils.ApplicationMode;
|
||
|
import org.gcube.gcat.utils.ContextUtility;
|
||
|
import org.gcube.gcat.utils.HTTPCall;
|
||
|
import org.gcube.gcat.workspace.StorageHubManagement;
|
||
|
import org.slf4j.Logger;
|
||
|
import org.slf4j.LoggerFactory;
|
||
|
|
||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||
|
|
||
|
/**
|
||
|
* @author Luca Frosini (ISTI - CNR)
|
||
|
*/
|
||
|
public class CKANResource extends CKAN {
|
||
|
|
||
|
private static final Logger logger = LoggerFactory.getLogger(CKANResource.class);
|
||
|
|
||
|
// see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.resource_create
|
||
|
public static final String RESOURCE_CREATE = CKAN.CKAN_API_PATH + "resource_create";
|
||
|
// see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.resource_show
|
||
|
public static final String RESOURCE_SHOW = CKAN.CKAN_API_PATH + "resource_show";
|
||
|
// see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.resource_update
|
||
|
public static final String RESOURCE_UPDATE = CKAN.CKAN_API_PATH + "resource_update";
|
||
|
// see http://docs.ckan.org/en/latest/api/#ckan.logic.action.patch.resource_patch
|
||
|
public static final String RESOURCE_PATCH = CKAN.CKAN_API_PATH + "resource_patch";
|
||
|
// see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.resource_delete
|
||
|
public static final String RESOURCE_DELETE = CKAN.CKAN_API_PATH + "resource_delete";
|
||
|
|
||
|
/* TODO Remove this code ASAP. It requires a function from Storage HUB */
|
||
|
private static final String URI_RESOLVER_STORAGE_HUB_HOST_PROD = "data.d4science.org";
|
||
|
private static final String URI_RESOLVER_STORAGE_HUB_HOST_DEV = "data-d.d4science.org";
|
||
|
|
||
|
public static final String URI_RESOLVER_STORAGE_HUB_HOST;
|
||
|
public static final String URI_RESOLVER_STORAGE_HUB_PATH = "/shub/";
|
||
|
|
||
|
protected static final String URL_KEY = "url";
|
||
|
|
||
|
private static final String RESOURCES_KEY = "resources";
|
||
|
private static final String PACKAGE_ID_KEY = "package_id";
|
||
|
private static final String MIME_TYPE_KEY = "mimetype";
|
||
|
private static final String REVISION_ID_KEY = "revision_id";
|
||
|
|
||
|
private static final String TEMP = "TEMP_";
|
||
|
|
||
|
|
||
|
static {
|
||
|
String context = ContextUtility.getCurrentContext();
|
||
|
if(context.startsWith("/gcube")) {
|
||
|
URI_RESOLVER_STORAGE_HUB_HOST = URI_RESOLVER_STORAGE_HUB_HOST_DEV;
|
||
|
} else {
|
||
|
URI_RESOLVER_STORAGE_HUB_HOST = URI_RESOLVER_STORAGE_HUB_HOST_PROD;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
/* TODO END Code to be Removed */
|
||
|
|
||
|
protected String itemID;
|
||
|
|
||
|
public String getItemID() {
|
||
|
return itemID;
|
||
|
}
|
||
|
|
||
|
protected String resourceID;
|
||
|
|
||
|
protected Boolean persisted;
|
||
|
protected URL persistedURL;
|
||
|
|
||
|
protected String mimeType;
|
||
|
|
||
|
protected JsonNode previousRepresentation;
|
||
|
|
||
|
protected StorageHubManagement storageHubManagement;
|
||
|
|
||
|
public URL getPersistedURL() {
|
||
|
return persistedURL;
|
||
|
}
|
||
|
|
||
|
public static String extractResourceID(JsonNode jsonNode) {
|
||
|
String resourceID = null;
|
||
|
if(jsonNode.has(ID_KEY)) {
|
||
|
resourceID = jsonNode.get(ID_KEY).asText();
|
||
|
}
|
||
|
return resourceID;
|
||
|
}
|
||
|
|
||
|
public String getResourceID() {
|
||
|
if(resourceID==null && previousRepresentation!=null) {
|
||
|
resourceID = CKANResource.extractResourceID(previousRepresentation);
|
||
|
}
|
||
|
return resourceID;
|
||
|
}
|
||
|
|
||
|
public void setResourceID(String resourceID) {
|
||
|
this.resourceID = resourceID;
|
||
|
}
|
||
|
|
||
|
public void setPreviousRepresentation(JsonNode jsonNode) {
|
||
|
previousRepresentation = jsonNode;
|
||
|
}
|
||
|
|
||
|
public JsonNode getPreviousRepresentation() {
|
||
|
if(previousRepresentation==null && resourceID!=null) {
|
||
|
super.read();
|
||
|
validate(result);
|
||
|
previousRepresentation = result;
|
||
|
}
|
||
|
return previousRepresentation;
|
||
|
}
|
||
|
|
||
|
public CKANResource(String itemID) {
|
||
|
super();
|
||
|
this.itemID = itemID;
|
||
|
CREATE = RESOURCE_CREATE;
|
||
|
READ = RESOURCE_SHOW;
|
||
|
UPDATE = RESOURCE_UPDATE;
|
||
|
PATCH = RESOURCE_PATCH;
|
||
|
DELETE = RESOURCE_DELETE;
|
||
|
PURGE = null;
|
||
|
persisted = null;
|
||
|
previousRepresentation = null;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String list() {
|
||
|
CKANPackage ckanPackage = new CKANPackage();
|
||
|
ckanPackage.setName(itemID);
|
||
|
String itemJson = ckanPackage.read();
|
||
|
JsonNode item = getAsJsonNode(itemJson);
|
||
|
JsonNode resources = item.get(RESOURCES_KEY);
|
||
|
return getAsString(resources);
|
||
|
}
|
||
|
|
||
|
protected ObjectNode persistStorageFile(ObjectNode objectNode) {
|
||
|
|
||
|
if(objectNode.has(URL_KEY)) {
|
||
|
String urlString = objectNode.get(URL_KEY).asText();
|
||
|
|
||
|
URL url;
|
||
|
try {
|
||
|
url = new URL(urlString);
|
||
|
} catch(MalformedURLException e) {
|
||
|
throw new BadRequestException(e);
|
||
|
}
|
||
|
|
||
|
url = copyStorageResource(url);
|
||
|
|
||
|
if(mimeType!=null) {
|
||
|
objectNode.put(MIME_TYPE_KEY, mimeType);
|
||
|
}
|
||
|
|
||
|
objectNode.put(URL_KEY, url.toString());
|
||
|
return objectNode;
|
||
|
}
|
||
|
|
||
|
String error = String.format("The content must contains the %s property", URL_KEY);
|
||
|
throw new BadRequestException(error);
|
||
|
|
||
|
}
|
||
|
|
||
|
protected ObjectNode validate(String json) throws MalformedURLException {
|
||
|
JsonNode jsonNode = getAsJsonNode(json);
|
||
|
return validate(jsonNode);
|
||
|
}
|
||
|
|
||
|
protected ObjectNode validate(JsonNode jsonNode) {
|
||
|
|
||
|
ObjectNode objectNode = (ObjectNode) jsonNode;
|
||
|
|
||
|
if(objectNode.has(PACKAGE_ID_KEY)) {
|
||
|
String packageId = objectNode.get(PACKAGE_ID_KEY).asText();
|
||
|
if(packageId.compareTo(itemID) != 0) {
|
||
|
String error = String.format(
|
||
|
"Item ID %s does not match %s which is the value of %s contained in the representation.",
|
||
|
itemID, packageId, PACKAGE_ID_KEY);
|
||
|
throw new BadRequestException(error);
|
||
|
}
|
||
|
} else {
|
||
|
objectNode.put(PACKAGE_ID_KEY, itemID);
|
||
|
}
|
||
|
|
||
|
if(objectNode.has(ID_KEY)) {
|
||
|
String gotId = objectNode.get(ID_KEY).asText();
|
||
|
if(resourceID == null) {
|
||
|
resourceID = gotId;
|
||
|
} else {
|
||
|
if(resourceID.compareTo(gotId) != 0) {
|
||
|
String error = String.format(
|
||
|
"Resource ID %s does not match %s which is the value of %s contained in the representation.",
|
||
|
resourceID, gotId, ID_KEY);
|
||
|
throw new BadRequestException(error);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
resourceID = TEMP + UUID.randomUUID().toString();
|
||
|
logger.trace(
|
||
|
"The id of the resource with name {} for package {} has not been provided. It has been generated : {}",
|
||
|
name, itemID, resourceID);
|
||
|
}
|
||
|
|
||
|
return objectNode;
|
||
|
}
|
||
|
|
||
|
protected URL getFinalURL(String url) {
|
||
|
try {
|
||
|
URL urlURL = new URL(url);
|
||
|
return getFinalURL(urlURL);
|
||
|
} catch(MalformedURLException e) {
|
||
|
throw new BadRequestException(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected URL getFinalURL(URL url) {
|
||
|
HTTPCall httpCall = new HTTPCall(url.toString());
|
||
|
httpCall.setgCubeTargetService(false);
|
||
|
URL finalURL = httpCall.getFinalURL(url);
|
||
|
return finalURL;
|
||
|
}
|
||
|
|
||
|
protected boolean isStorageFile(URL url) {
|
||
|
if(url.getHost().compareTo(URI_RESOLVER_STORAGE_HUB_HOST) == 0) {
|
||
|
if(url.getPath().startsWith(URI_RESOLVER_STORAGE_HUB_PATH)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if the URl is a workspace URL so that is has to copy the resource to guarantee
|
||
|
* the resource remain persistent
|
||
|
* @param url the URL to check
|
||
|
* @return the public URL of the copied resource if any. It return the original URL otherwise
|
||
|
*/
|
||
|
protected URL copyStorageResource(URL url) {
|
||
|
persistedURL = getFinalURL(url);
|
||
|
if(isStorageFile(persistedURL)) {
|
||
|
ApplicationMode applicationMode = new ApplicationMode();
|
||
|
applicationMode.start();
|
||
|
storageHubManagement = new StorageHubManagement();
|
||
|
persistedURL = storageHubManagement.ensureResourcePersistence(persistedURL, itemID, resourceID, name);
|
||
|
mimeType = storageHubManagement.getMimeType();
|
||
|
persisted = true;
|
||
|
applicationMode.end();
|
||
|
}
|
||
|
return persistedURL;
|
||
|
}
|
||
|
|
||
|
protected void deleteStorageResource(URL url) {
|
||
|
persistedURL = getFinalURL(url);
|
||
|
if(isStorageFile(persistedURL)) {
|
||
|
ApplicationMode applicationMode = new ApplicationMode();
|
||
|
applicationMode.start();
|
||
|
storageHubManagement = new StorageHubManagement();
|
||
|
storageHubManagement.deleteResourcePersistence(persistedURL, itemID);
|
||
|
applicationMode.end();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public boolean isPersisted() {
|
||
|
if(persisted == null) {
|
||
|
persistedURL = getFinalURL(persistedURL);
|
||
|
if(isStorageFile(persistedURL)) {
|
||
|
ApplicationMode applicationMode = new ApplicationMode();
|
||
|
applicationMode.start();
|
||
|
storageHubManagement = new StorageHubManagement();
|
||
|
persisted = storageHubManagement.isItemPersistedFile(persistedURL, itemID);
|
||
|
applicationMode.end();
|
||
|
}
|
||
|
}
|
||
|
return persisted;
|
||
|
}
|
||
|
|
||
|
protected String create(JsonNode jsonNode) {
|
||
|
try {
|
||
|
ObjectNode objectNode = validate(jsonNode);
|
||
|
objectNode = persistStorageFile(objectNode);
|
||
|
String ret = super.create(getAsString(objectNode));
|
||
|
String gotResourceID = result.get(ID_KEY).asText();
|
||
|
if(gotResourceID.compareTo(resourceID)!=0) {
|
||
|
resourceID = gotResourceID;
|
||
|
String revisionID = result.get(REVISION_ID_KEY).asText();
|
||
|
storageHubManagement.renameFile(resourceID, revisionID);
|
||
|
}
|
||
|
return ret;
|
||
|
} catch(WebApplicationException e) {
|
||
|
throw e;
|
||
|
} catch(Exception e) {
|
||
|
throw new InternalServerErrorException(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String create(String json) {
|
||
|
JsonNode jsonNode = getAsJsonNode(json);
|
||
|
return create(jsonNode);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String read() {
|
||
|
return sendGetRequest(READ, getMapWithID(resourceID));
|
||
|
}
|
||
|
|
||
|
|
||
|
protected String update(JsonNode jsonNode) {
|
||
|
ObjectNode resourceNode = (ObjectNode) jsonNode;
|
||
|
// This cannot be moved outside otherwise we don't
|
||
|
resourceNode = validate(resourceNode);
|
||
|
|
||
|
getPreviousRepresentation();
|
||
|
|
||
|
String oldURL = previousRepresentation.get(CKANResource.URL_KEY).asText();
|
||
|
String newURL = resourceNode.get(CKANResource.URL_KEY).asText();
|
||
|
if(oldURL.compareTo(newURL)==0) {
|
||
|
logger.trace("The URL of the resource with id {} was not changed", resourceID);
|
||
|
}else {
|
||
|
logger.trace("The URL of resource with id {} has been changed the old URL was {}, the new URL is {}", resourceID, oldURL, newURL);
|
||
|
resourceNode = persistStorageFile(resourceNode);
|
||
|
/*
|
||
|
try {
|
||
|
URL urlOLD = new URL(oldURL);
|
||
|
deleteStorageResource(urlOLD);
|
||
|
}catch (Exception e) {
|
||
|
logger.error("Unable to remove old file at URL {}", oldURL);
|
||
|
}
|
||
|
*/
|
||
|
}
|
||
|
String ret = super.update(getAsString(resourceNode));
|
||
|
String revisionID = result.get(REVISION_ID_KEY).asText();
|
||
|
storageHubManagement.addRevisionID(resourceID, revisionID);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String update(String json) {
|
||
|
JsonNode jsonNode = getAsJsonNode(json);
|
||
|
return update(jsonNode);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String patch(String json) {
|
||
|
String[] moreAllowed = new String[] {HEAD.class.getSimpleName(), GET.class.getSimpleName(),
|
||
|
PUT.class.getSimpleName(), DELETE.class.getSimpleName()};
|
||
|
throw new NotAllowedException(OPTIONS.class.getSimpleName(), moreAllowed);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void delete() {
|
||
|
try {
|
||
|
deleteFile();
|
||
|
super.delete();
|
||
|
} catch(WebApplicationException e) {
|
||
|
throw e;
|
||
|
} catch(Exception e) {
|
||
|
throw new WebApplicationException(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void purge() {
|
||
|
String[] moreAllowed = new String[] {HEAD.class.getSimpleName(), GET.class.getSimpleName(),
|
||
|
PUT.class.getSimpleName(), DELETE.class.getSimpleName()};
|
||
|
throw new NotAllowedException(OPTIONS.class.getSimpleName(), moreAllowed);
|
||
|
}
|
||
|
|
||
|
public JsonNode createOrUpdate(JsonNode jsonNode) {
|
||
|
ObjectNode resourceNode = (ObjectNode) jsonNode;
|
||
|
if(resourceNode.has(ID_KEY)) {
|
||
|
update(resourceNode);
|
||
|
}else {
|
||
|
create(resourceNode);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
public void deleteFile() {
|
||
|
try {
|
||
|
getPreviousRepresentation();
|
||
|
URL url = new URL(previousRepresentation.get(URL_KEY).asText());
|
||
|
deleteStorageResource(url);
|
||
|
} catch(Exception e) {
|
||
|
logger.error("Unable to delete resource {}", previousRepresentation!=null ? getAsString(previousRepresentation) : "");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void rollback() {
|
||
|
if(previousRepresentation!=null) {
|
||
|
update(previousRepresentation);
|
||
|
}else {
|
||
|
delete();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|