package org.gcube.datacatalogue.ckanutillibrary.server; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.math.BigDecimal; import java.net.URLEncoder; import java.nio.charset.Charset; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import net.htmlparser.jericho.Renderer; import net.htmlparser.jericho.Segment; import net.htmlparser.jericho.Source; import org.gcube.datacatalogue.ckanutillibrary.server.models.CKanUserWrapper; import org.gcube.datacatalogue.ckanutillibrary.server.models.CkanDatasetRelationship; import org.gcube.datacatalogue.ckanutillibrary.server.models.DatasetRelationships; import org.gcube.datacatalogue.ckanutillibrary.server.models.ResourceBean; import org.gcube.datacatalogue.ckanutillibrary.server.models.State; import org.gcube.datacatalogue.ckanutillibrary.server.utils.UtilMethods; import org.gcube.datacatalogue.ckanutillibrary.server.utils.url.EntityContext; import org.gcube.datacatalogue.ckanutillibrary.shared.RolesCkanGroupOrOrg; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.json.simple.parser.JSONParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.trentorise.opendata.jackan.CheckedCkanClient; import eu.trentorise.opendata.jackan.CkanClient; import eu.trentorise.opendata.jackan.exceptions.JackanException; import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpEntity; import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpResponse; import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpStatus; import eu.trentorise.opendata.jackan.internal.org.apache.http.client.methods.HttpGet; import eu.trentorise.opendata.jackan.internal.org.apache.http.client.methods.HttpPost; import eu.trentorise.opendata.jackan.internal.org.apache.http.entity.ContentType; import eu.trentorise.opendata.jackan.internal.org.apache.http.entity.StringEntity; import eu.trentorise.opendata.jackan.internal.org.apache.http.entity.mime.MultipartEntityBuilder; import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.CloseableHttpClient; import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.HttpClientBuilder; import eu.trentorise.opendata.jackan.internal.org.apache.http.util.EntityUtils; import eu.trentorise.opendata.jackan.model.CkanDataset; import eu.trentorise.opendata.jackan.model.CkanGroup; import eu.trentorise.opendata.jackan.model.CkanLicense; import eu.trentorise.opendata.jackan.model.CkanOrganization; import eu.trentorise.opendata.jackan.model.CkanPair; import eu.trentorise.opendata.jackan.model.CkanResource; import eu.trentorise.opendata.jackan.model.CkanTag; import eu.trentorise.opendata.jackan.model.CkanUser; /** * This is the Ckan Utils implementation class. * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) */ public class DataCatalogueImpl implements DataCatalogue{ private static final Logger logger = LoggerFactory.getLogger(DataCatalogueImpl.class); private String CKAN_CATALOGUE_URL; private String CKAN_DB_NAME; private String CKAN_DB_USER; private String CKAN_DB_PASSWORD; private String CKAN_DB_URL; private Integer CKAN_DB_PORT; private String PORTLET_URL_FOR_SCOPE; private String CKAN_TOKEN_SYS; private String URI_RESOLVER_URL; private boolean MANAGE_PRODUCT_BUTTON; private String CONTEXT; // ckan client private CkanClient client; // hashmap for ckan api keys private ConcurrentHashMap apiKeysMap; // apikey bean expires after X minutes in the above map private static final int EXPIRE_KEY_TIME = 5 * 60 * 1000; private class CKANTokenBean{ private String apiKey; private long timestamp; public CKANTokenBean(String apiKey, long timestamp) { super(); this.apiKey = apiKey; this.timestamp = timestamp; } } /** * The ckan catalogue url and database will be discovered in this scope * @param scope * @throws Exception if unable to find datacatalogue info */ public DataCatalogueImpl(String scope) throws Exception{ DataCatalogueRunningCluster runningInstance = new DataCatalogueRunningCluster(scope); // save information CKAN_DB_URL = runningInstance.getDatabaseHosts().get(0).trim(); CKAN_DB_NAME = runningInstance.getDataBaseName().trim(); CKAN_DB_USER = runningInstance.getDataBaseUser().trim(); CKAN_DB_PASSWORD = runningInstance.getDataBasePassword().trim(); CKAN_TOKEN_SYS = runningInstance.getSysAdminToken().trim(); CKAN_DB_PORT = runningInstance.getDatabasePorts().get(0); CKAN_CATALOGUE_URL = runningInstance.getDataCatalogueUrl().get(0).trim(); PORTLET_URL_FOR_SCOPE = runningInstance.getPortletUrl().trim(); MANAGE_PRODUCT_BUTTON = runningInstance.isManageProductEnabled(); URI_RESOLVER_URL = runningInstance.getUrlResolver(); logger.debug("Plain sys admin token first 3 chars are " + CKAN_TOKEN_SYS.substring(0, 3)); logger.debug("Plain db password first 3 chars are " + CKAN_DB_PASSWORD.substring(0, 3)); // build the client client = new CkanClient(CKAN_CATALOGUE_URL); // init map apiKeysMap = new ConcurrentHashMap(); // save the context CONTEXT = scope; } @Override public String getCatalogueUrl() { return CKAN_CATALOGUE_URL; } @Override public String getPortletUrl() { return PORTLET_URL_FOR_SCOPE; } @Override public String getUriResolverUrl() { return URI_RESOLVER_URL; } /** * Retrieve connection from the pool * @return a connection available within the pool * @throws SQLException * @throws ClassNotFoundException */ private Connection getConnection() throws SQLException, ClassNotFoundException{ logger.debug("CONNECTION REQUEST"); // create db connection Class.forName("org.postgresql.Driver"); Connection connection = DriverManager.getConnection( "jdbc:postgresql://" + CKAN_DB_URL + ":" + CKAN_DB_PORT + "/" + CKAN_DB_NAME, CKAN_DB_USER, CKAN_DB_PASSWORD); return connection; } /** * Check if the manage product is enabled * @return */ @Override public boolean isManageProductEnabled() { return MANAGE_PRODUCT_BUTTON; } /** * Tries to close a connection * @param connection */ private void closeConnection(Connection connection){ if(connection != null){ try{ connection.close(); }catch(Exception e){ logger.error("Unable to close this connection ", e); } } } @Override public String getApiKeyFromUsername(String username) { logger.debug("Request api key for user = " + username); // checks checkNotNull(username); checkArgument(!username.isEmpty()); // in order to avoid errors, the username is always converted String ckanUsername = UtilMethods.fromUsernameToCKanUsername(username); // check in the hashmap first if(apiKeysMap.containsKey(ckanUsername)){ CKANTokenBean bean = apiKeysMap.get(ckanUsername); if((bean.timestamp + EXPIRE_KEY_TIME) > System.currentTimeMillis()){ // it's still ok return bean.apiKey; } } logger.debug("Api key was not in cache or it expired"); // the connection Connection connection = null; String apiToReturn = null; try{ connection = getConnection(); String query = "SELECT \"apikey\" FROM \"user\" WHERE \"name\"=? and \"state\"=?;"; PreparedStatement preparedStatement = connection.prepareStatement(query); preparedStatement.setString(1, ckanUsername); preparedStatement.setString(2, State.ACTIVE.toString().toLowerCase()); ResultSet rs = preparedStatement.executeQuery(); while (rs.next()) { apiToReturn = rs.getString("apikey"); logger.debug("Api key retrieved for user " + ckanUsername); break; } // save into the hashmap if(apiToReturn != null) apiKeysMap.put(ckanUsername, new CKANTokenBean(apiToReturn, System.currentTimeMillis())); return apiToReturn; }catch(Exception e){ logger.error("Unable to retrieve key for user " + ckanUsername, e); return null; }finally{ closeConnection(connection); } } @Override public CKanUserWrapper getUserFromApiKey(String apiKey) { logger.debug("Request user whose api key is = " + apiKey.substring(0, 3) + "*************"); // checks checkNotNull(apiKey); checkArgument(!apiKey.isEmpty()); CKanUserWrapper user = null; // the connection Connection connection = null; try{ connection = getConnection(); String query = "SELECT * FROM \"user\" WHERE \"apikey\"=? and \"state\"=?;"; PreparedStatement preparedStatement = connection.prepareStatement(query); preparedStatement.setString(1, apiKey); preparedStatement.setString(2, State.ACTIVE.toString().toLowerCase()); ResultSet rs = preparedStatement.executeQuery(); while (rs.next()) { user = new CKanUserWrapper(); user.setId(rs.getString("id")); user.setName(rs.getString("name")); user.setApiKey(apiKey); user.setCreationTimestamp(rs.getTimestamp("created").getTime()); user.setAbout(rs.getString("about")); user.setOpenId(rs.getString("openid")); user.setFullName(rs.getString("fullname")); user.setEmail(rs.getString("email")); user.setAdmin(rs.getBoolean("sysadmin")); logger.debug("User retrieved"); break; } }catch(Exception e){ logger.error("Unable to retrieve user with api key " + apiKey, e); }finally{ closeConnection(connection); } return user; } @Override public List getOrganizationsByUser(String username) { logger.debug("Requested organizations for user " + username); // checks checkNotNull(username); // in order to avoid errors, the username is always converted String ckanUsername = UtilMethods.fromUsernameToCKanUsername(username); // list to return List toReturn = new ArrayList(); try{ // get the list of all organizations List organizations = client.getOrganizationList(); // iterate over them for (CkanOrganization ckanOrganization : organizations) { // get the list of users in it (if you try ckanOrganization.getUsers() it returns null.. maybe a bug TODO) List users = client.getOrganization(ckanOrganization.getName()).getUsers(); // check if the current user is among them for (CkanUser ckanUser : users) { if(ckanUser.getName().equals(ckanUsername)){ logger.debug("User " + ckanUsername + " is into " + ckanOrganization.getName()); toReturn.add(ckanOrganization); break; } } } }catch(Exception e){ logger.error("Unable to get user's organizations", e); } return toReturn; } @Override public List getGroupsByUser(String username) { logger.debug("Requested groups for user " + username); // checks checkNotNull(username); // in order to avoid errors, the username is always converted String ckanUsername = UtilMethods.fromUsernameToCKanUsername(username); // list to return List toReturn = new ArrayList(); try{ // get the list of all organizations List groups = client.getGroupList(); // iterate over them for (CkanGroup ckanGroup : groups) { List users = client.getGroup(ckanGroup.getName()).getUsers(); // check if the current user is among them for (CkanUser ckanUser : users) { if(ckanUser.getName().equals(ckanUsername)){ logger.debug("User " + ckanUsername + " is into " + ckanGroup.getName()); toReturn.add(ckanGroup); break; } } } }catch(Exception e){ logger.error("Unable to get user's groups", e); } return toReturn; } @Override public Map> getOrganizationsAndRolesByUser( String username, List rolesToMatch) { // checks checkNotNull(username); checkArgument(!rolesToMatch.isEmpty()); logger.debug("Requested roles that the user " + username + " has into his organizations"); logger.debug("Roles to check are " + rolesToMatch); Map> toReturn = new HashMap>(); try{ // in order to avoid errors, the username is always converted String ckanUsername = UtilMethods.fromUsernameToCKanUsername(username); // get the list of organizations in which the user is present List organizationsByUser = getOrganizationsByUser(ckanUsername); // iterate over them for (CkanOrganization ckanOrganization : organizationsByUser) { // get the list of users in it (if you try ckanOrganization.getUsers() it returns null.. maybe a bug TODO) List users = client.getOrganization(ckanOrganization.getName()).getUsers(); // check if the current user is among them for (CkanUser ckanUser : users) { if(ckanUser.getName().equals(ckanUsername)){ // the role (admin, editor, member) String capacity = ckanUser.getCapacity(); // list of roles List rolesIntoOrg = new ArrayList(); if(rolesToMatch.contains(RolesCkanGroupOrOrg.convertFromCapacity(capacity))){ RolesCkanGroupOrOrg enumRole = RolesCkanGroupOrOrg.convertFromCapacity(capacity); rolesIntoOrg.add(enumRole); logger.debug("User " + ckanUsername + " has role " + enumRole + " into organization with name " + ckanOrganization.getName()); } // save it if(!rolesIntoOrg.isEmpty()){ String orgName = ckanOrganization.getName(); toReturn.put(orgName, rolesIntoOrg); } break; } } } logger.debug("Result is " + toReturn); return toReturn; }catch(Exception e){ logger.error("Unable to analyze user's roles", e); } return null; } @Override public List getOrganizationsIds(){ List toReturn = new ArrayList(); List orgs = client.getOrganizationList(); for (CkanOrganization ckanOrganization : orgs) { logger.debug("Retrieved org " + ckanOrganization.getName()); toReturn.add(ckanOrganization.getId()); } return toReturn; } @Override public List getOrganizationsNames(){ List toReturn = new ArrayList(); List orgs = client.getOrganizationList(); for (CkanOrganization ckanOrganization : orgs) { logger.debug("Retrieved org " + ckanOrganization.getName()); toReturn.add(ckanOrganization.getName()); } return toReturn; } @Override public List getOrganizationsNamesByUser(String username) { logger.debug("Requested organizations for user " + username); // checks checkNotNull(username); // in order to avoid errors, the username is always converted String ckanUsername = UtilMethods.fromUsernameToCKanUsername(username); List orgs = getOrganizationsByUser(ckanUsername); List orgsName = new ArrayList(); for (CkanOrganization ckanOrganization : orgs) { orgsName.add(ckanOrganization.getName()); logger.debug("Organization name is " + ckanOrganization.getName()); } return orgsName; } @Override public String findLicenseIdByLicenseTitle(String chosenLicense) { logger.debug("Requested license id"); // checks checkNotNull(chosenLicense); //retrieve the list of available licenses List licenses = client.getLicenseList(); for (CkanLicense ckanLicense : licenses) { if(ckanLicense.getTitle().equals(chosenLicense)) return ckanLicense.getId(); } return null; } @Override public List getLicenseTitles() { logger.debug("Request for CKAN licenses"); // get the url and the api key of the user List result = new ArrayList(); //retrieve the list of available licenses List licenses = client.getLicenseList(); for (CkanLicense ckanLicense : licenses) { result.add(ckanLicense.getTitle()); logger.debug("License is " + ckanLicense.getTitle() + " and id " + ckanLicense.getId()); } return result; } @Override public List getLicenses() { logger.debug("Request for CKAN licenses (original jackan objects are going to be retrieved)"); //retrieve the list of available licenses return client.getLicenseList(); } /** * Set dataset private * @param priv * @param organizationId (NOTE: The ID, not the name!) * @param datasetId (NOTE: The ID, not the name!) * @param apiKey the user's api key * @return true on success, false otherwise */ protected boolean setDatasetPrivate(boolean priv, String organizationId, String datasetId, String apiKey) { // checks checkNotNull(organizationId); checkNotNull(apiKey); checkNotNull(datasetId); checkArgument(!apiKey.isEmpty()); checkArgument(!datasetId.isEmpty()); checkArgument(!organizationId.isEmpty()); String pathSetPrivate = "/api/3/action/bulk_update_private"; String pathSetPublic = "/api/3/action/bulk_update_public"; // Request parameters to be replaced String parameter = "{" + "\"org_id\":\"ORGANIZATION_ID\"," + "\"datasets\":[\"DATASET_ID\"]" + "}"; // replace with right data parameter = parameter.replace("ORGANIZATION_ID", organizationId); parameter = parameter.replace("DATASET_ID", datasetId); if(priv){ try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();) { HttpPost request = new HttpPost(CKAN_CATALOGUE_URL + pathSetPrivate); request.addHeader("Authorization", apiKey); StringEntity params = new StringEntity(parameter); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("[PRIVATE]Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) return true; }catch (Exception ex) { logger.error("Error while trying to set private the dataset ", ex); } }else { try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();) { HttpPost request = new HttpPost(CKAN_CATALOGUE_URL + pathSetPublic); StringEntity params = new StringEntity(parameter); request.addHeader("Authorization", apiKey); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("[PUBLIC]Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) return true; }catch (Exception ex) { logger.error("Error while trying to set public the dataset ", ex); } } return false; } @Override public String addResourceToDataset(ResourceBean resourceBean, String apiKey) { logger.debug("Request to add a resource described by this bean " + resourceBean); // checks checkNotNull(resourceBean); checkNotNull(apiKey); checkArgument(!apiKey.isEmpty()); try{ if(UtilMethods.resourceExists(resourceBean.getUrl())){ // in order to avoid errors, the username is always converted String ckanUsername = UtilMethods.fromUsernameToCKanUsername(resourceBean.getOwner()); CkanResource resource = new CkanResource(CKAN_CATALOGUE_URL, resourceBean.getDatasetId()); resource.setName(resourceBean.getName()); // escape description Source description = new Source(resourceBean.getDescription()); Segment htmlSeg = new Segment(description, 0, description.length()); Renderer htmlRend = new Renderer(htmlSeg); resource.setDescription(htmlRend.toString()); resource.setUrl(resourceBean.getUrl()); resource.setOwner(ckanUsername); // Checked client CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); CkanResource createdRes = client.createResource(resource); if(createdRes != null){ logger.debug("Resource " + createdRes.getName() + " is now available"); return createdRes.getId(); } }else logger.error("There is no resource at this url " + resourceBean.getUrl()); }catch(Exception e){ logger.error("Unable to create the resource described by the bean " + resourceBean, e); } return null; } @Override public boolean deleteResourceFromDataset(String resourceId, String apiKey) { logger.error("Request to delete a resource with id " + resourceId + " coming by user with key " + apiKey); // checks checkNotNull(apiKey); checkNotNull(resourceId); checkArgument(!apiKey.isEmpty()); checkArgument(!resourceId.isEmpty()); try{ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); client.deleteResource(resourceId); return true; }catch(Exception e){ logger.error("Unable to delete resource whose id is " + resourceId, e); } return false; } @Override public String createCKanDataset(String apiKey, String title, String name, String organizationNameOrId, String author, String authorMail, String maintainer, String maintainerMail, long version, String description, String licenseId, List tags, Map customFields, List resources, boolean setPublic) { // delegate the private method return createCkanDatasetBody(apiKey, title, name, organizationNameOrId, author, authorMail, maintainer,maintainerMail, version, description, licenseId, tags, customFields, null, resources, setPublic); } @Override public String createCKanDatasetMultipleCustomFields(String apiKey, String title, String name, String organizationNameOrId, String author, String authorMail, String maintainer, String maintainerMail, long version, String description, String licenseId, List tags, Map> customFieldsMultiple, List resources, boolean setPublic) { // delegate the private method return createCkanDatasetBody(apiKey, title, name, organizationNameOrId, author, authorMail, maintainer,maintainerMail, version, description, licenseId, tags, null, customFieldsMultiple, resources, setPublic); } // the body of the actual dataset creation methods private String createCkanDatasetBody(String apiKey, String title, String name, String organizationNameOrId, String author, String authorMail, String maintainer, String maintainerMail, long version, String description, String licenseId, List tags, Map customFields, Map> customFieldsMultipleValues, List resources, boolean setPublic){ // checks (minimum) checkNotNull(apiKey); checkNotNull(organizationNameOrId); checkArgument(!apiKey.isEmpty()); checkArgument(!organizationNameOrId.isEmpty()); checkArgument(!((title == null && name == null) || (title.isEmpty() && name.isEmpty())), "Name and Title cannot be empty/null at the same time!"); logger.debug("Request for dataset creation"); CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); // get client from apiKey String ckanUsername = getUserFromApiKey(apiKey).getName(); // create the base dataset and fill it CkanDataset dataset = new CkanDataset(); // get the name from the title String nameToUse = name; if(nameToUse == null) nameToUse = UtilMethods.fromProductTitleToName(title); logger.debug("Name of the dataset is going to be " + nameToUse + ". Title is going to be " + title); dataset.setName(nameToUse); dataset.setTitle(title); CkanOrganization orgOwner = client.getOrganization(organizationNameOrId); dataset.setOwnerOrg(orgOwner.getId()); dataset.setAuthor(author); dataset.setAuthorEmail(authorMail); dataset.setMaintainer(maintainer); dataset.setMaintainerEmail(maintainerMail); dataset.setVersion(String.valueOf(version)); // description must be escaped if(description != null && !description.isEmpty()){ Source descriptionEscaped = new Source(description); Segment htmlSeg = new Segment(descriptionEscaped, 0, descriptionEscaped.length()); Renderer htmlRend = new Renderer(htmlSeg); dataset.setNotes(htmlRend.toString()); logger.debug("Description (escaped is ) " + htmlRend.toString()); } dataset.setLicenseId(licenseId); // set the tags, if any if(tags != null && !tags.isEmpty()){ // convert to ckan tags List ckanTags = new ArrayList(tags.size()); for (String stringTag : tags) { ckanTags.add(new CkanTag(stringTag)); } dataset.setTags(ckanTags); } // set the custom fields, if any if(customFields != null && !customFields.isEmpty()){ // iterate and create Iterator> iterator = customFields.entrySet().iterator(); List extras = new ArrayList(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); extras.add(new CkanPair(entry.getKey(), entry.getValue())); } dataset.setExtras(extras); }else if(customFieldsMultipleValues != null && !customFieldsMultipleValues.isEmpty()){ // iterate and create Iterator>> iterator = customFieldsMultipleValues.entrySet().iterator(); List extras = new ArrayList(); while (iterator.hasNext()) { Map.Entry> entry = (Map.Entry>) iterator.next(); List valuesForEntry = entry.getValue(); for (String value : valuesForEntry) { extras.add(new CkanPair(entry.getKey(), value)); } } // sort them Collections.sort(extras, new Comparator() { @Override public int compare(CkanPair b1, CkanPair b2) { return b1.getKey().compareTo(b2.getKey()); } }); dataset.setExtras(extras); } // check if we need to add the resources if(resources != null && !resources.isEmpty()){ logger.debug("We need to add resources to the dataset"); try{ List resourcesCkan = new ArrayList(); for(ResourceBean resource: resources){ CkanResource newResource = new CkanResource(); newResource.setDescription(resource.getDescription()); newResource.setId(resource.getId()); newResource.setUrl(resource.getUrl()); newResource.setName(resource.getName()); newResource.setMimetype(resource.getMimeType()); newResource.setOwner(ckanUsername); resourcesCkan.add(newResource); } // add to the dataset dataset.setResources(resourcesCkan); }catch(Exception e){ logger.error("Unable to add those resources to the dataset", e); } } // try to create CkanDataset res = null; try{ res = client.createDataset(dataset); if(res != null){ logger.debug("Dataset with name " + res.getName() + " has been created. Setting visibility"); // set visibility boolean visibilitySet = setDatasetPrivate( !setPublic, // swap to private res.getOrganization().getId(), res.getId(), CKAN_TOKEN_SYS); // use sysadmin api key to be sure it will be set logger.info("Was visibility set to " + (setPublic ? "public" : "private") + "? " + visibilitySet); // set searchable to true if dataset visibility is private if(!setPublic){ // (swap to private) boolean searchableSet = setSearchableField(res.getId(), true); logger.info("Was searchable set to True? " + searchableSet); } return res.getId(); } }catch(Exception e){ // try to update logger.error("Error while creating the dataset.", e); } return null; } @Override public String getUrlFromDatasetIdOrName(String datasetIdOrName) { logger.debug("Request coming for getting dataset url (encrypted) of dataset with name/id " + datasetIdOrName); // checks checkNotNull(datasetIdOrName); checkArgument(!datasetIdOrName.isEmpty()); String url = null; try{ // get the dataset from name CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); CkanDataset dataset = client.getDataset(datasetIdOrName); String name = dataset.getName(); if(dataset != null){ if(getUriResolverUrl() != null) url = getUrlForProduct(CONTEXT, EntityContext.PRODUCT, name, false); if(url == null || url.isEmpty()) url = getPortletUrl() + "?" + URLEncoder.encode("path=/dataset/" + name, "UTF-8"); } }catch(Exception e){ logger.error("Error while retrieving dataset with id/name=" + datasetIdOrName, e); } return url; } @Override public String getUnencryptedUrlFromDatasetIdOrName(String datasetIdOrName) { logger.debug("Request coming for getting dataset url (not encrypted) of dataset with name/id " + datasetIdOrName); // checks checkNotNull(datasetIdOrName); checkArgument(!datasetIdOrName.isEmpty()); String url = null; try{ // get the dataset from name CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); CkanDataset dataset = client.getDataset(datasetIdOrName); String name = dataset.getName(); if(dataset != null){ if(getUriResolverUrl() != null) url = getUrlForProduct(CONTEXT, EntityContext.PRODUCT, name, true); if(url == null || url.isEmpty()) url = getPortletUrl() + "?" + URLEncoder.encode("path=/dataset/" + name, "UTF-8"); } }catch(Exception e){ logger.error("Error while retrieving dataset with id/name=" + datasetIdOrName, e); } return url; } /** * Retrieve an url for the tuple scope, entity, entity name * @param context * @param entityContext * @param entityName * @return the url for the product */ @SuppressWarnings("unchecked") private String getUrlForProduct(String context, EntityContext entityContext, String entityName, boolean unencrypted){ String toReturn = null; try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { HttpPost httpPostRequest = new HttpPost(getUriResolverUrl()); JSONObject requestEntity = new JSONObject(); requestEntity.put("gcube_scope", context); requestEntity.put("entity_context", entityContext.toString()); requestEntity.put("entity_name", entityName); requestEntity.put("clear_url", Boolean.toString(unencrypted)); StringEntity params = new StringEntity(requestEntity.toJSONString(), ContentType.APPLICATION_JSON); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if(response.getStatusLine().getStatusCode() != 200) throw new Exception("There was an error while creating an url " + response.getStatusLine()); toReturn = EntityUtils.toString(response.getEntity()); logger.debug("Result is " + toReturn); }catch(Exception e){ logger.error("Failed to get an url for this product", e); } return toReturn; } @Override public boolean checkRoleIntoOrganization(String username, String organizationName, RolesCkanGroupOrOrg correspondentRoleToCheck) { logger.debug("Request for checking if " + username + " into organization " + organizationName + " has role " + correspondentRoleToCheck); // checks checkNotNull(username); checkNotNull(organizationName); checkNotNull(correspondentRoleToCheck); checkArgument(!username.isEmpty()); checkArgument(!organizationName.isEmpty()); // convert ckan username String ckanUsername = UtilMethods.fromUsernameToCKanUsername(username); // check if this role is already present in ckan for this user within the organization String organizationNameToCheck = organizationName.toLowerCase(); try{ boolean alreadyPresent = isRoleAlreadySet(ckanUsername, organizationNameToCheck, correspondentRoleToCheck, false); if(alreadyPresent) return true; // just return else{ // we need to use the apis to make it String path = "/api/3/action/organization_member_create"; // Request parameters to be replaced String parameter = "{" + "\"id\":\"ORGANIZATION_ID_NAME\"," + "\"username\":\"USERNAME_ID_NAME\"," + "\"role\":\"ROLE\"" + "}"; // replace those values parameter = parameter.replace("ORGANIZATION_ID_NAME", organizationNameToCheck); parameter = parameter.replace("USERNAME_ID_NAME", ckanUsername); parameter = parameter.replace("ROLE", RolesCkanGroupOrOrg.convertToCkanCapacity(correspondentRoleToCheck)); logger.debug("API request for organization membership is going to be " + parameter); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();) { HttpPost request = new HttpPost(CKAN_CATALOGUE_URL + path); request.addHeader("Authorization", CKAN_TOKEN_SYS); // sys token StringEntity params = new StringEntity(parameter); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); return (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK); }catch (Exception ex) { logger.error("Error while trying to change the role for this user ", ex); } } }catch (Exception ex) { logger.error("Unable to check if this role was already set, please check your parameters! ", ex); } return false; } /** * Check if the user has this role into the organization/group with groupOrOrganization name * @param ckanUsername * @param organizationName * @param correspondentRoleToCheck * @return true if he has the role, false otherwise */ protected boolean isRoleAlreadySet(String ckanUsername, String groupOrOrganization, RolesCkanGroupOrOrg correspondentRoleToCheck, boolean group) throws Exception{ // get the users (if you try ckanOrganization.getUsers() it returns null.. maybe a bug TODO) List users; if(group) users = client.getGroup(groupOrOrganization).getUsers(); else users = client.getOrganization(groupOrOrganization).getUsers(); for (CkanUser ckanUser : users) { if(ckanUser.getName().equals(ckanUsername)) if(ckanUser.getCapacity().equals(RolesCkanGroupOrOrg.convertToCkanCapacity(correspondentRoleToCheck))) return true; else break; } return false; } @Override public boolean checkRoleIntoGroup(String username, String groupName, RolesCkanGroupOrOrg correspondentRoleToCheck) { logger.debug("Request for checking if " + username + " into group " + groupName + " has role " + correspondentRoleToCheck); // checks checkNotNull(username); checkNotNull(groupName); checkNotNull(correspondentRoleToCheck); checkArgument(!username.isEmpty()); checkArgument(!groupName.isEmpty()); // convert ckan username String ckanUsername = UtilMethods.fromUsernameToCKanUsername(username); // check if this role is already present in ckan for this user within the group String groupNameToCheck = UtilMethods.fromGroupTitleToName(groupName); try{ boolean alreadyPresent = isRoleAlreadySet(ckanUsername, groupNameToCheck, correspondentRoleToCheck, true); if(alreadyPresent) return true; // just return else{ // we need to use the apis to make it String path = "/api/3/action/group_member_create"; // Request parameters to be replaced String parameter = "{" + "\"id\":\"ORGANIZATION_ID_NAME\"," + "\"username\":\"USERNAME_ID_NAME\"," + "\"role\":\"ROLE\"" + "}"; // replace those values parameter = parameter.replace("ORGANIZATION_ID_NAME", groupNameToCheck); parameter = parameter.replace("USERNAME_ID_NAME", ckanUsername); parameter = parameter.replace("ROLE", RolesCkanGroupOrOrg.convertToCkanCapacity(correspondentRoleToCheck)); logger.debug("API request for organization membership is going to be " + parameter); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();) { HttpPost request = new HttpPost(CKAN_CATALOGUE_URL + path); request.addHeader("Authorization", CKAN_TOKEN_SYS); // sys token StringEntity params = new StringEntity(parameter); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); return (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK); }catch (Exception ex) { logger.error("Error while trying to change the role for this user ", ex); } } }catch (Exception ex) { logger.error("Unable to check if this role was already set, please check your parameters! ", ex); } return false; } @Override public boolean isSysAdmin(String username) { // checks checkNotNull(username); checkArgument(!username.isEmpty()); // in order to avoid errors, the username is always converted String ckanUsername = UtilMethods.fromUsernameToCKanUsername(username); try{ return client.getUser(ckanUsername).isSysadmin(); }catch(Exception e){ logger.error("Failed to check if the user " + username + " has role sysadmin", e); } return false; } @Override public boolean createDatasetRelationship(String datasetIdSubject, String datasetIdObject, DatasetRelationships relation, String relationComment, String apiKey) { // checks checkNotNull(datasetIdSubject); checkNotNull(datasetIdObject); checkNotNull(relation); checkNotNull(apiKey); checkArgument(!datasetIdSubject.isEmpty()); checkArgument(!datasetIdObject.isEmpty()); checkArgument(!apiKey.isEmpty()); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){ // we need to use the apis to make it String path = "/api/3/action/package_relationship_create"; // Request parameters to be replaced String parameter = "{" + "\"subject\":\"SUBJECT\"," + "\"object\":\"OBJECT\"," + "\"type\":\"RELATIONSHIP\"," + "\"comment\" : \"COMMENT\"" + "}"; // replace those values parameter = parameter.replace("SUBJECT", datasetIdSubject); parameter = parameter.replace("OBJECT", datasetIdObject); parameter = parameter.replace("RELATIONSHIP", relation.toString()); if(relationComment != null && !relationComment.isEmpty()) parameter = parameter.replace("COMMENT", relationComment); logger.debug("API request for relationship create is going to be " + parameter); HttpPost request = new HttpPost(CKAN_CATALOGUE_URL + path); request.addHeader("Authorization", apiKey); StringEntity params = new StringEntity(parameter); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); return (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK); }catch(Exception e){ logger.error("Failed to create the relationship between dataset subject = " + datasetIdSubject + " and " + " dataset subject " + datasetIdObject, e); } return false; } @Override public boolean deleteDatasetRelationship(String datasetIdSubject, String datasetIdObject, DatasetRelationships relation, String apiKey) { // checks checkNotNull(datasetIdSubject); checkNotNull(datasetIdObject); checkNotNull(relation); checkNotNull(apiKey); checkArgument(!datasetIdSubject.isEmpty()); checkArgument(!datasetIdObject.isEmpty()); checkArgument(!apiKey.isEmpty()); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){ // we need to use the apis to make it String path = "/api/3/action/package_relationship_delete"; // Request parameters to be replaced String parameter = "{" + "\"subject\":\"SUBJECT\"," + "\"object\":\"OBJECT\"," + "\"type\":\"RELATIONSHIP\"" + "}"; // replace those values parameter = parameter.replace("SUBJECT", datasetIdSubject); parameter = parameter.replace("OBJECT", datasetIdObject); parameter = parameter.replace("RELATIONSHIP", relation.toString()); logger.debug("API request for delete relationship is going to be " + parameter); HttpPost request = new HttpPost(CKAN_CATALOGUE_URL + path); request.addHeader("Authorization", apiKey); StringEntity params = new StringEntity(parameter); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); return (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK); }catch(Exception e){ logger.error("Failed to delete the relationship between dataset subject = " + datasetIdSubject + " and " + " dataset subject " + datasetIdObject, e); } return false; } @SuppressWarnings("unchecked") @Override public List getRelationshipDatasets( String datasetIdSubject, String datasetIdObject, String apiKey) { // checks checkNotNull(datasetIdSubject); checkNotNull(apiKey); checkArgument(!datasetIdSubject.isEmpty()); checkArgument(!apiKey.isEmpty()); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){ List toReturn = new ArrayList(); // we need to use the apis to make it String path = "/api/3/action/package_relationships_list"; String parameter; // Request parameters to be replaced if(datasetIdObject == null || datasetIdObject.isEmpty()) parameter = "{" + "\"id\":\"SUBJECT\"" + "}"; else parameter = "{" + "\"id\":\"SUBJECT\"," + "\"id2\":\"OBJECT\"" + "}"; // replace those values parameter = parameter.replace("SUBJECT", datasetIdSubject); if(datasetIdObject != null && !datasetIdObject.isEmpty()) parameter = parameter.replace("OBJECT", datasetIdObject); logger.debug("API request for getting relationship is going to be " + parameter); HttpPost request = new HttpPost(CKAN_CATALOGUE_URL + path); request.addHeader("Authorization", apiKey); StringEntity params = new StringEntity(parameter); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){ // parse the json and convert to java beans BufferedReader br = new BufferedReader( new InputStreamReader((response.getEntity().getContent()))); String output; String res = ""; while ((output = br.readLine()) != null) { res += output; } if(res == "") return toReturn; // parse the json object returned JSONParser parser = new JSONParser(); JSONObject json = (JSONObject) parser.parse(res); JSONArray resultJson = (JSONArray) json.get("result"); Iterator it = resultJson.iterator(); while (it.hasNext()) { JSONObject object = (JSONObject) it.next(); try{ CkanDatasetRelationship relation = new CkanDatasetRelationship(object); toReturn.add(relation); }catch(Exception e){ logger.error("Error while building CkanRelationship bean from object " + object, e); } } } return toReturn; }catch(Exception e){ logger.error("Failed to retrieve the relationship between dataset subject = " + datasetIdSubject + " and " + " dataset object " + datasetIdObject, e); } return null; } @Override public boolean existProductWithNameOrId(String nameOrId) { // checks checkNotNull(nameOrId); checkArgument(!nameOrId.isEmpty()); try{ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); CkanDataset product = client.getDataset(nameOrId); return product != null; }catch(Exception e){ logger.debug("A dataset with name " + nameOrId + " doesn't exist"); return false; } } @Override public CkanGroup createGroup(String nameOrId, String title, String description) { // checks checkNotNull(nameOrId); checkArgument(!nameOrId.trim().isEmpty()); // check if it exists CkanGroup toCreate = null; CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); logger.debug("Request for creating group with name " + nameOrId + " title " + title + " and description " + description); String name = UtilMethods.fromGroupTitleToName(nameOrId); if((toCreate = groupExists(name, client))!= null) return toCreate; else{ try{ CkanGroup group = new CkanGroup(name); group.setTitle(title); group.setDisplayName(title); group.setDescription(description); toCreate = client.createGroup(group); }catch(JackanException je){ logger.error("Unable to create such a group", je); } } return toCreate; } /** * Just check if the group exists * @param nameOrId * @param client * @return */ private CkanGroup groupExists(String nameOrId, CheckedCkanClient client){ CkanGroup toReturn = null; try{ toReturn = client.getGroup(nameOrId); }catch(JackanException je){ logger.error("This group doesn't exist"); } return toReturn; } @SuppressWarnings("unchecked") @Override public boolean assignDatasetToGroup(String groupNameOrId, String datasetNameOrId, String apiKey) { // checks checkNotNull(groupNameOrId); checkArgument(!groupNameOrId.isEmpty()); checkNotNull(datasetNameOrId); checkArgument(!datasetNameOrId.isEmpty()); checkNotNull(apiKey); checkArgument(!apiKey.isEmpty()); String groupNameToCheck = UtilMethods.fromGroupTitleToName(groupNameOrId); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); // check the group exists CkanGroup group = client.getGroup(groupNameToCheck); // we need to use the apis to make it String pathPackageShow = CKAN_CATALOGUE_URL + "/api/3/action/package_show?id=" + datasetNameOrId; HttpGet getRequest = new HttpGet(pathPackageShow); getRequest.addHeader("Authorization", CKAN_TOKEN_SYS); HttpResponse response = httpClient.execute(getRequest); List fetchedGroups = new ArrayList(); logger.debug("Response is " + response.getStatusLine().getStatusCode() + " and message is " + response.getStatusLine().getReasonPhrase()); // read the json dataset and fetch the groups and fetch the groups' names, if any if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){ // parse the json and convert to java beans String jsonAsString = EntityUtils.toString(response.getEntity()); JSONParser parser = new JSONParser(); JSONObject json = (JSONObject) parser.parse(jsonAsString); JSONObject resultJson = (JSONObject) json.get("result"); JSONArray groupsJson = (JSONArray)resultJson.get("groups"); Iterator it = groupsJson.iterator(); while (it.hasNext()) { JSONObject object = (JSONObject) it.next(); try{ if(object.containsKey("name")) fetchedGroups.add((String)object.get("name")); }catch(Exception e){ logger.error("Error while building CkanRelationship bean from object " + object, e); } } // add the new one if(!fetchedGroups.contains(group.getName())) fetchedGroups.add(group.getName()); logger.debug("Groups to be added are " + fetchedGroups); // now we patch the dataset with the new group String pathUpdatePatch = CKAN_CATALOGUE_URL + "/api/3/action/package_patch"; String parameterPostPatch = "{\"id\":\"PACKAGE_ID\", \"groups\":[GROUPS]}"; parameterPostPatch = parameterPostPatch.replace("PACKAGE_ID", datasetNameOrId); String singleGroup = "{\"name\":\"GROUP_ID\"}"; // evaluate parameterPostPatch String replaceGROUPS = ""; for (int i = 0; i < fetchedGroups.size(); i++) { replaceGROUPS += singleGroup.replace("GROUP_ID", fetchedGroups.get(i)); if(i != fetchedGroups.size() - 1) replaceGROUPS += ","; } // replace this into parameterPostPatch parameterPostPatch = parameterPostPatch.replace("GROUPS", replaceGROUPS); logger.debug("Request for patch is going to be " + parameterPostPatch); HttpPost request = new HttpPost(pathUpdatePatch); request.addHeader("Authorization", CKAN_TOKEN_SYS); StringEntity params = new StringEntity(parameterPostPatch); request.setEntity(params); HttpResponse responsePatch = httpClient.execute(request); logger.debug("Response code is " + responsePatch.getStatusLine().getStatusCode() + " and response message is " + responsePatch.getStatusLine().getReasonPhrase()); if(responsePatch.getStatusLine().getStatusCode() == HttpStatus.SC_OK){ logger.info("Dataset Added to the group!!"); return true; } } }catch(Exception e){ logger.error("Unable to make this association", e); } return false; } @SuppressWarnings("unchecked") @Override public boolean removeDatasetFromGroup(String groupNameOrId, String datasetNameOrId, String apiKey) { // checks checkNotNull(groupNameOrId); checkNotNull(datasetNameOrId); checkNotNull(apiKey); try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String apiRequestUrl = CKAN_CATALOGUE_URL + "/api/3/action/package_patch"; HttpPost httpPostRequest = new HttpPost(apiRequestUrl); httpPostRequest.setHeader("Authorization", CKAN_TOKEN_SYS); // Request parameters to be replaced JSONObject jsonRequest = new JSONObject(); JSONArray groupsAsJson = new JSONArray(); CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); CkanDataset dataset = client.getDataset(datasetNameOrId); List currentGroups = dataset.getGroups(); // build the json array for the "groups" field.. each object looks like // { // // "display_name": "Fishery activity", // "description": "", // "image_display_url": "", // "title": "Fishery activity", // "id": "20b8d23c-d2dd-4613-9e1e-856311862e87", // "name": "fishery-activity" // // } // get the id of the group to be removed String groupId = client.getGroup(groupNameOrId).getId(); for(CkanGroup ckanGroup : currentGroups){ if(!ckanGroup.getId().equals(groupId)){ JSONObject obj = new JSONObject(); obj.put("display_name", ckanGroup.getDisplayName()); obj.put("description", ckanGroup.getDescription()); obj.put("image_display_url", ckanGroup.getImageDisplayUrl()); obj.put("title", ckanGroup.getTitle()); obj.put("id", ckanGroup.getId()); obj.put("name", ckanGroup.getName()); groupsAsJson.add(obj); } } // perform the request jsonRequest.put("id", datasetNameOrId); jsonRequest.put("groups", groupsAsJson); logger.debug("Request param is going to be " + jsonRequest); StringEntity params = new StringEntity(jsonRequest.toJSONString(), ContentType.APPLICATION_JSON); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to patch the product. response status line from " + apiRequestUrl + " was: " + response.getStatusLine()); } return true; }catch(Exception e){ logger.error("Failed to remove the group " + groupNameOrId + " from product " + datasetNameOrId, e); } return false; } @Override public Map> getRolesAndUsersOrganization(String organizationName) { // checks checkNotNull(organizationName); checkArgument(!organizationName.isEmpty()); Map> capacityAndUsers = new HashMap>(); CkanOrganization org = client.getOrganization(organizationName); List users = org.getUsers(); for (CkanUser ckanUser : users) { logger.debug(ckanUser.getName()); List listUsers; if(capacityAndUsers.containsKey(ckanUser.getCapacity())){ listUsers = capacityAndUsers.get(ckanUser.getCapacity()); }else listUsers = new ArrayList(); listUsers.add(ckanUser.getName()); capacityAndUsers.put(ckanUser.getCapacity(), listUsers); } return capacityAndUsers; } @Override public Map> getRolesAndUsersGroup(String groupName) { // checks checkNotNull(groupName); checkArgument(!groupName.isEmpty()); Map> capacityAndUsers = null; String groupNameToCheck = UtilMethods.fromGroupTitleToName(groupName); CkanGroup group = client.getGroup(groupNameToCheck); if(group != null){ capacityAndUsers = new HashMap>(); List users = group.getUsers(); for (CkanUser ckanUser : users) { List listUsers; if(capacityAndUsers.containsKey(RolesCkanGroupOrOrg.convertFromCapacity(ckanUser.getCapacity()))){ listUsers = capacityAndUsers.get(RolesCkanGroupOrOrg.convertFromCapacity(ckanUser.getCapacity())); }else listUsers = new ArrayList(); listUsers.add(ckanUser.getName()); capacityAndUsers.put(RolesCkanGroupOrOrg.convertFromCapacity(ckanUser.getCapacity()), listUsers); } logger.info("Returning " + capacityAndUsers); } return capacityAndUsers; } @Override public String getRoleOfUserInOrganization(String username, String orgName, String apiKey) { String toReturn = null; String usernameCkan = UtilMethods.fromUsernameToCKanUsername(username); try{ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); List users = client.getOrganization(orgName).getUsers(); for (CkanUser ckanUser : users) { if(ckanUser.getName().equals(usernameCkan)){ toReturn = ckanUser.getCapacity(); break; } } }catch(Exception e){ logger.error("Unable to retrieve the role the user has into this organization", e); } return toReturn; } @Override public String getRoleOfUserInGroup(String username, String groupName, String apiKey) { String toReturn = null; String usernameCkan = UtilMethods.fromUsernameToCKanUsername(username); try{ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); List users = client.getGroup(groupName).getUsers(); for (CkanUser ckanUser : users) { if(ckanUser.getName().equals(usernameCkan)){ toReturn = ckanUser.getCapacity(); break; } } }catch(Exception e){ logger.error("Unable to retrieve the role the user has into this group", e); } return toReturn; } @Override public boolean deleteProduct(String datasetId, String apiKey, boolean purge) { // checks checkNotNull(datasetId); checkArgument(!datasetId.isEmpty()); checkNotNull(apiKey); checkArgument(!apiKey.isEmpty()); logger.debug("Incoming request of deleting dataset with id " + datasetId); try{ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); client.deleteDataset(datasetId); logger.info("Dataset with id " + datasetId + " deleted!"); if(purge){ logger.debug("Purging also ...."); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){ String path = CKAN_CATALOGUE_URL + "/api/3/action/dataset_purge"; HttpPost request = new HttpPost(path); request.addHeader("Authorization", CKAN_TOKEN_SYS); // this must be a sys_admin key String entityBody = "{\"id\": \"" + datasetId + "\"}"; StringEntity params = new StringEntity(entityBody); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); if(response.getStatusLine().getStatusCode() == 200){ logger.info("Dataset with id " + datasetId + " delete and purged!"); return true; } return false; } } return true; }catch(Exception e){ logger.error("Unable to delete such dataset ", e); } return false; } @Override public CkanDataset getDataset(String datasetId, String apiKey) { logger.info("Request ckan dataset with id " + datasetId); // checks checkNotNull(datasetId); checkArgument(!datasetId.isEmpty()); checkNotNull(apiKey); checkArgument(!apiKey.isEmpty()); try{ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); return client.getDataset(datasetId); }catch(Exception e){ logger.error("Unable to retrieve such dataset, returning null ...", e); } return null; } @Override public boolean setSearchableField(String datasetId, boolean searchable) { // checks checkNotNull(datasetId); checkArgument(!datasetId.isEmpty()); String searchableAsString = searchable ? "True" : "False"; // Patch package path String patchPackage = CKAN_CATALOGUE_URL + "/api/3/action/package_patch"; // Request parameters to be replaced String parameter = "{" + "\"id\":\"DATASET_ID\"," + "\"searchable\":\"SEARCHABLE\"" + "}"; // replace with right data parameter = parameter.replace("DATASET_ID", datasetId); parameter = parameter.replace("SEARCHABLE", searchableAsString); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();) { HttpPost request = new HttpPost(patchPackage); request.addHeader("Authorization", CKAN_TOKEN_SYS); StringEntity params = new StringEntity(parameter); request.setEntity(params); HttpResponse response = httpClient.execute(request); logger.debug("Response code is " + response.getStatusLine().getStatusCode() + " and response message is " + response.getStatusLine().getReasonPhrase()); if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) return true; }catch (Exception ex) { logger.error("Error while trying to set searchable the dataset ", ex); } return false; } @Override public List getGroups() { try{ CkanClient client = new CkanClient(CKAN_CATALOGUE_URL); return client.getGroupList(); }catch(Exception e){ logger.error("Failed to retrieve the list groups", e); return null; } } @Override public CkanResource uploadResourceFile(File file, String packageId, String token, String name, String description) { // checks checkNotNull(file); checkNotNull(packageId); checkNotNull(token); checkNotNull(name); String returnedId = null; try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String apiRequestUrl = CKAN_CATALOGUE_URL + "/api/3/action/resource_create"; HttpPost httpPostRequest = new HttpPost(apiRequestUrl); httpPostRequest.setHeader("Authorization", token); HttpEntity mpEntity = MultipartEntityBuilder.create() .addTextBody("package_id", packageId, ContentType.TEXT_PLAIN) .addTextBody("url", "upload", ContentType.TEXT_PLAIN) .addTextBody("description", description == null ? "" : description, ContentType.TEXT_PLAIN) .addTextBody("name", name, ContentType.TEXT_PLAIN) .addBinaryBody("upload", file, ContentType.create( "application/octet-stream", Charset.forName("UTF-8")), name) .build(); httpPostRequest.setEntity(mpEntity); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to add the file to CKAN storage. response status line from " + apiRequestUrl + " was: " + response.getStatusLine()); } logger.info("Returned message is " + response.getStatusLine()); String json = EntityUtils.toString(response.getEntity()); Object obj = JSONValue.parse(json); JSONObject finalResult=(JSONObject)obj; JSONObject result = (JSONObject)finalResult.get("result"); logger.debug("Returned json is " + result.get("id")); returnedId = (String) result.get("id"); return new CheckedCkanClient(CKAN_CATALOGUE_URL, token).getResource(returnedId); } catch (Exception e) { logger.error("Error while uploading file"); return null; } } @SuppressWarnings("unchecked") @Override public boolean patchResource(String resourceId, String url, String name, String description, String urlType, String apiKey) { // checks checkNotNull(resourceId); checkNotNull(apiKey); logger.debug("Going to change resource with id " + resourceId +"." + " Request comes from user with key " + apiKey.substring(0, 5) + "****************"); try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String apiRequestUrl = CKAN_CATALOGUE_URL + "/api/3/action/resource_patch"; HttpPost httpPostRequest = new HttpPost(apiRequestUrl); httpPostRequest.setHeader("Authorization", apiKey); // Request parameters to be replaced JSONObject jsonRequest = new JSONObject(); Map requestMap = new HashMap(); requestMap.put("id", resourceId); if(url != null && !url.isEmpty()) requestMap.put("url", url); if(description != null) requestMap.put("description", description); if(name != null && !name.isEmpty()) requestMap.put("name", name); if(urlType != null) requestMap.put("url_type", urlType); jsonRequest.putAll(requestMap); StringEntity params = new StringEntity(jsonRequest.toJSONString()); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to patch the resource. response status line from " + apiRequestUrl + " was: " + response.getStatusLine()); } return true; }catch(Exception e){ logger.error("Failed to update the resource ", e); } return false; } @SuppressWarnings("unchecked") @Override public boolean patchProductCustomFields(String productId, String apiKey, Map> customFieldsToChange) { // checks checkNotNull(productId); checkNotNull(apiKey); if(customFieldsToChange == null || customFieldsToChange.isEmpty()) // TODO.. remove all custom fields maybe?! return true; logger.info("Going to change product with id " + productId +"." + " Request comes from user with key " + apiKey.substring(0, 5) + "****************"); logger.info("The new values are " + customFieldsToChange); // Get already available custom fields Map> fromCKANCustomFields = new HashMap>(); CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); List extras = client.getDataset(productId).getExtras(); if(extras == null) extras = new ArrayList(); // fill the above map with these values for (CkanPair ckanPair : extras) { List forThisValue = null; String key = ckanPair.getKey(); if(fromCKANCustomFields.containsKey(key)) forThisValue = fromCKANCustomFields.get(key); else forThisValue = new ArrayList(); forThisValue.add(ckanPair.getValue()); fromCKANCustomFields.put(key, forThisValue); } logger.info("The generated map from jackan looks like " + fromCKANCustomFields + ". Going to merge them"); // merge them with the new values Iterator>> iteratorUserMap = customFieldsToChange.entrySet().iterator(); while (iteratorUserMap.hasNext()) { Map.Entry> entry = (Map.Entry>) iteratorUserMap .next(); String key = entry.getKey(); List newValues = entry.getValue(); // get the unique set of values Set uniqueValues = new HashSet(); if(fromCKANCustomFields.containsKey(key)) uniqueValues.addAll(fromCKANCustomFields.get(key)); uniqueValues.addAll(newValues); fromCKANCustomFields.put(key, new ArrayList(uniqueValues)); } logger.info("After merging it is " + fromCKANCustomFields); try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String apiRequestUrl = CKAN_CATALOGUE_URL + "/api/3/action/package_patch"; HttpPost httpPostRequest = new HttpPost(apiRequestUrl); httpPostRequest.setHeader("Authorization", apiKey); // Request parameters to be replaced JSONObject jsonRequest = new JSONObject(); // build the json array for the "extras" field.. each object looks like {"key": ..., "value": ...} JSONArray extrasObject = new JSONArray(); Iterator>> iteratorNewFields = fromCKANCustomFields.entrySet().iterator(); while (iteratorNewFields.hasNext()) { Map.Entry> entry = (Map.Entry>) iteratorNewFields .next(); String key = entry.getKey(); List values = entry.getValue(); for (String value : values) { JSONObject obj = new JSONObject(); obj.put("value", value); obj.put("key", key); extrasObject.add(obj); } } // perform the request jsonRequest.put("id", productId); jsonRequest.put("extras", extrasObject); logger.debug("Request param is going to be " + jsonRequest); StringEntity params = new StringEntity(jsonRequest.toJSONString(), ContentType.APPLICATION_JSON); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to patch the product. response status line from " + apiRequestUrl + " was: " + response.getStatusLine()); } return true; }catch(Exception e){ logger.error("Failed to patch the product ", e); } return false; } @SuppressWarnings("unchecked") @Override public boolean removeCustomField(String productId, String key, String value, String apiKey) { // checks checkNotNull(productId); checkNotNull(apiKey); checkNotNull(value); checkNotNull(key); logger.info("Going to change product with id " + productId +"." + " Request comes from user with key " + apiKey.substring(0, 5) + "****************"); logger.info("The couple key/value to remove from custom fields is [" + key + "," + value +"]"); CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); List extras = client.getDataset(productId).getExtras(); Iterator iterator = extras.iterator(); while (iterator.hasNext()) { CkanPair ckanPair = (CkanPair) iterator.next(); if(ckanPair.getKey().equals(key) && ckanPair.getValue().equals(value)){ logger.info("Removed from the ckan pairs list"); iterator.remove(); break; } } try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String apiRequestUrl = CKAN_CATALOGUE_URL + "/api/3/action/package_patch"; HttpPost httpPostRequest = new HttpPost(apiRequestUrl); httpPostRequest.setHeader("Authorization", apiKey); // Request parameters to be replaced JSONObject jsonRequest = new JSONObject(); // build the json array for the "extras" field.. each object looks like {"key": ..., "value": ...} JSONArray extrasObject = new JSONArray(); for(CkanPair extra: extras){ JSONObject obj = new JSONObject(); obj.put("value", extra.getValue()); obj.put("key", extra.getKey()); extrasObject.add(obj); } // perform the request jsonRequest.put("id", productId); jsonRequest.put("extras", extrasObject); logger.debug("Request param is going to be " + jsonRequest); StringEntity params = new StringEntity(jsonRequest.toJSONString(), ContentType.APPLICATION_JSON); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to patch the product. response status line from " + apiRequestUrl + " was: " + response.getStatusLine()); } return true; }catch(Exception e){ logger.error("Failed to remove the custom field for this product ", e); } return false; } @SuppressWarnings("unchecked") @Override public boolean removeTag(String productId, String apiKey, String tagToRemove) { // checks checkNotNull(productId); checkNotNull(apiKey); checkNotNull(tagToRemove); try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String apiRequestUrl = CKAN_CATALOGUE_URL + "/api/3/action/package_patch"; HttpPost httpPostRequest = new HttpPost(apiRequestUrl); httpPostRequest.setHeader("Authorization", apiKey); // Request parameters to be replaced JSONObject jsonRequest = new JSONObject(); JSONArray tagsAsJson = new JSONArray(); CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); CkanDataset dataset = client.getDataset(productId); List currentTags = dataset.getTags(); // build the json array for the "tags" field.. each object looks like // { // // "vocabulary_id": null, // "state": "active", // "display_name": "....", // "id": "....", // "name": "..." // // } for(CkanTag ckanTag : currentTags){ if(!ckanTag.getName().equals(tagToRemove)){ JSONObject obj = new JSONObject(); obj.put("vocabulary_id", ckanTag.getVocabularyId()); obj.put("state", ckanTag.getState().toString()); obj.put("display_name", ckanTag.getDisplayName()); obj.put("id", ckanTag.getId()); obj.put("name", ckanTag.getName()); tagsAsJson.add(obj); } } // perform the request jsonRequest.put("id", productId); jsonRequest.put("tags", tagsAsJson); logger.debug("Request param is going to be " + jsonRequest); StringEntity params = new StringEntity(jsonRequest.toJSONString(), ContentType.APPLICATION_JSON); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to patch the product. response status line from " + apiRequestUrl + " was: " + response.getStatusLine()); } return true; }catch(Exception e){ logger.error("Failed to remove the tag " + tagToRemove, e); } return false; } @Override public boolean addTag(String productId, String apiKey, String tagToAdd) { // checks checkNotNull(productId); checkNotNull(apiKey); checkNotNull(tagToAdd); try{ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); CkanDataset dataset = client.getDataset(productId); List currentTags = dataset.getTags(); Iterator tagsIterator = currentTags.iterator(); boolean added = true; // check if it is already there ... while (tagsIterator.hasNext()) { CkanTag ckanTag = (CkanTag) tagsIterator.next(); if(ckanTag.getName().equals(tagToAdd)){ added = false; break; } } if(added){ currentTags.add(new CkanTag(tagToAdd)); dataset.setTags(currentTags); client.patchUpdateDataset(dataset); } return true; }catch(Exception e){ logger.error("Failed to add the tag " + tagToAdd, e); } return false; } @Override public List getParentGroups(String groupName, String apiKey) { // checks checkNotNull(groupName); checkNotNull(apiKey); try{ CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, apiKey); return client.getGroup(groupName).getGroups(); }catch(Exception e){ logger.error("Something went wrong, returning null", e); } return null; } @SuppressWarnings("unchecked") @Override public boolean setGroupParent(String parentName, String groupName) { // checks checkNotNull(parentName); checkNotNull(groupName); try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { String apiRequestUrl = CKAN_CATALOGUE_URL + "/api/3/action/group_patch"; HttpPost httpPostRequest = new HttpPost(apiRequestUrl); httpPostRequest.setHeader("Authorization", CKAN_TOKEN_SYS); CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); List alreadyAvailableParents = client.getGroup(groupName).getGroups(); Set parentsNames = new HashSet(); parentsNames.add(parentName); for (CkanGroup alreadyAvailableParent : alreadyAvailableParents) { parentsNames.add(alreadyAvailableParent.getName()); } logger.info("Setting as parents of group " + groupName + " :" + parentsNames); // Request parameters to be replaced JSONObject jsonRequest = new JSONObject(); JSONArray parentsAsJson = new JSONArray(); for(String parent : parentsNames){ JSONObject obj = new JSONObject(); obj.put("name", parent); parentsAsJson.add(obj); } // perform the request jsonRequest.put("id", groupName); jsonRequest.put("groups", parentsAsJson); logger.debug("Request param is going to be " + jsonRequest); StringEntity params = new StringEntity(jsonRequest.toJSONString(), ContentType.APPLICATION_JSON); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to patch the group. response status line from " + apiRequestUrl + " was: " + response.getStatusLine()); } return true; }catch(Exception e){ logger.error("Failed to set parents groups", e); } return false; } @Override public boolean isDatasetInGroup(String groupName, String datasetId) { // checks checkNotNull(datasetId); checkNotNull(groupName); CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); List groups = client.getDataset(datasetId).getGroups(); for (CkanGroup ckanGroup : groups) { if(ckanGroup.getName().equals(groupName)) return true; } return false; } @Override public List getProductsInGroup(String groupName) { checkNotNull(groupName); List toReturn = null; try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { CheckedCkanClient client = new CheckedCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); String apiRequestUrl = CKAN_CATALOGUE_URL + "/api/3/action/group_show" + "?id=" + groupName + "&include_datasets=true"; HttpGet httpGetRequest = new HttpGet(apiRequestUrl); httpGetRequest.setHeader("Authorization", CKAN_TOKEN_SYS); HttpResponse response = httpClient.execute(httpGetRequest); String jsonString = EntityUtils.toString(response.getEntity()); JSONParser parser = new JSONParser(); JSONObject parsedJson = (JSONObject)parser.parse(jsonString); logger.debug("JSONObject looks like " + parsedJson); // get "packages" array toReturn = new ArrayList(); JSONObject result = (JSONObject)parsedJson.get("result"); JSONArray packages = (JSONArray)result.get("packages"); logger.debug("Packages looks like " + packages); for (int i = 0, size = packages.size(); i < size; i++){ JSONObject objectInArray = (JSONObject)packages.get(i); String packageId = (String)objectInArray.get("id"); toReturn.add(client.getDataset(packageId)); } }catch(Exception e){ logger.error("Failed to get groups information", e); } return toReturn; } @Override public String patchProductWithJSON(String productId, JSONObject jsonRequest, String apiKey) { checkNotNull(productId); checkNotNull(jsonRequest); checkNotNull(apiKey); logger.info("Request of patching product " + productId + " with json " + jsonRequest); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){ String pathUpdatePatch = getCatalogueUrl() + "/api/3/action/package_patch"; HttpPost httpPostRequest = new HttpPost(pathUpdatePatch); httpPostRequest.setHeader("Authorization", CKAN_TOKEN_SYS); StringEntity params = new StringEntity(jsonRequest.toJSONString(), ContentType.APPLICATION_JSON); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to patch the product. response status line from " + pathUpdatePatch + " was: " + response.getStatusLine()); } return null; }catch(Exception e){ logger.error("Error while trying to patch grsf record " + e.getMessage()); return e.getMessage(); } } @Override public List getProductsIdsInGroupOrOrg(String orgOrGroupName, boolean isOrganization, int start, int rows) throws ClassNotFoundException, SQLException { List toReturn = new ArrayList(); checkNotNull(orgOrGroupName); checkArgument(start >= 0); checkArgument(rows >= 0); Connection connection = getConnection(); try{ ResultSet rs; if(isOrganization){ String joinQuery = "SELECT \"package\".\"id\" AS \"dataset_id\" FROM \"package\" INNER JOIN \"group\" ON" + " \"package\".\"owner_org\"=\"group\".\"id\" WHERE \"group\".\"name\"=? " + "AND \"group\".\"is_organization\"=? AND \"package\".\"type\"=? AND \"package\".\"state\"=? LIMIT ? OFFSET ?; "; PreparedStatement preparedStatement = connection.prepareStatement(joinQuery); preparedStatement.setString(1, orgOrGroupName); preparedStatement.setBoolean(2, isOrganization); preparedStatement.setString(3, "dataset"); preparedStatement.setString(4, "active"); preparedStatement.setBigDecimal(5, new BigDecimal(rows)); preparedStatement.setBigDecimal(6, new BigDecimal(start)); rs = preparedStatement.executeQuery(); }else{ /** * Inner join between the member table and the package table. * Basically every time a dataset is added to a group, a new row is added to the table * where table_id is the package_id, and group_id is the owner group identifier (not the name) */ String groupId = client.getGroup(orgOrGroupName).getId(); String joinQuery = "SELECT \"table_id\" AS \"dataset_id\" FROM \"package\" INNER JOIN \"member\" ON" + " \"member\".\"table_id\"=\"package\".\"id\" WHERE \"group_id\"=? " + "AND \"member\".\"state\"=? LIMIT ? OFFSET ?;"; PreparedStatement preparedStatement = connection.prepareStatement(joinQuery); preparedStatement.setString(1, groupId); preparedStatement.setString(2, "active"); preparedStatement.setBigDecimal(3, new BigDecimal(rows)); preparedStatement.setBigDecimal(4, new BigDecimal(start)); rs = preparedStatement.executeQuery(); } while (rs.next()) { toReturn.add(rs.getString("dataset_id")); } }catch(Exception e){ logger.error("Failed to retrieve the ids of products in group/org. Error is " + e.getMessage()); return null; }finally{ closeConnection(connection); } return toReturn; } @Override public boolean deleteGroup(String groupName, boolean purge) { checkNotNull(groupName); logger.info("Request of deleting group " + groupName + ". Purge is " + Boolean.toString(purge)); try(CloseableHttpClient httpClient = HttpClientBuilder.create().build();){ String deletePath = getCatalogueUrl() + "/api/3/action/group_delete"; String purgePath = getCatalogueUrl() + "/api/3/action/group_purge"; String requestToPerform = purge ? purgePath : deletePath; HttpPost httpPostRequest = new HttpPost(requestToPerform); httpPostRequest.setHeader("Authorization", CKAN_TOKEN_SYS); JSONObject obj = new JSONObject(); obj.put("id", groupName); StringEntity params = new StringEntity(obj.toJSONString(), ContentType.APPLICATION_JSON); httpPostRequest.setEntity(params); HttpResponse response = httpClient.execute(httpPostRequest); if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { throw new RuntimeException("failed to delete/purge the group. response status line from " + requestToPerform + " was: " + response.getStatusLine()); } return true; }catch(Exception e){ logger.error("Error while trying to delete/purge the group " + e.getMessage()); return false; } } }