package org.gcube.gcat.persistence.ckan; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.util.HashMap; import java.util.Map; import javax.ws.rs.BadRequestException; import javax.ws.rs.ForbiddenException; import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.NotFoundException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import org.gcube.com.fasterxml.jackson.core.JsonProcessingException; import org.gcube.com.fasterxml.jackson.databind.JsonNode; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; import org.gcube.com.fasterxml.jackson.databind.node.NullNode; import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode; import org.gcube.common.gxhttp.request.GXHTTPStringRequest; import org.gcube.gcat.configuration.CatalogueConfigurationFactory; import org.gcube.gcat.utils.HTTPUtility; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Luca Frosini (ISTI - CNR) */ public abstract class CKAN { private static final Logger logger = LoggerFactory.getLogger(CKAN.class); protected static final String ID_KEY = "id"; protected static final String TITLE_KEY = "title"; protected static final String NAME_KEY = "name"; protected static final String ERROR_KEY = "error"; protected static final String ERROR_TYPE_KEY = "__type"; protected static final String MESSAGE_KEY = "message"; protected static final String OWNER_ORG_KEY = "owner_org"; protected static final String RESULT_KEY = "result"; protected static final String SUCCESS_KEY = "success"; public static final String LIMIT_KEY = "limit"; public static final String OFFSET_KEY = "offset"; protected static final String NOT_FOUND_ERROR = "Not Found Error"; protected static final String AUTHORIZATION_ERROR = "Authorization Error"; protected static final String VALIDATION_ERROR = "Validation Error"; // api rest path CKAN public final static String CKAN_API_PATH = "/api/3/action/"; // ckan header authorization public final static String AUTH_CKAN_HEADER = HttpHeaders.AUTHORIZATION; public final static String NAME_REGEX = "^[a-z0-9_\\\\-]{2,100}$"; protected String LIST; protected String CREATE; protected String READ; protected String UPDATE; protected String PATCH; protected String DELETE; protected String PURGE; protected final ObjectMapper mapper; protected String name; protected String apiKey; protected JsonNode result; protected String nameRegex; protected UriInfo uriInfo; public void setUriInfo(UriInfo uriInfo) { this.uriInfo = uriInfo; } public String getApiKey() { if(apiKey == null) { try { return CKANUtility.getApiKey(); } catch(Exception e) { throw new InternalServerErrorException(e); } } return apiKey; } public void setApiKey(String apiKey) { this.apiKey = apiKey; } public String getName() { return name; } public void setName(String name) { this.name = name; } public ObjectMapper getMapper() { return mapper; } public JsonNode getJsonNodeResult() { return result; } protected CKAN() { try { this.mapper = new ObjectMapper(); this.nameRegex = CKAN.NAME_REGEX; } catch(Exception e) { throw new InternalServerErrorException(e); } } protected JsonNode getAsJsonNode(String json) { try { return mapper.readTree(json); } catch(IOException e) { throw new BadRequestException(e); } } /** * Validate the CKAN response and return the * @param json * @return the validated response as JsonNode */ protected JsonNode validateCKANResponse(String json) { JsonNode jsonNode = getAsJsonNode(json); if(jsonNode.get(SUCCESS_KEY).asBoolean()) { return jsonNode.get(RESULT_KEY); } else { try { JsonNode error = jsonNode.get(ERROR_KEY); String errorType = error.get(ERROR_TYPE_KEY).asText(); if(errorType.compareTo(VALIDATION_ERROR) == 0) { throw new BadRequestException(getAsString(error)); } String message = error.get(MESSAGE_KEY).asText(); if(errorType.compareTo(NOT_FOUND_ERROR) == 0) { throw new NotFoundException(message); } if(errorType.compareTo(AUTHORIZATION_ERROR) == 0) { throw new ForbiddenException(message); } // TODO parse more cases } catch(WebApplicationException e) { throw e; } catch(Exception e) { throw new BadRequestException(json); } throw new BadRequestException(json); } } protected String getAsString(JsonNode node) { try { String json = mapper.writeValueAsString(node); return json; } catch(JsonProcessingException e) { throw new InternalServerErrorException(e); } } protected JsonNode checkName(JsonNode jsonNode) { try { String gotName = jsonNode.get(NAME_KEY).asText(); if(!gotName.matches(nameRegex)) { throw new BadRequestException( "The 'name' must be between 2 and 100 characters long and contain only lowercase alphanumeric characters, '-' and '_'. You can validate your name using the regular expression : " + NAME_REGEX); } if(name == null) { name = gotName; } if(gotName != null && gotName.compareTo(name) != 0) { String error = String.format( "The name (%s) does not match with the '%s' contained in the provided content (%s).", name, NAME_KEY, gotName); throw new BadRequestException(error); } return jsonNode; } catch(BadRequestException e) { throw e; } catch(Exception e) { throw new BadRequestException("Unable to obtain a correct 'name' from the provided content"); } } protected JsonNode checkName(String json) { JsonNode jsonNode = getAsJsonNode(json); checkName(jsonNode); return jsonNode; } protected JsonNode createJsonNodeWithID(String id) { ObjectNode objectNode = mapper.createObjectNode(); objectNode.put(ID_KEY, id); return objectNode; } protected JsonNode createJsonNodeWithNameAsID() { return createJsonNodeWithID(name); } protected Map getMapWithNameAsID() { return getMapWithID(name); } protected Map getMapWithID(String id) { Map map = new HashMap<>(); map.put(ID_KEY, id); return map; } protected GXHTTPStringRequest getGXHTTPStringRequest(String path, boolean post) throws UnsupportedEncodingException { String catalogueURL = CatalogueConfigurationFactory.getInstance().getCkanURL(); GXHTTPStringRequest gxhttpStringRequest = HTTPUtility.createGXHTTPStringRequest(catalogueURL, path, post); gxhttpStringRequest.isExternalCall(true); gxhttpStringRequest.header(AUTH_CKAN_HEADER, getApiKey()); return gxhttpStringRequest; } protected String getResultAsString(HttpURLConnection httpURLConnection) throws IOException { int responseCode = httpURLConnection.getResponseCode(); if(responseCode >= Status.BAD_REQUEST.getStatusCode()) { Status status = Status.fromStatusCode(responseCode); switch (status) { case NOT_FOUND: throw new NotFoundException(); default: break; } InputStream inputStream = httpURLConnection.getErrorStream(); StringBuilder result = HTTPUtility.getStringBuilder(inputStream); logger.trace(result.toString()); try { JsonNode jsonNode = getAsJsonNode(result.toString()); JsonNode error = jsonNode.get(ERROR_KEY); throw new WebApplicationException(getAsString(error), status); }catch (WebApplicationException e) { throw e; }catch (Exception e) { throw new WebApplicationException(result.toString(), status); } } InputStream inputStream = httpURLConnection.getInputStream(); String ret = HTTPUtility.getStringBuilder(inputStream).toString(); logger.trace("Got Respose is {}", ret); return ret; } protected String getResultAndValidate(HttpURLConnection httpURLConnection) throws IOException { String ret = getResultAsString(httpURLConnection); logger.trace("Got Respose is {}", ret); result = validateCKANResponse(ret); if(result instanceof NullNode) { result = mapper.createObjectNode(); } return getAsString(result); } protected String sendGetRequest(String path, Map parameters) { try { logger.debug("Going to send GET request with parameters {}", parameters); GXHTTPStringRequest gxhttpStringRequest = getGXHTTPStringRequest(path, false); gxhttpStringRequest.queryParams(parameters); HttpURLConnection httpURLConnection = gxhttpStringRequest.get(); return getResultAndValidate(httpURLConnection); } catch(WebApplicationException e) { throw e; } catch(Exception e) { throw new InternalServerErrorException(e); } } protected String sendPostRequest(String path, String body) { try { logger.debug("Going to send POST request with body {}", body); GXHTTPStringRequest gxhttpStringRequest = getGXHTTPStringRequest(path, true); HttpURLConnection httpURLConnection = gxhttpStringRequest.post(body); return getResultAndValidate(httpURLConnection); } catch(WebApplicationException e) { throw e; } catch(Exception e) { throw new InternalServerErrorException(e); } } protected String sendPostRequest(String path, JsonNode jsonNode) { return sendPostRequest(path, getAsString(jsonNode)); } public String list(int limit, int offset) { Map parameters = new HashMap<>(); if(limit > 0) { parameters.put(LIMIT_KEY, String.valueOf(limit)); } if(offset >= 0) { parameters.put(OFFSET_KEY, String.valueOf(offset)); } return sendGetRequest(LIST, parameters); } public String create(String json) { return sendPostRequest(CREATE, json); } public String read() { return sendGetRequest(READ, getMapWithNameAsID()); } public String update(String json) { checkName(json); return sendPostRequest(UPDATE, json); } public String patch(String json) { JsonNode jsonNode = checkName(json); ObjectNode objectNode = ((ObjectNode) jsonNode); objectNode.put(ID_KEY, name); objectNode.remove(NAME_KEY); return sendPostRequest(PATCH, objectNode); } protected void delete() { sendPostRequest(DELETE, createJsonNodeWithNameAsID()); } public void delete(boolean purge) { if(purge) { purge(); } else { delete(); } } protected void purge() { sendPostRequest(PURGE, createJsonNodeWithNameAsID()); } }