package org.gcube.data_catalogue.grsf_publish_ws.utils; import java.beans.PropertyDescriptor; import java.io.BufferedReader; import java.io.File; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.gcube.common.homelibrary.home.exceptions.InternalErrorException; import org.gcube.common.homelibrary.home.workspace.WorkspaceFolder; import org.gcube.common.homelibrary.home.workspace.WorkspaceItem; import org.gcube.common.homelibrary.home.workspace.exceptions.InsufficientPrivilegesException; import org.gcube.common.homelibrary.home.workspace.exceptions.ItemAlreadyExistException; import org.gcube.common.homelibrary.home.workspace.folder.items.ExternalFile; import org.gcube.data_catalogue.grsf_publish_ws.custom_annotations.CkanResource; import org.gcube.data_catalogue.grsf_publish_ws.custom_annotations.CustomField; import org.gcube.data_catalogue.grsf_publish_ws.custom_annotations.Group; import org.gcube.data_catalogue.grsf_publish_ws.custom_annotations.Tag; import org.gcube.data_catalogue.grsf_publish_ws.json.input.Base; import org.gcube.data_catalogue.grsf_publish_ws.json.input.Resource; import org.gcube.data_catalogue.grsf_publish_ws.json.input.TimeSeriesBean; import org.gcube.datacatalogue.ckanutillibrary.DataCatalogue; import org.gcube.datacatalogue.ckanutillibrary.DataCatalogueFactory; import org.gcube.datacatalogue.ckanutillibrary.DataCatalogueImpl; import org.gcube.datacatalogue.ckanutillibrary.models.ResourceBean; import org.jsoup.Jsoup; import org.jsoup.safety.Whitelist; import org.slf4j.LoggerFactory; import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpResponse; import eu.trentorise.opendata.jackan.internal.org.apache.http.client.methods.HttpGet; 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.model.CkanLicense; /** * Helper methods * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) */ @SuppressWarnings({"unchecked","rawtypes"}) public abstract class HelperMethods { private static final org.slf4j.Logger logger = LoggerFactory.getLogger(HelperMethods.class); // to be retrieved from the web.xml public static final String PENDING_CONTEX_KEY = "PendingContext"; public static final String CONFIRMED_CONTEX_KEY = "ConfirmedContext"; private static final int TIME_SERIES_TAKE_LAST_VALUES = 5; private static final String CSV_MIME = "text/csv"; private static final String PATH_SEPARATOR = "/"; private static final String REGEX_TAGS = "[^\\s\\w-_.]"; /** * Convert a group name to its id on ckan * @param origName * @return */ public static String getGroupNameOnCkan(String origName){ if(origName == null) throw new IllegalArgumentException("origName cannot be null"); String modified = origName.trim().toLowerCase().replaceAll("[^A-Za-z0-9-]", "-"); if(modified.startsWith("-")) modified = modified.substring(1); if(modified.endsWith("-")) modified = modified.substring(0, modified.length() -1); logger.info("Group name generated is " + modified); return modified; } /** * Retrieve the running instance of the data catalogue for this scope * @return * @throws Exception */ public static DataCatalogue getDataCatalogueRunningInstance(String scope){ try{ DataCatalogueImpl instance = DataCatalogueFactory.getFactory().getUtilsPerScope(scope); return instance; }catch(Exception e){ logger.error("Failed to instanciate data catalogue lib", e); } return null; } /** * Retrieve the list of tags for this object */ public static void getTags(Set tags, Base record){ Class current = record.getClass(); do{ Field[] fields = current.getDeclaredFields(); for (Field field : fields) { if(field.isAnnotationPresent(Tag.class)){ try{ Object f = new PropertyDescriptor(field.getName(), current).getReadMethod().invoke(record); if(f != null){ if(f instanceof List){ List asList = ((List) f); if(!asList.isEmpty()){ logger.debug("The object annotated with @Tag is a list. Adding ... "); int elementsToConsider = asList.size(); // check if it is a time series, in this take the last X elements if(asList.get(0).getClass().equals(TimeSeriesBean.class)){ elementsToConsider = Math.min(elementsToConsider, TIME_SERIES_TAKE_LAST_VALUES); for (int i = (asList.size() - elementsToConsider); i < asList.size(); i++) { String finalTag = asList.get(i).toString().trim().replaceAll(REGEX_TAGS, ""); tags.add(finalTag); } }else{ // else add all the available elements for (int i = 0; i < elementsToConsider; i++) { String finalTag = asList.get(i).toString().trim().replaceAll(REGEX_TAGS, ""); tags.add(finalTag); } } } }else{ logger.debug("The object annotated with @Tag is a simple one. Adding ... "); String finalTag = f.toString().trim().replaceAll(REGEX_TAGS, ""); logger.debug(finalTag); tags.add(finalTag); } } }catch(Exception e){ logger.error("Failed ot read value for field " + field.getName() + " skipping", e); } } } } while((current = current.getSuperclass())!=null); // start from the inherited class up to the Object.class logger.info("Tags are " + tags); } /** * Retrieve the list of groups' names for this object */ public static void getGroups(Set groups, Base record){ Class current = record.getClass(); do{ Field[] fields = current.getDeclaredFields(); for (Field field : fields) { if(field.isAnnotationPresent(Group.class)){ try{ Object f = new PropertyDescriptor(field.getName(), current).getReadMethod().invoke(record); if(f != null){ if(f instanceof List){ List asList = ((List) f); if(!asList.isEmpty()){ logger.debug("The object annotated with @Tag/@Group is a list. Adding ... "); // else add all the available elements for (int i = 0; i < asList.size(); i++) { String groupName = getGroupNameOnCkan(asList.get(i).toString().trim()); logger.debug(groupName); groups.add(groupName); } } }else{ // also convert to the group name that should be on ckan String groupName = getGroupNameOnCkan(f.toString().trim()); groups.add(groupName); } } // check if the field is an enumerator, and the enum class is also annotated with @Group if(field.getType().isEnum() && field.getType().isAnnotationPresent(Group.class)){ logger.info("Class " + field.getClass().getSimpleName() + " has annotation @Group"); // extract the name from the enum class and add it to the groups // also convert to the group name that should be on ckan String groupName = getGroupNameOnCkan(field.getType().getSimpleName()); groups.add(groupName); } }catch(Exception e){ logger.error("Failed ot read value for field " + field.getName() + " skipping", e); } } } } while((current = current.getSuperclass())!=null); // start from the inherited class up to the Object.class logger.info("Groups is " + groups); } /** * Retrieve the list of extras for this object */ public static void getExtras(Map> extras, Base record){ Class current = record.getClass(); do{ Field[] fields = current.getDeclaredFields(); for (Field field : fields) { if(field.isAnnotationPresent(CustomField.class)){ try{ Object f = new PropertyDescriptor(field.getName(), current).getReadMethod().invoke(record); String keyField = field.getAnnotation(CustomField.class).key(); if(f != null){ List valuesForKey = new ArrayList(); // check if the map already contains this key if(extras.containsKey(keyField)) valuesForKey = extras.get(keyField); if(f instanceof List){ logger.debug("The object " + field.getName() + " is a list and is annotated with @CustomField. Adding ..."); List asList = (List)f; if(!asList.isEmpty()){ int elementsToConsider = asList.size(); // check if it is a time series, in this case take the last X elements if(asList.get(0).getClass().equals(TimeSeriesBean.class)){ elementsToConsider = Math.min(elementsToConsider, TIME_SERIES_TAKE_LAST_VALUES); for (int i = (asList.size() - elementsToConsider); i < asList.size(); i++) { // trim and remove html String clean = HelperMethods.removeHTML(asList.get(i).toString().trim()); valuesForKey.add(clean); } }else{ for (int i = 0; i < elementsToConsider; i++) { String clean = HelperMethods.removeHTML(asList.get(i).toString().trim()); valuesForKey.add(clean); } } } }else{ String clean = HelperMethods.removeHTML(f.toString().trim()); valuesForKey.add(clean); } // add to the map extras.put(keyField, valuesForKey); } }catch(Exception e){ logger.error("Failed ot read value for field " + field.getName() + " skipping", e); } } } } while((current = current.getSuperclass())!=null); // start from the inherited class up to the Object.class logger.info("Extras is " + extras); } /** * Retrieve the organization name in which the user wants to publish starting from the scope * @param contextInWhichPublish * @return */ public static String retrieveOrgNameFromScope(String scope) { String[] splittedScope = scope.split("/"); return splittedScope[splittedScope.length - 1].toLowerCase(); } // /** // * Return the context in which the user wants to publish by the status information // * @param status // * @param contextServlet // * @return // */ // public static String getContextFromStatus(Status status, ServletContext contextServlet) { // // String toReturn = null; // switch(status){ // case Confirmed : // toReturn = (String)contextServlet.getInitParameter(CONFIRMED_CONTEX_KEY); // break; // case Pending: // toReturn = (String)contextServlet.getInitParameter(PENDING_CONTEX_KEY); // break; // default: break; // // } // logger.debug("Context evaluated is " + toReturn); // return toReturn; // } /** * Validate the name the product will have * @param futureName * @return */ public static boolean isNameValid(String futureName) { if(futureName == null || futureName.isEmpty()) return false; else{ return futureName.matches("[\\sA-Za-z0-9_.-]+"); } } /** * Retrieve the user's email given his/her username * @param context * @param token * @return * @throws Exception */ public static String getUserEmail(String context, String token){ String baseUrl = new ServiceEndPointReaderSocial(context).getBasePath(); String url = baseUrl + "users/getUserEmail?gcube-token=" + token; logger.debug("Request url is " + url); return executGETHttpRequest(url, 200); } /** * Retrieve the user's fullname given his/her username * @param context * @param token * @return * @throws Exception */ public static String getUserFullname(String context, String token){ String baseUrl = new ServiceEndPointReaderSocial(context).getBasePath(); String url = baseUrl + "users/getUserFullname?gcube-token=" + token; logger.debug("Request url is " + url); return executGETHttpRequest(url, 200); } /** * Execute the GET http request at this url, and return the result as string * @return */ private static String executGETHttpRequest(String url, int expectedCodeOnSuccess){ try(CloseableHttpClient client = HttpClientBuilder.create().build();){ HttpGet getRequest = new HttpGet(url); HttpResponse response = client.execute(getRequest); if (response.getStatusLine().getStatusCode() != expectedCodeOnSuccess) { throw new RuntimeException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode()); } BufferedReader br = new BufferedReader( new InputStreamReader((response.getEntity().getContent()))); String res = ""; String temp = null; while ((temp = br.readLine()) != null) { res += temp; } return res; }catch(Exception e){ logger.error("error while performing get method " + e.toString()); } return null; } /** * Retrieve the list of ckan licenses and build up a map * @return * @throws Exception */ public static Map getLicenses(DataCatalogue catalogue) throws Exception { Map toReturn = new HashMap(); List licenses = catalogue.getLicenses(); for (CkanLicense ckanLicense : licenses) { toReturn.put(ckanLicense.getId(), ckanLicense.getTitle()); } return toReturn; } /** * Check that the given license id is in CKAN * @param license id to check * @return * @throws Exception */ public static boolean existsLicenseId(String license, DataCatalogue catalogue) throws Exception { List licenses = catalogue.getLicenses(); for (CkanLicense ckanLicense : licenses) { if(ckanLicense.getId().equals(license)) return true; } return false; } /** * Retrieve the ResourceBean given the record (extract resources from Database Sources and Source of Information and others) * @param record * @param username * @param tags * @param groups * @param resources * @return */ public static void getResourcesFromBean(Base record, String username, Set tags, Set groups, List resources){ Class current = record.getClass(); do{ Field[] fields = current.getDeclaredFields(); for (Field field : fields) { if(field.isAnnotationPresent(CkanResource.class)){ try{ Object f = new PropertyDescriptor(field.getName(), current).getReadMethod().invoke(record); if(f != null){ if(f instanceof List){ List listOfResources = (List)f; for (Resource resource : listOfResources) { resources.add(new ResourceBean(resource.getUrl(), resource.getName().toString(), resource.getDescription(), null, username, null, null)); } }else{ Resource res = (Resource)f; resources.add(new ResourceBean(res.getUrl(), res.getName().toString(), res.getDescription(), null, username, null, null)); } } }catch(Exception e){ logger.error("Failed ot read value for field " + field.getName() + " skipping", e); } } } } while((current = current.getSuperclass())!=null); // iterate from the inherited class up to the Object.class logger.info("Returning resources " + resources); } /** * Upload a file in the shared folder * @param resourceFormatFolder * @param resourceToAttachName * @param description * @param csvFile * @return */ public static ExternalFile uploadExternalFile(WorkspaceFolder resourceFormatFolder, String resourceToAttachName, String description, File csvFile) { try { return resourceFormatFolder.createExternalFileItem(resourceToAttachName, description, CSV_MIME, csvFile); } catch (InsufficientPrivilegesException | ItemAlreadyExistException | InternalErrorException e) { logger.error("Failed to upload the file into the workspace shared folder for " + resourceToAttachName, e); } return null; } /** * Create subfolders in cascade, returning the last created ones * It could be also used for getting them if they already exists * @param folder * @param subPath * @return null if an error occurred */ public static WorkspaceFolder createOrGetSubFoldersByPath(WorkspaceFolder folder, String subPath){ WorkspaceFolder parentFolder = folder; if(folder == null) throw new IllegalArgumentException("Root folder is null!"); if(subPath == null || subPath.isEmpty()) throw new IllegalArgumentException("subPath is null/empty!"); try{ if(subPath.startsWith(PATH_SEPARATOR)) subPath = subPath.replaceFirst(PATH_SEPARATOR, ""); if(subPath.endsWith(PATH_SEPARATOR)) subPath = subPath.substring(0, subPath.length() - 1); logger.debug("Splitting path " + subPath); String[] splittedPaths = subPath.split(PATH_SEPARATOR); for (String path : splittedPaths) { WorkspaceFolder createdFolder = getFolderOrCreate(parentFolder, path, ""); logger.debug("Created subfolder with path " + createdFolder.getPath()); parentFolder = createdFolder; } }catch(Exception e){ logger.error("Failed to create the subfolders by path " + subPath); return null; } return parentFolder; } /** * Get a folder within the catalogue folder or create it if it doesn't exist. * @return */ public static WorkspaceFolder getFolderOrCreate(WorkspaceFolder folder, String relativePath, String descriptionFolder){ WorkspaceFolder result = null; try { WorkspaceItem foundFolder = folder.find(relativePath); if(foundFolder != null && foundFolder.isFolder()) result = (WorkspaceFolder)foundFolder; if(result != null) logger.debug("Folder found with name " + result.getName() + ", it has id " + result.getId()); else throw new Exception("There is no folder with name " + relativePath + " under folder " + folder.getName()); } catch (Exception e) { logger.debug("Probably the folder doesn't exist"); try{ result = folder.createFolder(relativePath, descriptionFolder); } catch (InsufficientPrivilegesException | InternalErrorException | ItemAlreadyExistException e2) { logger.error("Failed to get or generate this folder", e2); } } return result; } /** * Strip out HTML code * @param html * @return * @throws UnsupportedEncodingException */ public static String removeHTML(String html) { if(html == null || html.isEmpty()) return html; // remove html and clean String withoutHTML = Jsoup.parse(html).text(); withoutHTML = Jsoup.clean(withoutHTML, Whitelist.basic()); // remove non ascii chars ... withoutHTML = withoutHTML.replaceAll("[^\\p{ASCII}]", " "); return withoutHTML; } }