package org.gcube.portlets.widgets.ckandatapublisherwidget.server; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; import javax.servlet.http.HttpSession; import org.gcube.common.portal.PortalContext; import org.gcube.common.storagehubwrapper.server.StorageHubWrapper; import org.gcube.common.storagehubwrapper.server.tohl.Workspace; import org.gcube.datacatalogue.utillibrary.server.DataCatalogue; import org.gcube.datacatalogue.utillibrary.server.DataCatalogueFactory; import org.gcube.datacatalogue.utillibrary.server.utils.CatalogueUtilMethods; import org.gcube.datacatalogue.utillibrary.server.utils.SessionCatalogueAttributes; import org.gcube.datacatalogue.utillibrary.shared.ResourceBean; import org.gcube.datacatalogue.utillibrary.shared.RolesCkanGroupOrOrg; import org.gcube.datacatalogue.utillibrary.shared.jackan.model.CkanDataset; import org.gcube.datacatalogue.utillibrary.shared.jackan.model.CkanGroup; import org.gcube.datacatalogue.utillibrary.shared.jackan.model.CkanLicense; import org.gcube.datacatalogue.utillibrary.shared.jackan.model.CkanOrganization; import org.gcube.datacatalogue.utillibrary.shared.jackan.model.CkanResource; import org.gcube.datacatalogue.utillibrary.shared.jackan.model.CkanTag; import org.gcube.portlets.widgets.ckandatapublisherwidget.client.CKanPublisherService; import org.gcube.portlets.widgets.ckandatapublisherwidget.server.utils.CatalogueRoleManager; import org.gcube.portlets.widgets.ckandatapublisherwidget.server.utils.DiscoverTagsList; import org.gcube.portlets.widgets.ckandatapublisherwidget.server.utils.GenericUtils; import org.gcube.portlets.widgets.ckandatapublisherwidget.server.utils.MetadataDiscovery; import org.gcube.portlets.widgets.ckandatapublisherwidget.server.utils.WorkspaceUtils; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.DatasetBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.MetadataProfileBeanForUpdate; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.OrganizationBean; import org.gcube.portlets.widgets.ckandatapublisherwidget.shared.ResourceElementBean; import org.gcube.portlets.widgets.mpformbuilder.shared.license.LicenseBean; import org.gcube.portlets.widgets.mpformbuilder.shared.metadata.MetaDataProfileBean; import org.gcube.portlets.widgets.mpformbuilder.shared.metadata.MetadataFieldWrapper; import org.gcube.vomanagement.usermanagement.GroupManager; import org.gcube.vomanagement.usermanagement.UserManager; import org.gcube.vomanagement.usermanagement.exception.GroupRetrievalFault; import org.gcube.vomanagement.usermanagement.exception.UserManagementSystemException; import org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager; import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager; import org.gcube.vomanagement.usermanagement.model.GCubeUser; import org.geojson.GeoJsonObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.liferay.portal.service.UserLocalServiceUtil; /** * Server side of the data publisher. * * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) */ public class CKANPublisherServicesImpl extends RemoteServiceServlet implements CKanPublisherService { private static final long serialVersionUID = 7252248774050361697L; // Logger // private static final org.slf4j.Logger logger = // LoggerFactory.getLogger(CKANPublisherServicesImpl.class); private static final Logger logger = LoggerFactory.getLogger(CKANPublisherServicesImpl.class); public static final String ITEM_URL_FIELD = "Item URL"; public static final String SYSTEM_KEY_PREFIX = "system:"; public static final String SYS_TYPE = SYSTEM_KEY_PREFIX + "type"; public static final String TAGS_VOCABULARY_KEY = "TAGS_VOCABULARY"; public static final List SYSTEM_CUSTOM_FIELDS_PREFIXES = Arrays.asList(ITEM_URL_FIELD, SYSTEM_KEY_PREFIX); // map private ConcurrentHashMap mapOrganizationScope = new ConcurrentHashMap(); /** * Dev mode set contexts. */ private void devModeSetContexts() { if (!isWithinPortal()) { logger.info("DETECTED DEV MODE"); GenericUtils.getCurrentContext(getThreadLocalRequest(), true); GenericUtils.getCurrentToken(getThreadLocalRequest(), true); } } /** * Retrieve an instance of the library for the scope. * * @param scope if it is null it is evaluated from the session * @return the catalogue */ public DataCatalogue getCatalogue(String scope) { DataCatalogue instance = null; String scopeInWhichDiscover = null; try { scopeInWhichDiscover = scope != null && !scope.isEmpty() ? scope : GenericUtils.getCurrentContext(getThreadLocalRequest(), false); logger.debug("Discovering ckan instance into scope " + scopeInWhichDiscover); instance = DataCatalogueFactory.getFactory().getUtilsPerScope(scopeInWhichDiscover); } catch (Exception e) { logger.warn("Unable to retrieve ckan utils in scope " + scopeInWhichDiscover + ". Error is " + e.getLocalizedMessage()); } return instance; } /** * Retrieve the list of organizations in which the user can publish (roles * ADMIN/EDITOR). * * @param username the username * @param scope the scope * @return the list of organizations * @throws UserManagementSystemException the user management system exception * @throws GroupRetrievalFault the group retrieval fault */ private List getUserOrganizationsListAdmin(String username, String scope) throws UserManagementSystemException, GroupRetrievalFault { logger.debug("Request for user " + username + " organizations list"); List orgsName = new ArrayList(); HttpSession httpSession = getThreadLocalRequest().getSession(); String keyPerScope = CatalogueUtilMethods .concatenateSessionKeyScope(SessionCatalogueAttributes.CKAN_ORGANIZATIONS_PUBLISH_KEY, scope); if (httpSession.getAttribute(keyPerScope) != null) { orgsName = (List) httpSession.getAttribute(keyPerScope); logger.info("List of organizations was into session " + orgsName); } else { String gatewayURL = GenericUtils.getGatewayClientHostname(getThreadLocalRequest()); logger.info("The Gateway URL is: " + gatewayURL); CatalogueRoleManager.getHighestRole(scope, username, GenericUtils.getGroupFromScope(scope).getGroupName(), this, orgsName, gatewayURL); httpSession.setAttribute(keyPerScope, orgsName); logger.info("Organizations name for user " + username + " has been saved into session " + orgsName); } return orgsName; } /** * Online or in development mode?. * * @return true if you're running into the portal, false if in development */ private boolean isWithinPortal() { try { UserLocalServiceUtil.getService(); return true; } catch (com.liferay.portal.kernel.bean.BeanLocatorException ex) { logger.trace("Development Mode ON"); return false; } } /** * Gets the workspace from storage hub. * * @return the workspace from storage hub * @throws Exception the exception */ protected Workspace getWorkspaceFromStorageHub() throws Exception { GCubeUser user = PortalContext.getConfiguration().getCurrentUser(this.getThreadLocalRequest()); StorageHubWrapper storageHubWrapper = WorkspaceUtils.getStorageHubWrapper(this.getThreadLocalRequest(), null, user); return storageHubWrapper.getWorkspace(); } /** * Find a license id given the license text. * * @param chosenLicense the chosen license * @return the string */ private String findLicenseIdByLicense(String chosenLicense) { // get scope from client url String scope = GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); return getCatalogue(scope).findLicenseIdByLicenseTitle(chosenLicense); } /** * Gets the licenses. * * @return the licenses */ @Override public List getLicenses() { // get http session HttpSession httpSession = getThreadLocalRequest().getSession(); String scope = GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); logger.info("Request for CKAN licenses for scope " + scope); String keyPerScope = CatalogueUtilMethods .concatenateSessionKeyScope(SessionCatalogueAttributes.CKAN_LICENSES_KEY, scope); // if(!isWithinPortal()){ // logger.info("DEV MODE returning funny licenses..."); // List licenses = new ArrayList(); // licenses.add(new LicenseBean("AFL-3.0", "https://opensource.org/licenses/AFL-3.0")); // return licenses; // } List licensesBean = null; if (httpSession.getAttribute(keyPerScope) != null) { licensesBean = (List) httpSession.getAttribute(keyPerScope); logger.info("List of licenses was into session"); } else { List licenses = getCatalogue(scope).getLicenses(); licensesBean = new ArrayList(); for (CkanLicense license : licenses) { licensesBean.add(new LicenseBean(license.getTitle(), license.getUrl())); } // sort the list Collections.sort(licensesBean, new Comparator() { public int compare(LicenseBean l1, LicenseBean l2) { return l1.getTitle().compareTo(l2.getTitle()); } }); httpSession.setAttribute(keyPerScope, licensesBean); logger.info("List of licenses has been saved into session"); } return licensesBean; } /** * Gets the dataset bean. * * @param folderId the folder id * @return the dataset bean * @throws Exception the exception */ @Override public DatasetBean getDatasetBean(String folderId) throws Exception { DatasetBean bean = null; String userName = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); logger.info("DatasetBean request for folderId " + folderId + " and " + userName); if (isWithinPortal()) { try { String scope = GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); logger.debug("Scope recovered from session is " + scope); logger.debug( "Request dataset metadata bean for folder with id " + folderId + " whose owner is " + userName); UserManager liferUserManager = new LiferayUserManager(); GCubeUser userOwner = liferUserManager.getUserByUsername(userName); // build bean logger.debug("Building bean"); bean = new DatasetBean(); bean.setId(folderId); bean.setOwnerIdentifier(userName); bean.setVersion(1); bean.setAuthorName(userOwner.getFirstName()); bean.setAuthorSurname(userOwner.getLastName()); bean.setAuthorEmail(userOwner.getEmail()); bean.setMaintainer(userOwner.getFullname()); bean.setMaintainerEmail(userOwner.getEmail()); bean.setOrganizationList(getUserOrganizationsListAdmin(userName, scope)); bean.setTagsVocabulary(discoverTagsVocabulary(scope)); // if the request comes from the workspace if (folderId != null && !folderId.isEmpty()) { Workspace workspace = getWorkspaceFromStorageHub(); WorkspaceUtils.toWorkspaceResource(folderId, userName, bean, workspace); } } catch (Exception e) { logger.error("Error while retrieving bean information", e); throw new Exception("Error while retrieving basic information " + e.getMessage()); } } else { logger.info("DEV MODE DETECTED"); GenericUtils.getCurrentToken(getThreadLocalRequest(), true); try { bean = new DatasetBean(); bean.setId(folderId); bean.setDescription("This is a fantastic description"); bean.setVersion(1); String onlyAlphanumeric = "test-creation-blablabla".replaceAll("[^A-Za-z0-9]", ""); bean.setTitle(onlyAlphanumeric + Calendar.getInstance().getTimeInMillis()); bean.setAuthorName("Francesco"); bean.setAuthorSurname("Mangiacrapa"); bean.setAuthorEmail("francesco.mangiacrapa@isti.cnr.it"); bean.setMaintainer("Francesco Mangiacrapa"); bean.setMaintainerEmail("francesco.mangiacrapa@isti.cnr.it"); // UPDATED By Francesco String scope = GenericUtils.getCurrentContext(this.getThreadLocalRequest(), false); String vreName = scope.substring(scope.lastIndexOf("/") + 1, scope.length()); logger.debug("In dev mode using the scope: " + scope + " and VRE name: " + vreName); bean.setOrganizationList(Arrays.asList(new OrganizationBean(vreName, vreName.toLowerCase(), true))); bean.setOwnerIdentifier(userName); if (folderId != null && !folderId.isEmpty()) { Workspace workspace = getWorkspaceFromStorageHub(); WorkspaceUtils.toWorkspaceResource(folderId, userName, bean, workspace); } } catch (Exception e) { logger.error("Error while building bean into dev mode", e); throw new Exception("Error while retrieving basic information " + e.getMessage()); } } logger.debug("Returning bean " + bean); return bean; } /** * Gets the dataset bean for update. * * @param datasetIdOrName the dataset id or name * @return the dataset bean for update * @throws Exception the exception */ @Override public DatasetBean getDatasetBeanForUpdate(String datasetIdOrName) throws Exception { DatasetBean bean = null; String userName = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); logger.info("DatasetBeanForUpdate request for " + datasetIdOrName + " and " + userName); String scopePerCurrentUrl = GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); DataCatalogue utils = getCatalogue(scopePerCurrentUrl); CkanDataset dataset = utils.getDataset(datasetIdOrName, userName); if (dataset == null) { // the user cannot read the item, so he/she is not the owner nor the admin throw new Exception("Dataset with id " + datasetIdOrName + " not found for user " + userName); } logger.debug("Building bean"); bean = new DatasetBean(); bean.setId(datasetIdOrName); bean.setCkanName(dataset.getName()); bean.setTitle(dataset.getTitle()); bean.setDescription(dataset.getNotes()); bean.setLicense(dataset.getLicenseTitle()); bean.setVisibile(dataset.isPriv()); long version = 1; try { version = Long.parseLong(dataset.getVersion()); } catch (Exception e) { // TODO: handle exception } bean.setVersion(version); bean.setOwnerIdentifier(dataset.getCreatorUserId()); bean.setAuthorFullName(dataset.getAuthor()); bean.setAuthorEmail(dataset.getAuthorEmail()); bean.setMaintainer(dataset.getMaintainer()); bean.setMaintainerEmail(dataset.getMaintainerEmail()); CkanOrganization ckanOrganization = dataset.getOrganization(); // UPDATED By Francesco // String vreName = scope.substring(scope.lastIndexOf("/") + 1, scope.length()); // logger.debug("In dev mode using the scope: " + scope + " and VRE name: " + vreName); final OrganizationBean ckanOrganizationBean = new OrganizationBean(ckanOrganization.getTitle(), ckanOrganization.getName(), true); bean.setOrganizationList(Arrays.asList(ckanOrganizationBean)); List listDatasetTags = dataset.getTags(); if (logger.isDebugEnabled()) { logger.debug("List tags from CKAN are: "); for (CkanTag ckanTag : listDatasetTags) { // logger.debug("ckanTag: " + ckanTag.getDisplayName()); logger.debug("ckanTag: " + ckanTag.getName()); } } // selected tags into Dataset if (listDatasetTags != null) { List listTags = dataset.getTags().stream().map(t -> t.getName()).collect(Collectors.toList()); logger.info("setTags: {}", listTags); bean.setTags(listTags); } // Vocabulary Tags from Generi Resources bean.setTagsVocabulary(discoverTagsVocabulary(scopePerCurrentUrl)); // By default setting the root folder ID for updating the Resources on the // client-side // TODO LOAD THE WORKSPACE ROOT /* * Workspace workspace = getWorkspaceFromStorageHub(); * WorkspaceUtils.toWorkspaceResource(workspace.getRoot().getId(), userName, * bean, workspace); */ // Settings the CKAN resources List resources = dataset.getResources(); if (resources != null) { List list = new ArrayList(resources.size()); for (CkanResource ckanResource : resources) { ResourceElementBean reb = PublisherCatalogueConveter.toResourceElementBean(ckanResource, ckanOrganizationBean.getName()); list.add(reb); } bean.setResources(list); } // Settings the dataset type Map> extras = dataset.getListExtrasAsHashMap(); if (extras != null) { List theDatasetType = extras.get(SYS_TYPE); if (theDatasetType != null && theDatasetType.size() > 0) { bean.setChosenType(theDatasetType.get(0)); } } logger.debug("Returning bean " + bean); logger.info("Returning the bean for dataset title {} and type {}" + bean.getTitle(), bean.getChosenType()); return bean; } /** * Discover from the IS the vocabulary of tags for this scope, if present. * * @param context the context * @return a list of tags vocabulary */ private List discoverTagsVocabulary(String context) { logger.debug("Looking for vocabulary of tags in this context " + context); String keyPerVocabulary = CatalogueUtilMethods.concatenateSessionKeyScope(TAGS_VOCABULARY_KEY, context); List vocabulary = (List) getThreadLocalRequest().getSession().getAttribute(keyPerVocabulary); if (vocabulary == null) { vocabulary = DiscoverTagsList.discoverTagsList(context); if (vocabulary != null) getThreadLocalRequest().getSession().setAttribute(keyPerVocabulary, vocabulary); } logger.debug("Vocabulary for tags is " + vocabulary); return vocabulary; } /** * Gets the tags for organization. * * @param orgName the org name * @return the tags for organization * @throws Exception the exception */ @Override public List getTagsForOrganization(String orgName) throws Exception { return discoverTagsVocabulary(getScopeFromOrgName(orgName)); } /** * Creates the C kan dataset. * * @param toCreate the to create * @return the dataset bean * @throws Exception the exception */ @Override public DatasetBean createCKanDataset(DatasetBean toCreate) throws Exception { try { devModeSetContexts(); logger.info("Request for creating a dataset with title: {} " + toCreate.getTitle()); if (logger.isDebugEnabled()) { logger.debug("Dataset is: {} " + toCreate); } String userName = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); String title = toCreate.getTitle(); String organizationNameOrId = toCreate.getSelectedOrganization(); String author = toCreate.getAuthorFullName(); String authorMail = toCreate.getAuthorEmail(); String maintainer = toCreate.getMaintainer(); String maintainerMail = toCreate.getMaintainerEmail(); long version = toCreate.getVersion(); String description = toCreate.getDescription(); String chosenLicense = toCreate.getLicense(); String licenseId = findLicenseIdByLicense(chosenLicense); List listOfTags = toCreate.getTags(); Map> customFields = toCreate.getCustomFields(); // add Type for custom fields if (toCreate.getChosenType() != null) customFields.put(SYS_TYPE, Arrays.asList(toCreate.getChosenType())); boolean setPublic = toCreate.getVisibility(); // get the list of resources and convert to ResourceBean List resources = null; ResourceElementBean resourcesToAdd = toCreate.getResourceRoot(); // converting to resources to be added if (resourcesToAdd != null) { Workspace workspace = getWorkspaceFromStorageHub(); resources = WorkspaceUtils.toResources(toCreate, workspace, userName); } logger.debug("The user wants to publish in organization with name " + organizationNameOrId); String scope = getScopeFromOrgName(organizationNameOrId); DataCatalogue utils = getCatalogue(scope); if (!isWithinPortal()) { logger.debug("Should be added:"); for (String key : customFields.keySet()) { logger.debug("Custom field with key: " + key + ", value: " + customFields.get(key)); } } String datasetId = utils.createCkanDatasetMultipleCustomFields(userName, title, null, organizationNameOrId, author, authorMail, maintainer, maintainerMail, version, description, licenseId, listOfTags, customFields, resources, setPublic, true, true); if (datasetId != null) { logger.info("Dataset created!"); toCreate.setId(datasetId); // #23491 Building the go to the item to "Catalogue Portlet URL" (instead of URI // Resolver URL) String catalogueURL = utils.getPortletUrl(); // logger.debug("Returning catalogueURL: "+catalogueURL); // logger.debug("Returning datasetId: "+datasetId); toCreate.setSource(String.format("%s?path=/dataset/%s", catalogueURL, datasetId)); logger.debug("Returning getSource(): " + toCreate.getSource()); logger.debug("Returning dataset: " + toCreate); // #24744 Returning lazy object toCreate.setGroups(null); toCreate.setCustomFields(null); toCreate.setGroupsForceCreation(null); toCreate.setMetadataList(null); toCreate.setOrganizationList(null); logger.info("Returning lazy dataset: " + toCreate); // createdDatasetBean.getSource(); // createdDatasetBean.getTitle(); // resourceForm = new AddResourceToDataset(eventBus, createdDatasetBean.getId(), // createdDatasetBean.getTitle(), // createdDatasetBean.getSelectedOrganization(), owner, datasetUrl); return toCreate; } else { logger.error("Failed to create the dataset"); } } catch (Exception e) { logger.error("Error while creating item ", e); throw new Exception(e.getMessage()); } return null; } /** * Update CKAN dataset. * * @param toUpdate the to create * @return the dataset bean * @throws Exception the exception */ @Override public DatasetBean updateCKANDataset(DatasetBean toUpdate) throws Exception { try { devModeSetContexts(); logger.info("Request for updating a dataset with title: {} " + toUpdate.getTitle()); if (logger.isDebugEnabled()) { logger.debug("Dataset is: {} " + toUpdate); } if (toUpdate.getCkanName() == null) throw new Exception("Error on updating: the input parameter 'name' is null"); String userName = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); String title = toUpdate.getTitle(); String organizationNameOrId = toUpdate.getSelectedOrganization(); String author = toUpdate.getAuthorFullName(); String authorMail = toUpdate.getAuthorEmail(); String maintainer = toUpdate.getMaintainer(); String maintainerMail = toUpdate.getMaintainerEmail(); long version = toUpdate.getVersion(); String description = toUpdate.getDescription(); String chosenLicense = toUpdate.getLicense(); String licenseId = findLicenseIdByLicense(chosenLicense); List listOfTags = toUpdate.getTags(); Map> customFields = toUpdate.getCustomFields(); // add Type for custom fields // if (toUpdate.getChosenType() != null) { // customFields.put(SYS_TYPE, Arrays.asList(toUpdate.getChosenType())); // } // Reading the CKAN Dataset with extras in order to set the reserver system into // update request String scopePerCurrentUrl = GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); DataCatalogue utils = getCatalogue(scopePerCurrentUrl); CkanDataset currentDataset = utils.getDataset(toUpdate.getId(), userName); Map> ckanExtras = currentDataset.getListExtrasAsHashMap(); Map> ckanReserverSystemExtras = getReserverdSystemFields(ckanExtras); // putting all reserved system fields into customFields to update customFields.putAll(ckanReserverSystemExtras); // Settings the CKAN resources List listCurrentResources = currentDataset.getResources(); List resources = null; if (listCurrentResources != null) { resources = new ArrayList(listCurrentResources.size()); for (CkanResource ckanResource : listCurrentResources) { String orgName = null; // not required here ResourceBean reb = PublisherCatalogueConveter.toResourceBean(ckanResource, orgName); resources.add(reb); } } boolean setPublic = toUpdate.getVisibility(); // get the list of resources and convert to ResourceBean from the Workspace ResourceElementBean resourcesToAdd = toUpdate.getResourceRoot(); // converting to resources to be added if (resourcesToAdd != null) { Workspace workspace = getWorkspaceFromStorageHub(); resources.addAll(WorkspaceUtils.toResources(toUpdate, workspace, userName)); } logger.debug("The user wants to publish in organization with name " + organizationNameOrId); // String scope = getScopeFromOrgName(organizationNameOrId); if (!isWithinPortal()) { logger.debug("Should be added:"); for (String key : customFields.keySet()) { logger.debug("Custom field with key: " + key + ", value: " + customFields.get(key)); } } String datasetId = utils.updateCkanDatasetMultipleCustomFields(userName, title, toUpdate.getCkanName(), organizationNameOrId, author, authorMail, maintainer, maintainerMail, version, description, licenseId, listOfTags, customFields, resources, setPublic, true, true); if (datasetId != null) { logger.info("Dataset updated!"); toUpdate.setId(datasetId); // #23491 Building the go to the item to "Catalogue Portlet URL" (instead of URI // Resolver URL) String catalogueURL = utils.getPortletUrl(); // logger.debug("Returning catalogueURL: "+catalogueURL); // logger.debug("Returning datasetId: "+datasetId); toUpdate.setSource(String.format("%s?path=/dataset/%s", catalogueURL, datasetId)); logger.debug("Returning getSource(): " + toUpdate.getSource()); logger.debug("Returning dataset: " + toUpdate); // #24744 Returning lazy object toUpdate.setGroups(null); toUpdate.setCustomFields(null); toUpdate.setGroupsForceCreation(null); toUpdate.setMetadataList(null); toUpdate.setOrganizationList(null); logger.info("Returning lazy dataset: " + toUpdate); // createdDatasetBean.getSource(); // createdDatasetBean.getTitle(); // resourceForm = new AddResourceToDataset(eventBus, createdDatasetBean.getId(), // createdDatasetBean.getTitle(), // createdDatasetBean.getSelectedOrganization(), owner, datasetUrl); return toUpdate; } else { logger.error("Failed to update the dataset"); } } catch (Exception e) { logger.error("Error while updating item ", e); throw new Exception(e.getMessage()); } return null; } /** * Adds the resource to dataset. * * @param resource the resource * @param organizationName the organization name * @param datasetId the dataset id * @return the resource element bean * @throws Exception the exception */ @Override public ResourceElementBean addResourceToDataset(ResourceElementBean resource, String organizationName, String datasetId) throws Exception { logger.info("called addResourceToDataset"); devModeSetContexts(); String username = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); logger.debug("Incoming request for creating new resource for dataset with id " + datasetId + " and organization name of the dataset is " + resource.getOrganizationNameDatasetParent()); logger.debug("Owner is " + username + " and resource is " + resource); ResourceBean resourceBean = new ResourceBean(resource.getUrl(), resource.getName(), resource.getDescription(), null, username, datasetId, null); // get the scope in which we should discover the ckan instance given the // organization name in which the dataset was created String scope = getScopeFromOrgName(resource.getOrganizationNameDatasetParent()); DataCatalogue catalogue = getCatalogue(scope); CkanResource theCreatedResource = catalogue.addResource(resourceBean, datasetId, resource.getOrganizationNameDatasetParent(), username); if (theCreatedResource != null) { logger.info("Resource " + resource.getName() + " is now available"); ResourceElementBean reb = PublisherCatalogueConveter.toResourceElementBean(theCreatedResource, organizationName); return reb; } logger.debug("No resource created"); return null; } /** * Delete resource from dataset. * * @param resource the resource * @return true, if successful * @throws Exception the exception */ @Override public boolean deleteResourceFromDataset(ResourceElementBean resource) throws Exception { logger.debug("Request for deleting resource " + resource); boolean deleted = false; devModeSetContexts(); try { // get the scope in which we should discover the ckan instance given the // organization name in which the dataset was created String scope = getScopeFromOrgName(resource.getOrganizationNameDatasetParent()); DataCatalogue catalogue = getCatalogue(scope); String username = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); deleted = catalogue.deleteResourceFromDataset(resource.getCkanResourceID(), username); if (deleted) { logger.info("Resource described by " + resource + " deleted"); } else logger.error("Resource described by " + resource + " NOT deleted"); } catch (Exception e) { String error = "Sorry, an error occurred while trying to delete resource described by " + resource.getName(); logger.error(error, e); throw new Exception(error + ". Error is: " + e.getMessage()); } return deleted; } /** * Gets the profiles. * * @param orgName the org name * @return the profiles * @throws Exception the exception */ @Override public List getProfiles(String orgName) throws Exception { logger.debug("Requested profiles for products into orgName " + orgName); List toReturn = new ArrayList(); try { String evaluatedScope = getScopeFromOrgName(orgName); logger.debug("Evaluated scope is " + evaluatedScope); toReturn = MetadataDiscovery.getMetadataProfilesList(evaluatedScope, getThreadLocalRequest()); } catch (Exception e) { logger.error("Failed to retrieve profiles for scope coming from organization name " + orgName, e); throw e; } return toReturn; } /** * Gets the profile for update. * * @param orgName the org name * @param datasetType the dataset type * @param datasedIdOrName the datased id or name * @return the profile for update * @throws Exception the exception */ @Override public MetadataProfileBeanForUpdate getProfileForUpdate(String orgName, String datasetType, String datasedIdOrName) throws Exception { logger.info("Called getProfileForUpdate for orgName {} and dataset type {} ", orgName, datasetType); logger.debug("Requested profiles for products into orgName " + orgName); List toRead = new ArrayList(); List toReturn = new ArrayList(); try { String evaluatedScope = getScopeFromOrgName(orgName); logger.debug("Evaluated scope is " + evaluatedScope); toRead = MetadataDiscovery.getMetadataProfilesList(evaluatedScope, getThreadLocalRequest()); // Getting profile for datasetType, expecting only one toReturn = toRead.stream().filter(tr -> tr.getType().compareTo(datasetType) == 0) .collect(Collectors.toList()); } catch (Exception e) { logger.error("Failed to retrieve profiles for scope coming from organization name " + orgName, e); throw e; } // retrieve scope per current Portlet url String scopePerCurrentUrl = GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); String username = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); DataCatalogue utils = getCatalogue(scopePerCurrentUrl); CkanDataset dataset = utils.getDataset(datasedIdOrName, username); if (toReturn.isEmpty()) throw new Exception("No Profile found for dataset type: " + datasetType); // Settings current values in the profile found MetaDataProfileBean profileBean = toReturn.get(0); if (logger.isTraceEnabled()) { logger.trace("The source profile is: {}", profileBean); logger.trace("The metadatafields into source profile are: {}", profileBean.getMetadataFields().size()); for (int i = 0; i < profileBean.getMetadataFields().size(); i++) { MetadataFieldWrapper mw = profileBean.getMetadataFields().get(i); logger.trace(i + " MetadataFieldWrapper : {}", mw); } } Map> extras = dataset.getListExtrasAsHashMap(); Map> customFieldsMap = new HashMap>(extras); logger.trace("Current extras are {}", extras); List expandedListForMultipleOccurs = new ArrayList(); // Operating on the source cloned list List clonedList = cloneList(profileBean.getMetadataFields()); // Creating bean to return MetaDataProfileBean toReturnMetaDataProfileBean = new MetaDataProfileBean(); toReturnMetaDataProfileBean.setTitle(profileBean.getTitle()); toReturnMetaDataProfileBean.setType(profileBean.getType()); toReturnMetaDataProfileBean.setCategories(profileBean.getCategories()); for (MetadataFieldWrapper metadataFieldWrapper : clonedList) { String fieldName = metadataFieldWrapper.getFieldNameFromCategory(); // removing profile key from the map logger.trace("Searching field name '{}' in the profile", fieldName); List currValuesOfExtraField = extras.get(fieldName); logger.trace("Current value found is '{}' for field name '{}'", currValuesOfExtraField, fieldName); // the profile field is set as extra field (it is not null), so going to set the // current values if (currValuesOfExtraField != null && !currValuesOfExtraField.isEmpty()) { customFieldsMap.remove(fieldName); if (currValuesOfExtraField.size() == 1) { metadataFieldWrapper.setCurrentValues(currValuesOfExtraField.stream().toArray(String[]::new)); } else { // Duplicating the fields with multiple occurrences int dataSize = currValuesOfExtraField.size(); for (int j = 0; j < dataSize; j++) { List cloned = cloneList(Arrays.asList(metadataFieldWrapper)); MetadataFieldWrapper mfw = cloned.get(0); // Duplicating MetadataFieldWrapper for data list with isMultiSelection==false if (!mfw.isMultiSelection()) { mfw.setCurrentValues(currValuesOfExtraField.get(j) + ""); // In case of array, from the first to second-last one, repeated field is set to // 'false' // These properties are managed properly with the // last one field if (j < dataSize - 1) { mfw.setMandatory(false); mfw.setMaxOccurs(1); } expandedListForMultipleOccurs.add(mfw); } else { // Setting dataArray as list of current values when isMultiSelection is true String[] toArray = (String[]) currValuesOfExtraField.toArray(new String[0]); mfw.setCurrentValues(toArray); expandedListForMultipleOccurs.add(mfw); // Exit from for break; } } // returns to external for because the field with multiple occurrences has been // added to list continue; } } // add field to list expandedListForMultipleOccurs.add(metadataFieldWrapper); } toReturnMetaDataProfileBean.setMetadataFields(expandedListForMultipleOccurs); logger.trace("CustomFieldsMap (extras) not matching metadata profile are {}", customFieldsMap); int customFieldsSize = customFieldsMap.size(); // There are the Custom Fields not belonging to profile logger.info("Custom fields founds # {}", customFieldsSize); if (customFieldsSize > 0) { customFieldsMap = purgeSystemFields(customFieldsMap); } logger.info("custom fields to return {}", customFieldsMap.keySet()); MetadataProfileBeanForUpdate mpfu = new MetadataProfileBeanForUpdate(); mpfu.setListProfileBean(Arrays.asList(toReturnMetaDataProfileBean)); mpfu.setCustomFields(customFieldsMap); if (logger.isDebugEnabled()) { logger.debug("Returning filled profile {}", toReturnMetaDataProfileBean.getType()); logger.debug("with MetadataFields {}", toReturnMetaDataProfileBean.getMetadataFields()); logger.debug("Custom fields founds {}", customFieldsMap.keySet()); } logger.info("returing the filled profile {}", profileBean.getType()); return mpfu; } /** * Dataset id already exists. * * @param title the title * @param orgName the org name * @return true, if successful * @throws Exception the exception */ @Override public boolean datasetIdAlreadyExists(String title, String orgName) throws Exception { if (title == null || title.isEmpty()) return true; // it's an error somehow try { String scopeFromOrgName = getScopeFromOrgName(orgName); String idFromTitle = CatalogueUtilMethods.fromProductTitleToName(title); logger.debug( "Evaluating if dataset with id " + title + " in context " + scopeFromOrgName + " already exists"); return getCatalogue(scopeFromOrgName).existProductWithNameOrId(idFromTitle); } catch (Exception e) { logger.error("Unable to check if such a dataset id already exists", e); throw new Exception("Unable to check if such a dataset id already exists " + e.getMessage()); } } /** * The method tries to retrieve the scope related to the organization using the * map first, if no match is found, it retrieves such information by using * liferay. * * @param orgName the org name * @return the scope from org name */ private String getScopeFromOrgName(String orgName) { logger.info("Request for scope related to orgName " + orgName + " [map that will be used is " + mapOrganizationScope.toString() + "]"); if (orgName == null || orgName.isEmpty()) throw new IllegalArgumentException("orgName cannot be empty or null!"); String toReturn = null; if (isWithinPortal()) { if (mapOrganizationScope.containsKey(orgName)) { toReturn = mapOrganizationScope.get(orgName); } else { try { String evaluatedScope = GenericUtils.retrieveScopeFromOrganizationName(orgName); // see #20801 if (evaluatedScope == null || evaluatedScope.isEmpty()) { logger.warn("Scope detected for OrganizationName: " + orgName + " is null or empty, skipping filling 'mapOrganizationScope' and returning null"); return toReturn; } mapOrganizationScope.put(orgName, evaluatedScope); toReturn = evaluatedScope; } catch (Exception e) { logger.error("Failed to retrieve scope from OrgName for organization " + orgName, e); } } } else { // UPDATED By FRANCESCO toReturn = GenericUtils.getCurrentContext(this.getThreadLocalRequest(), false); mapOrganizationScope.put(orgName, toReturn); } logger.info("Returning scope " + toReturn); return toReturn; } /** * Gets the user groups. * * @param orgName the org name * @return the user groups */ @Override public List getUserGroups(String orgName) { List toReturn = new ArrayList(); if (isWithinPortal()) { GCubeUser user = GenericUtils.getCurrentUser(getThreadLocalRequest()); String username = null; if (user != null) username = user.getUsername(); logger.debug("Request for user " + username + " groups. Organization name is " + orgName); // get http session HttpSession httpSession = getThreadLocalRequest().getSession(); String scope = orgName != null ? getScopeFromOrgName(orgName) : GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); // check if they are in session String keyPerScopeGroups = CatalogueUtilMethods .concatenateSessionKeyScope(SessionCatalogueAttributes.CKAN_GROUPS_MEMBER, scope); if (httpSession.getAttribute(keyPerScopeGroups) != null) { toReturn = (List) httpSession.getAttribute(keyPerScopeGroups); logger.info("Found user's groups in session " + toReturn); } else { // Fixing Incident #12563 try { DataCatalogue catalogue = getCatalogue(scope); // Fixing Incident #12563 if (username != null && !username.isEmpty()) { Map> mapRoleGroup = catalogue .getUserRoleByGroup(username); Set>> set = mapRoleGroup.entrySet(); for (Entry> entry : set) { Set> subSet = entry.getValue().entrySet(); for (Entry subEntry : subSet) { toReturn.add(new OrganizationBean(subEntry.getKey().getTitle(), subEntry.getKey().getName(), false)); } } httpSession.setAttribute(keyPerScopeGroups, toReturn); } else logger.warn("The API_KEY for " + username + " is null or empty in the catalogue: " + catalogue.getCatalogueUrl()); } catch (Exception e) { logger.error("Error on recovery the user groups for " + username, e); } } } else { logger.warn("Dev mode detected"); toReturn = Arrays.asList(); } return toReturn; } /** * Checks if is publisher user. * * @return true, if is publisher user * @throws Exception the exception */ @Override public boolean isPublisherUser() throws Exception { String username = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); logger.info("Checking if the user " + username + " can publish or not on the catalogue"); if (!isWithinPortal()) { logger.warn("OUT FROM PORTAL DETECTED RETURNING TRUE"); return true; } try { HttpSession httpSession = this.getThreadLocalRequest().getSession(); // retrieve scope per current portlet url String scopePerCurrentUrl = GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); // get key per scope String keyPerScopeRole = CatalogueUtilMethods .concatenateSessionKeyScope(SessionCatalogueAttributes.CKAN_HIGHEST_ROLE, scopePerCurrentUrl); String keyPerScopeOrganizations = CatalogueUtilMethods.concatenateSessionKeyScope( SessionCatalogueAttributes.CKAN_ORGANIZATIONS_PUBLISH_KEY, scopePerCurrentUrl); String keyPerScopeGroups = CatalogueUtilMethods .concatenateSessionKeyScope(SessionCatalogueAttributes.CKAN_GROUPS_MEMBER, scopePerCurrentUrl); // check if this information was already into session(true means the user has at // least in one org // the role editor), false that he is just a member so he cannot publish RolesCkanGroupOrOrg role = (RolesCkanGroupOrOrg) httpSession.getAttribute(keyPerScopeRole); // if the attribute was already set.. if (role != null) return !role.equals(RolesCkanGroupOrOrg.MEMBER); else { try { GroupManager gm = new LiferayGroupManager(); String groupName = gm.getGroup(gm.getGroupIdFromInfrastructureScope(scopePerCurrentUrl)) .getGroupName(); // we build up also a list that keeps track of the scopes (orgs) in which the // user has role ADMIN/EDITOR List orgsInWhichAtLeastEditorRole = new ArrayList(); String gatewayURL = GenericUtils.getGatewayClientHostname(getThreadLocalRequest()); logger.info("The Gateway URL is: " + gatewayURL); role = CatalogueRoleManager.getHighestRole(scopePerCurrentUrl, username, groupName, this, orgsInWhichAtLeastEditorRole, gatewayURL); // if he is an admin/editor preload: // 1) organizations in which he can publish (the widget will find these info in // session) if (!role.equals(RolesCkanGroupOrOrg.MEMBER)) { httpSession.setAttribute(keyPerScopeOrganizations, orgsInWhichAtLeastEditorRole); String orgName = scopePerCurrentUrl.split("/")[scopePerCurrentUrl.split("/").length - 1]; httpSession.setAttribute(keyPerScopeGroups, getUserGroups(orgName)); } } catch (Exception e) { logger.error("Unable to retrieve the role information for this user. Returning FALSE", e); return false; } } // set role in session for this scope httpSession.setAttribute(keyPerScopeRole, role); logger.info("Does the user have the right to publish on the catalogue? " + role); return !role.equals(RolesCkanGroupOrOrg.MEMBER); } catch (Exception e) { logger.error("Failed to check the user's role", e); throw new Exception("Failed to check if you are an Administrator or Editor " + e.getMessage()); } } /** * Checks if is publisher owner or admin user. * * @param datasetIdOrName the dataset id or name * @return true, if is publisher owner or admin user * @throws Exception the exception */ @Override public boolean isPublisherOwnerOrAdminUser(String datasetIdOrName) throws Exception { String username = GenericUtils.getCurrentUser(getThreadLocalRequest()).getUsername(); logger.info("Checking if the user " + username + " can publish or not on the catalogue"); boolean isPublisher = isPublisherUser(); if (isPublisher) { RolesCkanGroupOrOrg role = null; String scopePerCurrentUrl = GenericUtils.getScopeFromClientUrl(getThreadLocalRequest()); if (!isWithinPortal()) { role = RolesCkanGroupOrOrg.EDITOR; logger.warn("OUT FROM PORTAL SETTING HARD-CODED ROLE " + role); } else { String keyPerScopeRole = CatalogueUtilMethods .concatenateSessionKeyScope(SessionCatalogueAttributes.CKAN_HIGHEST_ROLE, scopePerCurrentUrl); HttpSession httpSession = this.getThreadLocalRequest().getSession(); role = (RolesCkanGroupOrOrg) httpSession.getAttribute(keyPerScopeRole); } // if the user is an EDITOT he/she must be also the owner of the dataset if (role.equals(RolesCkanGroupOrOrg.EDITOR)) { logger.info("The user {} is an {}", username, RolesCkanGroupOrOrg.EDITOR); String loggedUserEmail = GenericUtils.getCurrentUser(getThreadLocalRequest()).getEmail(); logger.debug("Logged user email: {} ", loggedUserEmail); DataCatalogue utils = getCatalogue(scopePerCurrentUrl); CkanDataset dataset = utils.getDataset(datasetIdOrName, username); String datasetOwnerEmail = dataset.getAuthorEmail(); logger.debug("Dataset Owner email: {} ", datasetOwnerEmail); if (loggedUserEmail != null && datasetOwnerEmail != null && datasetOwnerEmail.compareTo(loggedUserEmail) == 0) { logger.info("The user {} is owner of the dataset id {}, returning isOwnerOrAdminUser true ", username, dataset.getId()); return true; } } else if (role.equals(RolesCkanGroupOrOrg.ADMIN)) { logger.info("The user {} is an {}", username, RolesCkanGroupOrOrg.ADMIN); return true; } } logger.info("The user {} does not have the rights to update the dataset with id {}", username, datasetIdOrName); return false; } /** * Checks if is geo JSON valid. * * @param geoJson the geo json * @return true, if is geo JSON valid * @throws Exception the exception */ @Override public boolean isGeoJSONValid(String geoJson) throws Exception { try { new ObjectMapper().readValue(geoJson, GeoJsonObject.class); return true; } catch (Exception e) { throw new Exception("GeoJSON field with value '" + geoJson + "' seems not valid!"); } } /** * Purge system fields. * * @param extras the extras * @return the map */ public static Map> purgeSystemFields(Map> extras) { logger.info("Purging extras from reserved fields {} ", SYSTEM_CUSTOM_FIELDS_PREFIXES); if (extras == null) return null; Map> extrasPop = new HashMap>(extras); for (String key : extras.keySet()) { List list = SYSTEM_CUSTOM_FIELDS_PREFIXES.stream().filter(scf -> key.startsWith(scf)) .collect(Collectors.toList()); if (list.size() > 0) extrasPop.remove(key); } logger.debug("returning purged extras {} ", extrasPop); return extrasPop; } /** * Gets the reserverd system fields. * * @param extras the extras * @return the reserverd system fields */ public static Map> getReserverdSystemFields(Map> extras) { logger.info("Reading reserved fields from extras..."); if (extras == null) return null; Map> reserverSystemExtras = new HashMap>(); for (String key : extras.keySet()) { List list = SYSTEM_CUSTOM_FIELDS_PREFIXES.stream().filter(scf -> key.startsWith(scf)) .collect(Collectors.toList()); if (list.size() > 0) reserverSystemExtras.put(key, extras.get(key)); } logger.debug("returning reserverd extras {} ", reserverSystemExtras); return reserverSystemExtras; } /** * Clone list. * * @param list the list * @return the list */ public static List cloneList(List list) { List listCloned = new ArrayList(list.size()); Function cloneWrapper = (mfw) -> { MetadataFieldWrapper newMfw = new MetadataFieldWrapper(); newMfw.setAsGroup(mfw.getAsGroup()); newMfw.setAsTag(mfw.getAsTag()); List listValues = mfw.getCurrentValues(); if (listValues != null) { newMfw.setCurrentValues(listValues.stream().toArray(String[]::new)); } newMfw.setDefaultValue(mfw.getDefaultValue()); newMfw.setFieldId(mfw.getFieldId()); newMfw.setFieldName(mfw.getFieldName()); newMfw.setFieldNameFromCategory(mfw.getFieldNameFromCategory()); newMfw.setMandatory(mfw.getMandatory()); newMfw.setMaxOccurs(mfw.getMaxOccurs()); newMfw.setMultiSelection(mfw.isMultiSelection()); newMfw.setNote(mfw.getNote()); newMfw.setOwnerCategory(mfw.getOwnerCategory()); newMfw.setType(mfw.getType()); newMfw.setValidator(mfw.getValidator()); newMfw.setVocabulary(mfw.getVocabulary()); return newMfw; }; for (MetadataFieldWrapper item : list) { MetadataFieldWrapper cloned = cloneWrapper.apply(item); listCloned.add(cloned); } return listCloned; } }