commit 5743fc03edf5a2ae353f6b68d29650bd5efa71e6 Author: Luca Frosini Date: Tue Dec 4 11:06:22 2018 +0000 Service Refatored and Renamed to gCcat Refs #12810: Refactor catalogue-ws Task-Url: https://support.d4science.org/issues/12810 git-svn-id: https://svn.d4science.research-infrastructures.eu/gcube/trunk/data-publishing/gcat@174532 82a268e6-3cf1-43bd-a215-b396298e98cf diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..ace8266 --- /dev/null +++ b/.classpath @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..74a4850 --- /dev/null +++ b/.project @@ -0,0 +1,42 @@ + + + science-catalogue + + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope new file mode 100644 index 0000000..b72a6a4 --- /dev/null +++ b/.settings/.jsdtscope @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..6e80039 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..3fbd090 --- /dev/null +++ b/.settings/org.eclipse.wst.common.component @@ -0,0 +1,14 @@ + + + + + + + + + uses + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml b/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml new file mode 100644 index 0000000..cc81385 --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..dcea5a1 --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs new file mode 100644 index 0000000..04cad8c --- /dev/null +++ b/.settings/org.eclipse.wst.validation.prefs @@ -0,0 +1,2 @@ +disabled=06target +eclipse.preferences.version=1 diff --git a/distro/LICENSE b/distro/LICENSE new file mode 100644 index 0000000..cdf50bd --- /dev/null +++ b/distro/LICENSE @@ -0,0 +1,4 @@ +gCube System - License +------------------------------------------------------------ + +${gcube.license} \ No newline at end of file diff --git a/distro/README b/distro/README new file mode 100644 index 0000000..054cd4a --- /dev/null +++ b/distro/README @@ -0,0 +1,63 @@ +The gCube System - ${name} +-------------------------------------------------- + +${description} + + +${gcube.description} + +${gcube.funding} + + +Version +-------------------------------------------------- + +${version} (${buildDate}) + +Please see the file named "changelog.xml" in this directory for the release notes. + + +Authors +-------------------------------------------------- + +* Luca Frosini (luca.frosini-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). +* Costantino Perciante (costantino.perciante@isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Maintainers +----------- + +* Luca Frosini (luca.frosini-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Download information +-------------------------------------------------- + +Source code is available from SVN: + ${scm.url} + +Binaries can be downloaded from the gCube website: + ${gcube.website} + + +Installation +-------------------------------------------------- + +Installation documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot} + +Documentation +-------------------------------------------------- + +Documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot} + +Support +-------------------------------------------------- + +Bugs and support requests can be reported in the gCube issue tracking tool: + ${gcube.issueTracking} + + +Licensing +-------------------------------------------------- + +This software is licensed under the terms you may find in the file named "LICENSE" in this directory. \ No newline at end of file diff --git a/distro/changelog.xml b/distro/changelog.xml new file mode 100644 index 0000000..9d50d14 --- /dev/null +++ b/distro/changelog.xml @@ -0,0 +1,31 @@ + + + + + + + + Complete refactoring of code + Item Port type is now RESTful (old methods are still allowed) + Added update support for Item #11516 + Changed caching mechanism from ehcache API to JSR-107. Ehcache is still used as runtime library. + Solved random NullPointer Exception on catalogue-ws related to old caching mechanism #11466 + Fixed normalization of the organization name #12506 + Added the possibility to deny social post on catalogue-ws #12514 + + + Item purge method enhanced + + + Minor fixes while checking user's permissions + Namespaces are no longer transparently managed + Fixed 'default' value for metadata + Improved exception handling + + + First Release + + diff --git a/distro/descriptor.xml b/distro/descriptor.xml new file mode 100644 index 0000000..ba2c192 --- /dev/null +++ b/distro/descriptor.xml @@ -0,0 +1,31 @@ + + servicearchive + + tar.gz + + / + + + ${distroDirectory} + ${file.separator} + true + + README + LICENSE + changelog.xml + profile.xml + + 755 + true + + + + + target${file.separator}${build.finalName}.${project.packaging} + ${file.separator}${artifactId} + + + \ No newline at end of file diff --git a/distro/gcube-app.xml b/distro/gcube-app.xml new file mode 100644 index 0000000..69d4bbd --- /dev/null +++ b/distro/gcube-app.xml @@ -0,0 +1,10 @@ + + + + ${artifactId} + ${serviceClass} + ${version} + ${description} + + + diff --git a/distro/profile.xml b/distro/profile.xml new file mode 100644 index 0000000..3944037 --- /dev/null +++ b/distro/profile.xml @@ -0,0 +1,27 @@ + + + + + Service + + ${description} + ${serviceClass} + ${artifactId} + 1.0.0 + +
+ ${description} + ${artifactId} + ${version} + + ${groupId} + ${artifactId} + ${version} + + + ${build.finalName}.${project.packaging} + +
+
+
+
diff --git a/distro/web.xml b/distro/web.xml new file mode 100644 index 0000000..c392a96 --- /dev/null +++ b/distro/web.xml @@ -0,0 +1,11 @@ + + + + + org.gcube.datacatalogue.sciencecatalogue.ResourceInitializer + + + org.gcube.datacatalogue.sciencecatalogue.ResourceInitializer + /* + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3854a59 --- /dev/null +++ b/pom.xml @@ -0,0 +1,304 @@ + + 4.0.0 + + maven-parent + org.gcube.tools + 1.0.0 + + + + org.gcube.data-publishing + gcat + war + 1.0.0-SNAPSHOT + gCube Catalogue Service + + This service allows any client to publish on the gCube Catalogue. + + + + + + org.gcube.distribution + gcube-bom + LATEST + pom + import + + + org.gcube.distribution + gcube-smartgears-bom + LATEST + pom + import + + + org.gcube.common + gxRest + 1.0.1-SNAPSHOT + + + + + + 2.13 + UTF-8 + ${project.basedir}/src/main/webapp/WEB-INF + + ${project.basedir}/distro + DataCatalogue + + + + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/data-catalogue/${project.artifactId} + scm:https://svn.d4science.research-infrastructures.eu/gcube//trunk/data-catalogue/${project.artifactId} + https://svn.d4science.research-infrastructures.eu/gcube/trunk/data-catalogue/${project.artifactId} + + + + + + + javax.cache + cache-api + 1.0.0 + + + org.ehcache + ehcache + 3.5.2 + runtime + + + + + org.slf4j + slf4j-api + provided + + + + org.gcube.data-catalogue + gcubedatacatalogue-metadata-discovery + [3.0.0-SNAPSHOT, 4.0.0-SNAPSHOT) + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + + + org.gcube.data-catalogue + ckan-util-library + [2.0.0-SNAPSHOT, 3.0.0-SNAPSHOT) + + + org.glassfish.jersey.containers + jersey-container-servlet + ${version.jersey} + + + + + org.glassfish.jersey.media + jersey-media-multipart + ${version.jersey} + + + + org.gcube.common + storagehub-client-library + [1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT) + + + + + org.gcube.common + home-library-jcr + [2.0.0-SNAPSHOT,3.0.0-SNAPSHOT) + + + org.gcube.common + home-library + [2.0.0-SNAPSHOT,3.0.0-SNAPSHOT) + + + + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + + + com.fasterxml.jackson.core + jackson-core + provided + + + com.fasterxml.jackson.core + jackson-databind + provided + + + com.fasterxml.jackson.core + jackson-annotations + provided + + + org.gcube.resources + common-gcore-resources + provided + + + org.gcube.core + common-gcore-stubs + provided + + + org.gcube.core + common-scope + provided + + + org.gcube.core + common-scope-maps + provided + + + org.gcube.resources.discovery + discovery-client + provided + + + org.gcube.core + common-encryption + provided + + + org.gcube.common + authorization-client + provided + + + org.gcube.common + common-authorization + provided + + + org.gcube.resources.discovery + ic-client + provided + + + + + + org.gcube.core + common-smartgears + provided + + + + de.grundid.opendatalab + geojson-jackson + 1.8 + + + + commons-lang + commons-lang + 2.3 + + + + + org.json + json + 20140107 + + + + + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-grizzly2 + ${version.jersey} + test + + + junit + junit + 4.11 + test + + + ch.qos.logback + logback-classic + 1.0.13 + test + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-profile + + copy-resources + + process-resources + + ${webappDirectory} + + + ${distroDirectory} + true + + + + + + + + org.apache.maven.plugins + maven-war-plugin + + ${artifactId} + false + + + + + servicearchive + install + + war + + + + + + + diff --git a/src/main/java/org/gcube/gcat/ResourceInitializer.java b/src/main/java/org/gcube/gcat/ResourceInitializer.java new file mode 100644 index 0000000..66c2179 --- /dev/null +++ b/src/main/java/org/gcube/gcat/ResourceInitializer.java @@ -0,0 +1,21 @@ +package org.gcube.gcat; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.MediaType; + +import org.gcube.gcat.rest.Group; +import org.glassfish.jersey.server.ResourceConfig; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@ApplicationPath("/") +public class ResourceInitializer extends ResourceConfig { + + public static final String APPLICATION_JSON_CHARSET_UTF_8 = MediaType.APPLICATION_JSON + ";charset=UTF-8"; + + public ResourceInitializer() { + packages(Group.class.getPackage().toString()); + } + +} diff --git a/src/main/java/org/gcube/gcat/annotation/PATCH.java b/src/main/java/org/gcube/gcat/annotation/PATCH.java new file mode 100644 index 0000000..0520379 --- /dev/null +++ b/src/main/java/org/gcube/gcat/annotation/PATCH.java @@ -0,0 +1,14 @@ +package org.gcube.gcat.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.ws.rs.HttpMethod; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@HttpMethod("PATCH") +public @interface PATCH { +} \ No newline at end of file diff --git a/src/main/java/org/gcube/gcat/annotation/PURGE.java b/src/main/java/org/gcube/gcat/annotation/PURGE.java new file mode 100644 index 0000000..39ae4dd --- /dev/null +++ b/src/main/java/org/gcube/gcat/annotation/PURGE.java @@ -0,0 +1,14 @@ +package org.gcube.gcat.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.ws.rs.HttpMethod; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@HttpMethod("PURGE") +public @interface PURGE { +} \ No newline at end of file diff --git a/src/main/java/org/gcube/gcat/exception/ScienceCatalogueException.java b/src/main/java/org/gcube/gcat/exception/ScienceCatalogueException.java new file mode 100644 index 0000000..50cc54a --- /dev/null +++ b/src/main/java/org/gcube/gcat/exception/ScienceCatalogueException.java @@ -0,0 +1,25 @@ +package org.gcube.gcat.exception; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class ScienceCatalogueException extends Exception { + + /** + * Generated Serial Version UID + */ + private static final long serialVersionUID = -5449813222333935588L; + + public ScienceCatalogueException(String message) { + super(message); + } + + public ScienceCatalogueException(Throwable cause) { + super(cause); + } + + public ScienceCatalogueException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/org/gcube/gcat/oldutils/CachesManager.java b/src/main/java/org/gcube/gcat/oldutils/CachesManager.java new file mode 100644 index 0000000..a37621d --- /dev/null +++ b/src/main/java/org/gcube/gcat/oldutils/CachesManager.java @@ -0,0 +1,67 @@ +package org.gcube.gcat.oldutils; + +import javax.cache.Cache; +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.configuration.MutableConfiguration; +import javax.cache.expiry.CreatedExpiryPolicy; +import javax.cache.expiry.Duration; +import javax.cache.spi.CachingProvider; + +import org.gcube.datacatalogue.metadatadiscovery.DataCalogueMetadataFormatReader; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Handle caches via Ehcache stuff. + * @author Costantino Perciante (ISTI - CNR) + * @author Luca Frosini (ISTI - CNR) + */ +public class CachesManager { + + // the following caches are declared within the ehcache.xml (no default is available, so pay attention) + public static final String PROFILES_READERS_CACHE = "profile_readers"; + public static final String PROFILES_USERS_CACHE = "profile_users"; + + private static final CacheManager cacheManager; + private static final Cache readerCache; + private static final Cache userCache; + + static { + CachingProvider provider = Caching.getCachingProvider(); + cacheManager = provider.getCacheManager(); + + MutableConfiguration readerConfiguration = + new MutableConfiguration() + .setTypes(String.class, DataCalogueMetadataFormatReader.class) + .setStoreByValue(false) + .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE)); + readerCache = cacheManager.createCache(PROFILES_READERS_CACHE, readerConfiguration); + + + + MutableConfiguration userConfiguration = + new MutableConfiguration() + .setTypes(String.class, JsonNode.class) + .setStoreByValue(false) + .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE)); + userCache = cacheManager.createCache(PROFILES_USERS_CACHE, userConfiguration); + } + + private CachesManager() {} + + public static Cache getReaderCache() { + return readerCache; + } + + public static Cache getUserCache() { + return userCache; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + cacheManager.close(); + } + +} diff --git a/src/main/java/org/gcube/gcat/oldutils/CustomField.java b/src/main/java/org/gcube/gcat/oldutils/CustomField.java new file mode 100644 index 0000000..b6609bd --- /dev/null +++ b/src/main/java/org/gcube/gcat/oldutils/CustomField.java @@ -0,0 +1,134 @@ +package org.gcube.gcat.oldutils; + +import org.json.simple.JSONObject; + +/** + * A custom field bean. It also stores index of the category and of the metadata field associated. + * These are used to sort them before pushing the content to CKAN. + * If they are missing, indexes are set to Integer.MAX_VALUE. + * @author Costantino Perciante (ISTI - CNR) + * @author Luca Frosini (ISTI - CNR) + */ +public class CustomField implements Comparable { + + private String key; + private String qualifiedKey; + private String value; + + private int indexCategory = Integer.MAX_VALUE; + private int indexMetadataField = Integer.MAX_VALUE; + + + private void init(String key, String value, int indexCategory, int indexMetadataField) { + if(key == null || value == null || key.isEmpty()) { + throw new IllegalArgumentException( + "A custom field must have a key and a value! Provided values are " + key + "=" + value); + } + + this.key = key; + this.qualifiedKey = key; + this.value = value; + + this.indexMetadataField = indexMetadataField; + this.indexCategory = indexCategory; + + if(this.indexCategory < 0) { + this.indexCategory = Integer.MAX_VALUE; + } + + if(this.indexMetadataField < 0) { + this.indexMetadataField = Integer.MAX_VALUE; + } + } + + + public CustomField(JSONObject object) { + super(); + init((String) object.get("key"), (String) object.get("value"), -1, -1); + } + + /** + * @param key + * @param value + */ + public CustomField(String key, String value) { + super(); + init(key, value, -1, -1); + } + + /** + * @param key + * @param value + * @param indexMetadataField + * @param indexCategory + */ + public CustomField(String key, String value, int indexCategory, int indexMetadataField) { + super(); + init(key, value, indexCategory, indexMetadataField); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getQualifiedKey() { + return qualifiedKey; + } + + public void setQualifiedKey(String qualifiedKey) { + this.qualifiedKey = qualifiedKey; + } + + + public int getIndexCategory() { + return indexCategory; + } + + public void setIndexCategory(int indexCategory) { + this.indexCategory = indexCategory; + if(this.indexCategory < 0) + this.indexCategory = Integer.MAX_VALUE; + } + + public int getIndexMetadataField() { + return indexMetadataField; + } + + public void setIndexMetadataField(int indexMetadataField) { + this.indexMetadataField = indexMetadataField; + if(this.indexMetadataField < 0) { + this.indexMetadataField = Integer.MAX_VALUE; + } + } + + @Override + public String toString() { + return "CustomField [key=" + key + ", qualifiedKey=" + qualifiedKey + ", value=" + value + + ", indexMetadataField=" + indexMetadataField + ", indexCategory=" + indexCategory + "]"; + } + + @Override + public int compareTo(CustomField o) { + if(this.indexCategory == o.indexCategory) { + if(this.indexMetadataField == o.indexMetadataField) { + return 0; + } else { + return this.indexMetadataField > o.indexMetadataField ? 1 : -1; + } + } else { + return this.indexCategory > o.indexCategory ? 1 : -1; + } + } +} diff --git a/src/main/java/org/gcube/gcat/oldutils/Validator.java b/src/main/java/org/gcube/gcat/oldutils/Validator.java new file mode 100644 index 0000000..4f28780 --- /dev/null +++ b/src/main/java/org/gcube/gcat/oldutils/Validator.java @@ -0,0 +1,633 @@ +package org.gcube.gcat.oldutils; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +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 javax.ws.rs.BadRequestException; + +import org.apache.commons.lang.math.NumberUtils; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.datacatalogue.ckanutillibrary.server.utils.CatalogueUtilMethods; +import org.gcube.datacatalogue.metadatadiscovery.DataCalogueMetadataFormatReader; +import org.gcube.datacatalogue.metadatadiscovery.bean.MetadataProfile; +import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.DataType; +import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.MetadataField; +import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.MetadataFormat; +import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.MetadataGrouping; +import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.MetadataTagging; +import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.NamespaceCategory; +import org.gcube.gcat.persistence.ckan.CKAN; +import org.gcube.gcat.persistence.ckan.CKANPackage; +import org.gcube.gcat.persistence.ckan.CKANUtility; +import org.gcube.gcat.rest.Namespace; +import org.gcube.gcat.rest.Profile; +import org.gcube.gcat.social.SocialService; +import org.geojson.GeoJsonObject; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.trentorise.opendata.jackan.model.CkanGroup; + +/** + * Validate creation item requests utilities. + * @author Costantino Perciante (ISTI - CNR) + * @author Luca Frosini (ISTI - CNR) + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class Validator { + + private static final Logger logger = LoggerFactory.getLogger(Validator.class); + + private static final SimpleDateFormat DATE_SIMPLE = new SimpleDateFormat("yyyy-MM-dd"); + private static final SimpleDateFormat DATE_HOUR_MINUTES = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + private static JSONObject getJSONObject(String json) { + JSONParser parser = new JSONParser(); + JSONObject jsonObject; + try { + jsonObject = (JSONObject) parser.parse(json); + } catch(ParseException e) { + throw new BadRequestException(e.getMessage()); + } + return jsonObject; + } + + public static void validateAgainstProfile(String json, List profiles) throws Exception { + JSONObject jsonObject = getJSONObject(json); + validateAgainstProfile(jsonObject, profiles); + } + + + /** + * This method validate the incoming json dataset wrt a metadata profile + * @param json + * @param caller + * @param profiles + * @return + * @throws Exception + */ + public static void validateAgainstProfile(JSONObject obj, List profiles) throws Exception { + + JSONArray extrasArrayOriginal = (JSONArray) obj.get(CKANPackage.EXTRA_TYPES_KEY); + JSONArray groupsArrayOriginal = (JSONArray) obj.get(CKANPackage.GROUPS_KEY); + JSONArray tagsArrayOriginal = (JSONArray) obj.get(CKANPackage.TAGS_KEY); + + if(extrasArrayOriginal == null || extrasArrayOriginal.isEmpty()) { + throw new Exception("'extras' field is missing in context where metadata profile(s) are defined!"); + } + + if(groupsArrayOriginal == null) { + groupsArrayOriginal = new JSONArray(); + } + + if(tagsArrayOriginal == null) { + tagsArrayOriginal = new JSONArray(); + } + + // get the metadata profile specifying the type + CustomField metadataTypeCF = null; + List customFields = new ArrayList(extrasArrayOriginal.size()); + Iterator iterator = extrasArrayOriginal.iterator(); + while(iterator.hasNext()) { + JSONObject object = (JSONObject) iterator.next(); + CustomField cf = new CustomField(object); + if(cf.getKey().equals(CKANPackage.EXTRA_TYPES_KEY_VALUE_SYSTEM_TYPE)) { + metadataTypeCF = cf; + } else if(cf.getKey().equals(SocialService.ITEM_URL)) { + continue; + } else { + customFields.add(cf); + } + } + + if(metadataTypeCF == null) { + throw new Exception("'" + CKANPackage.EXTRA_TYPES_KEY_VALUE_SYSTEM_TYPE + + "' extra field is missing in context where metadata profile(s) are defined!"); + } + + + // fetch the profile by metadata type specified above + MetadataFormat profile = null; + for(String profileName : profiles) { + profile = Validator.getMetadataProfile(profileName); + if(profile.getType().equals(metadataTypeCF.getValue())) { + break; + } else { + profile = null; + } + } + + if(profile == null) { + throw new Exception("'" + CKANPackage.EXTRA_TYPES_KEY_VALUE_SYSTEM_TYPE + + "' extra field's value ('" + metadataTypeCF.getValue() + + "') specified as custom field doesn't match any of the profiles defined in this context!"); + } else { + JSONArray extrasArrayUpdated = null; + List metadataFields = profile.getMetadataFields(); + + if(metadataFields == null || metadataFields.isEmpty()) { + extrasArrayUpdated = extrasArrayOriginal; + } else { + extrasArrayUpdated = new JSONArray(); + + List categories = Namespace.getNamespaceCategories(); + logger.debug("Retrieved namespaces are {}", categories); + List categoriesIds = new ArrayList(categories == null ? 0 : categories.size()); + if(categories == null || categories.isEmpty()) { + logger.warn("No category defined in context {}", ScopeProvider.instance.get()); + } else { + for(NamespaceCategory metadataCategory : categories) { + categoriesIds.add(metadataCategory.getId()); // save them later for matching with custom fields + } + } + + // the list of already validated customFields + List validatedCustomFields = new ArrayList(customFields.size()); + + // keep track of mandatory fields and their cardinality + Map fieldsMandatoryLowerBoundMap = new HashMap(metadataFields.size()); + Map fieldsMandatoryUpperBoundMap = new HashMap(metadataFields.size()); + Map numberFieldsMandatorySameKeyMap = new HashMap( + metadataFields.size()); + + // keep track of the groups that must be created AFTER validation but BEFORE item creation + List groupsToCreateAfterValidation = new ArrayList(); + + // now validate fields + int metadataIndex = 0; + for(MetadataField metadataField : metadataFields) { + + int categoryIdIndex = categoriesIds.indexOf(metadataField.getCategoryRef()); + logger.debug("Found index for category " + metadataField.getCategoryRef() + " " + categoryIdIndex); + List validCFs = validateAgainstMetadataField(metadataIndex, categoryIdIndex, + customFields, tagsArrayOriginal, groupsArrayOriginal, metadataField, categories, + fieldsMandatoryLowerBoundMap, fieldsMandatoryUpperBoundMap, numberFieldsMandatorySameKeyMap, + groupsToCreateAfterValidation); + validatedCustomFields.addAll(validCFs); + metadataIndex++; + + } + + // check mandatory fields + Iterator> iteratorLowerBounds = fieldsMandatoryLowerBoundMap.entrySet() + .iterator(); + while(iteratorLowerBounds.hasNext()) { + Map.Entry entry = (Map.Entry) iteratorLowerBounds + .next(); + int lowerBound = entry.getValue(); + int upperBound = fieldsMandatoryUpperBoundMap.get(entry.getKey()); + int inserted = numberFieldsMandatorySameKeyMap.get(entry.getKey()); + + logger.info("Field with key '" + entry.getKey() + "' has been found " + inserted + + " times and its lower bound is " + lowerBound + " and upper bound " + upperBound); + + if(inserted < lowerBound || inserted > upperBound) { + throw new Exception("Field with key '" + entry.getKey() + + "' is mandatory, but it's not present among the provided fields or its cardinality is not respected ([min = " + + lowerBound + ", max=" + upperBound + "])."); + } + } + + // if there are no tags, throw an exception + if(tagsArrayOriginal.isEmpty()) { + throw new Exception("Please define at least one tag for this item!"); + } + + // sort validated custom fields and add to the extrasArrayUpdated json array + Collections.sort(validatedCustomFields); + + logger.debug("Sorted list of custom fields is " + validatedCustomFields); + + // add missing fields with no match (append them at the end, since no metadataIndex or categoryIndex was defined for them) + for(CustomField cf : customFields) + validatedCustomFields.add(cf); + + // convert back to json + for(CustomField customField : validatedCustomFields) { + JSONObject jsonObj = new JSONObject(); + jsonObj.put(CKANPackage.EXTRA_TYPES_KEY_KEY, customField.getQualifiedKey()); + jsonObj.put(CKANPackage.EXTRA_TYPES_VALUE_KEY, customField.getValue()); + extrasArrayUpdated.add(jsonObj); + } + + // add metadata type field as last element + JSONObject metadataTypeJSON = new JSONObject(); + metadataTypeJSON.put(CKANPackage.EXTRA_TYPES_KEY_KEY, metadataTypeCF.getKey()); + metadataTypeJSON.put(CKANPackage.EXTRA_TYPES_VALUE_KEY, metadataTypeCF.getValue()); + extrasArrayUpdated.add(metadataTypeJSON); + + // create groups + for(String title : groupsToCreateAfterValidation) { + try { + createGroupAsSysAdmin(title, title, ""); + } catch(Exception e) { + logger.error("Failed to create group with title " + title, e); + } + } + } + + obj.put(CKANPackage.TAGS_KEY, tagsArrayOriginal); + obj.put(CKANPackage.GROUPS_KEY, groupsArrayOriginal); + obj.put(CKANPackage.EXTRA_TYPES_KEY, extrasArrayUpdated); + + } + + } + + /** + * Retrieve an instance of the library for the scope + * @param scope if it is null it is evaluated from the session + * @return + * @throws Exception + */ + public static CkanGroup createGroupAsSysAdmin(String title, String groupName, String description) throws Exception { + return CKAN.getCatalogue().createGroup(groupName, title, description); + } + + /** + * Validate this field and generate a new value (or returns the same if there is nothing to update) + * @param metadataIndex + * @param categoryIndex + * @param customFields + * @param tagsArrayOriginal + * @param groupsArrayOriginal + * @param metadataField + * @param categories + * @param numberFieldsSameKeyMap + * @param fieldsMandatoryLowerBoundMap + * @param isApplication + * @return + * @throws Exception + */ + private static List validateAgainstMetadataField(int metadataIndex, int categoryIndex, + List customFields, JSONArray tagsArrayOriginal, JSONArray groupsArrayOriginal, + MetadataField metadataField, List categories, + Map fieldsMandatoryLowerBoundMap, Map fieldsMandatoryUpperBoundMap, + Map numberFieldsMandatorySameKeyMap, List groupToCreate) throws Exception { + + List toReturn = new ArrayList(); + String metadataFieldName = metadataField.getCategoryFieldQName(); // get the qualified one, if any + int fieldsFoundWithThisKey = 0; + + Iterator iterator = customFields.iterator(); + while(iterator.hasNext()) { + CustomField cf = (CustomField) iterator.next(); + if(cf.getKey().equals(metadataFieldName)) { + validate(cf, metadataField); + fieldsFoundWithThisKey++; + cf.setIndexCategory(categoryIndex); + cf.setIndexMetadataField(metadataIndex); + checkAsGroup(cf, metadataField, groupsArrayOriginal, groupToCreate); + checkAsTag(cf, metadataField, tagsArrayOriginal); + toReturn.add(cf); + iterator.remove(); + } + } + + // in case of mandatory fields, keep track of the number of times they appear + if(metadataField.getMandatory()) { + // lower bound + int lowerBound = 1; + if(fieldsMandatoryLowerBoundMap.containsKey(metadataFieldName)) + lowerBound = fieldsMandatoryLowerBoundMap.get(metadataFieldName) + 1; + fieldsMandatoryLowerBoundMap.put(metadataFieldName, lowerBound); + + // upper bound + boolean hasVocabulary = metadataField.getVocabulary() != null; + int upperBound = hasVocabulary ? (metadataField.getVocabulary().isMultiSelection() + ? metadataField.getVocabulary().getVocabularyFields().size() + : 1) : 1; + + if(fieldsMandatoryUpperBoundMap.containsKey(metadataFieldName)) + upperBound += fieldsMandatoryUpperBoundMap.get(metadataFieldName); + + fieldsMandatoryUpperBoundMap.put(metadataFieldName, upperBound); + + // fields with this same key + int countPerFields = fieldsFoundWithThisKey; + if(numberFieldsMandatorySameKeyMap.containsKey(metadataFieldName)) + countPerFields += numberFieldsMandatorySameKeyMap.get(metadataFieldName); + numberFieldsMandatorySameKeyMap.put(metadataFieldName, countPerFields); + } + + // if there was no field with this key and it was not mandatory, just add an entry of the kind {"key": "key-value", "value" : ""}. + // Sometimes it is important to view the field as empty. + if(fieldsFoundWithThisKey == 0 && !metadataField.getMandatory()) { + toReturn.add(new CustomField(metadataFieldName, "", -1, -1)); + } + + return toReturn; + + } + + public static final int MAX_TAG_CHARS = 100; + + /** + * Check if a tag must be generated + * @param fieldToValidate + * @param metadataField + * @param tagsArrayOriginal + */ + private static void checkAsTag(CustomField fieldToValidate, MetadataField metadataField, + JSONArray tagsArrayOriginal) { + MetadataTagging tagging = metadataField.getTagging(); + if(tagging != null) { + + String tag = ""; + + switch(tagging.getTaggingValue()) { + case onFieldName: + tag = metadataField.getFieldName(); + break; + case onValue: + tag = fieldToValidate.getValue(); + break; + case onFieldName_onValue: + tag = metadataField.getFieldName() + tagging.getSeparator() + fieldToValidate.getValue(); + break; + case onValue_onFieldName: + tag = fieldToValidate.getValue() + tagging.getSeparator() + metadataField.getFieldName(); + break; + default: + return; + } + + tag = tag.substring(0, MAX_TAG_CHARS > tag.length() ? tag.length() : MAX_TAG_CHARS); + logger.debug("Tag is " + tag); + + JSONObject tagJSON = new JSONObject(); + tagJSON.put("name", tag); + tagJSON.put("display_name", tag); + tagsArrayOriginal.add(tagJSON); + + } + + } + + /** + * Check if a group must be generated + * @param fieldToValidate + * @param metadataField + * @param groupsArrayOriginal + * @param isApplication + * @throws Exception + */ + private static void checkAsGroup(CustomField fieldToValidate, MetadataField metadataField, + JSONArray groupsArrayOriginal, List groupToCreate) throws Exception { + + logger.debug("Custom field is " + fieldToValidate); + logger.debug("MetadataField field is " + metadataField); + logger.debug("JSONArray field is " + groupsArrayOriginal); + + MetadataGrouping grouping = metadataField.getGrouping(); + if(grouping != null) { + + boolean propagateUp = grouping.getPropagateUp(); + final Set groupNames = new HashSet(); + + switch(grouping.getGroupingValue()) { + case onFieldName: + groupNames.add(metadataField.getFieldName()); + break; + case onValue: + if(fieldToValidate.getValue() != null && !fieldToValidate.getValue().isEmpty()) + groupNames.add(fieldToValidate.getValue()); + break; + case onFieldName_onValue: + case onValue_onFieldName: + groupNames.add(metadataField.getFieldName()); + if(fieldToValidate.getValue() != null && !fieldToValidate.getValue().isEmpty()) + groupNames.add(fieldToValidate.getValue()); + break; + default: + return; + } + + for(String title : groupNames) { + logger.debug("Adding group to which add this item " + CatalogueUtilMethods.fromGroupTitleToName(title)); + JSONObject group = new JSONObject(); + group.put("name", CatalogueUtilMethods.fromGroupTitleToName(title)); + if(propagateUp) { + List parents = Validator.getGroupHierarchyNames(CatalogueUtilMethods.fromGroupTitleToName(title)); + for(String parent : parents) { + JSONObject groupP = new JSONObject(); + groupP.put("name", parent); + groupsArrayOriginal.add(groupP); + } + } + groupsArrayOriginal.add(group); + } + + // force group creation if needed + if(grouping.getCreate()) { + for(String title : groupNames) + groupToCreate.add(title); + } + } + + } + + /** + * Validate the single field + * @param fieldToValidate + * @param metadataField + * @param isFirst + * @return + * @throws Exception + */ + private static void validate(CustomField fieldToValidate, MetadataField metadataField) throws Exception { + + DataType dataType = metadataField.getDataType(); + String regex = metadataField.getValidator() != null ? metadataField.getValidator().getRegularExpression() + : null; + boolean hasControlledVocabulary = metadataField.getVocabulary() != null; + String value = fieldToValidate.getValue(); + String key = fieldToValidate.getKey(); + String defaultValue = metadataField.getDefaultValue(); + + // replace key by prepending the qualified name of the category, if needed + fieldToValidate.setQualifiedKey(metadataField.getCategoryFieldQName()); + + if((value == null || value.isEmpty())) + if(metadataField.getMandatory() || hasControlledVocabulary) + throw new Exception("Mandatory field with name '" + key + + "' doesn't have a value but it is mandatory or has a controlled vocabulary!"); + else { + if(defaultValue != null && !defaultValue.isEmpty()) { + value = defaultValue; + fieldToValidate.setValue(defaultValue); + } + return; // there is no need to check other stuff + } + + switch(dataType) { + + case String: + case Text: + + if(regex != null && !value.matches(regex)) + throw new Exception("Field with key '" + key + "' doesn't match the provided regular expression (" + + regex + ")!"); + + if(hasControlledVocabulary) { + + List valuesVocabulary = metadataField.getVocabulary().getVocabularyFields(); + + if(valuesVocabulary == null || valuesVocabulary.isEmpty()) + return; + + boolean match = false; + for(String valueVocabulary : valuesVocabulary) { + match = value.equals(valueVocabulary); + if(match) + break; + } + + if(!match) + throw new Exception("Field with key '" + key + "' has a value '" + value + + "' but it doesn't match any of the vocabulary's values (" + valuesVocabulary + ")!"); + + } + + break; + case Time: + + if(!isValidDate(value)) + throw new Exception("Field with key '" + key + "' doesn't seem a valid time!"); + + break; + case Time_Interval: + + String[] timeValues = value.split("/"); + for(int i = 0; i < timeValues.length; i++) { + String time = timeValues[i]; + if(!isValidDate(time)) + throw new Exception("Field with key '" + key + "' doesn't seem a valid time interval!"); + } + + break; + case Times_ListOf: + + String[] timeIntervals = value.split(","); + for(int i = 0; i < timeIntervals.length; i++) { + String[] timeIntervalValues = timeIntervals[i].split("/"); + if(timeIntervalValues.length > 2) + throw new Exception("Field with key '" + key + "' doesn't seem a valid list of times!"); + for(i = 0; i < timeIntervalValues.length; i++) { + String time = timeIntervalValues[i]; + if(!isValidDate(time)) + throw new Exception("Field with key '" + key + "' doesn't seem a valid list of times!"); + } + } + + break; + case Boolean: + + if(value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { + + } else + throw new Exception("Field with key '" + key + "' doesn't seem a valid boolean value!"); + + break; + case Number: + + if(!NumberUtils.isNumber(value)) + throw new Exception("Field's value with key '" + key + "' is not a valid number!"); + + break; + case GeoJSON: + + try { + new ObjectMapper().readValue(fieldToValidate.getValue(), GeoJsonObject.class); + } catch(Exception e) { + throw new Exception("GeoJSON field with key '" + key + "' seems not valid!"); + } + + break; + default: + break; + } + + } + + /** + * Validate a time date against a formatter + * @param value + * @param formatter + * @return + */ + private static boolean isValidDate(String value) { + + try { + DATE_HOUR_MINUTES.parse(value); + return true; + } catch(Exception e) { + logger.debug("failed to parse date with hours and minutes, trying the other one"); + try { + DATE_SIMPLE.parse(value); + return true; + } catch(Exception e2) { + logger.warn("failed to parse date with simple format, returning false"); + return false; + } + } + + } + + /** + * Get the group hierarchy + * @param groupName + * @param isApplication + * @return + * @throws Exception + */ + public static List getGroupHierarchyNames(String groupName) throws Exception { + List toReturn = new ArrayList(); + String apiKey = CKANUtility.getApiKey(); + List ckanGroups = CKAN.getCatalogue().getParentGroups(groupName, apiKey); + if(ckanGroups != null && !ckanGroups.isEmpty()) { + for(CkanGroup ckanGroup : ckanGroups) { + toReturn.add(ckanGroup.getName()); + } + } + return toReturn; + } + + + /** + * Returns the metadataform of the metadata profile (specified via name) in a given context + * @param context + * @return + * @throws Exception + */ + public static MetadataFormat getMetadataProfile(String profileName) throws Exception { + + DataCalogueMetadataFormatReader reader = Profile.getDataCalogueMetadataFormatReader(); + List listProfiles = reader.getListOfMetadataProfiles(); + + if(listProfiles != null && !listProfiles.isEmpty()) { + for(MetadataProfile profile : listProfiles) { + if(profile.getName().equals(profileName)) { + return reader.getMetadataFormatForMetadataProfile(profile); + } + } + } + + return null; + } + +} diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKAN.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKAN.java new file mode 100644 index 0000000..be772d9 --- /dev/null +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKAN.java @@ -0,0 +1,300 @@ +package org.gcube.gcat.persistence.ckan; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.NotAuthorizedException; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; + +import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue; +import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueFactory; +import org.gcube.gcat.utils.ContextUtility; +import org.gcube.gcat.utils.HTTPCall; +import org.gcube.gcat.utils.HTTPCall.HTTPMETHOD; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public abstract class CKAN { + + private static final Logger logger = LoggerFactory.getLogger(CKAN.class); + + protected static final String ID_KEY = "id"; + protected static final String NAME_KEY = "name"; + + protected static final String ERROR_KEY = "error"; + protected static final String ERROR_TYPE_KEY = "__type"; + protected static final String MESSAGE_KEY = "message"; + protected static final String OWNER_ORG_KEY = "owner_org"; + + protected static final String RESULT_KEY = "result"; + protected static final String SUCCESS_KEY = "success"; + + protected static final String NOT_FOUND_ERROR = "Not Found Error"; + protected static final String AUTHORIZATION_ERROR = "Authorization Error"; + protected static final String VALIDATION_ERROR = "Validation Error"; + + // api rest path CKAN + public final static String CKAN_API_PATH = "/api/3/action/"; + + // ckan header authorization + public final static String AUTH_CKAN_HEADER = "Authorization"; + + protected String LIST; + protected String CREATE; + protected String READ; + protected String UPDATE; + protected String PATCH; + protected String DELETE; + protected String PURGE; + + protected final ObjectMapper mapper; + protected final DataCatalogue dataCatalogue; + + protected String name; + protected String apiKey; + + protected JsonNode result; + + + + public String getApiKey() { + if(apiKey==null) { + try { + return CKANUtility.getApiKey(); + }catch (Exception e) { + throw new InternalServerErrorException(e); + } + } + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ObjectMapper getMapper() { + return mapper; + } + + public JsonNode getJsonNodeResult() { + return result; + } + + protected CKAN() { + try { + this.mapper = new ObjectMapper(); + this.dataCatalogue = getCatalogue(); + }catch (Exception e) { + throw new InternalServerErrorException(e); + } + } + + protected JsonNode getAsJsonNode(String json) { + try { + return mapper.readTree(json); + } catch(IOException e) { + throw new BadRequestException(e); + } + } + + /** + * Retrieve an instance of the library for the current scope + * @return + * @throws Exception + */ + public static DataCatalogue getCatalogue() throws Exception { + String context = ContextUtility.getCurrentContext(); + logger.debug("Discovering ckan instance in context {}", context); + return DataCatalogueFactory.getFactory().getUtilsPerScope(context); + } + + + /** + * Validate the CKAN response and return the + * @param json + * @return + */ + protected JsonNode validateCKANResponse(String json) { + JsonNode jsonNode = getAsJsonNode(json); + if(jsonNode.get(SUCCESS_KEY).asBoolean()) { + return jsonNode.get(RESULT_KEY); + } else { + try { + JsonNode error = jsonNode.get(ERROR_KEY); + + String errorType = error.get(ERROR_TYPE_KEY).asText(); + + if(errorType.compareTo(VALIDATION_ERROR) == 0) { + throw new BadRequestException(getAsString(error)); + } + + + String message = error.get(MESSAGE_KEY).asText(); + + if(errorType.compareTo(NOT_FOUND_ERROR) == 0) { + throw new NotFoundException(message); + } + + if(errorType.compareTo(AUTHORIZATION_ERROR) == 0) { + throw new NotAuthorizedException(message); + } + + + + // TODO parse more cases + } catch(WebApplicationException e) { + throw e; + } catch(Exception e) { + throw new BadRequestException(json); + } + throw new BadRequestException(json); + } + } + + protected String getAsString(JsonNode node) { + try { + String json = mapper.writeValueAsString(node); + return json; + } catch(JsonProcessingException e) { + throw new InternalServerErrorException(e); + } + } + + protected JsonNode checkName(JsonNode jsonNode) { + try { + String gotName = jsonNode.get(NAME_KEY).asText(); + if(name==null) { + name = gotName; + } + + if(gotName != null && gotName.compareTo(name) != 0) { + String error = String.format( + "The name (%s) does not match with the '%s' contained in the provided content (%s).", + name, NAME_KEY, gotName); + throw new BadRequestException(error); + } + return jsonNode; + } catch(BadRequestException e) { + throw e; + } catch(Exception e) { + throw new BadRequestException("Unable to obtain a correct 'name' from the provided content"); + } + } + + protected JsonNode checkName(String json) { + JsonNode jsonNode = getAsJsonNode(json); + checkName(jsonNode); + return jsonNode; + } + + protected JsonNode createJsonNodeWithID(String id) { + ObjectNode objectNode = mapper.createObjectNode(); + objectNode.put(ID_KEY, id); + return objectNode; + } + + protected JsonNode createJsonNodeWithNameAsID() { + return createJsonNodeWithID(name); + } + + protected Map getMapWithNameAsID() { + return getMapWithID(name); + } + + protected Map getMapWithID(String id) { + Map map = new HashMap<>(); + map.put(ID_KEY, id); + return map; + } + + public JsonNode sendRequest(HTTPMETHOD httpMethod, String path, Map parameters, String body) { + String catalogueURL = dataCatalogue.getCatalogueUrl().endsWith("/") ? dataCatalogue.getCatalogueUrl() : dataCatalogue.getCatalogueUrl() + "/"; + HTTPCall httpCall = new HTTPCall(catalogueURL); + httpCall.setgCubeTargetService(false); + httpCall.addHeader(AUTH_CKAN_HEADER, getApiKey()); + String ret = httpCall.call(path, httpMethod, parameters, body, MediaType.APPLICATION_JSON); + JsonNode result = validateCKANResponse(ret); + if(result instanceof NullNode) { + result = mapper.createObjectNode(); + } + return result; + } + + public String sendGetRequest(String path, Map parameters) { + result = sendRequest(HTTPMETHOD.GET, path, parameters, null); + return getAsString(result); + } + + public String sendPostRequest(String path, String body) { + result = sendRequest(HTTPMETHOD.POST, path, null, body); + return getAsString(result); + } + + public String sendPostRequest(String path, JsonNode jsonNode) { + return sendPostRequest(path, getAsString(jsonNode)); + } + + public String list() { + return sendGetRequest(LIST, null); + } + + public String create(String json) { + return sendPostRequest(CREATE, json); + } + + public String read() { + return sendGetRequest(READ, getMapWithNameAsID()); + } + + public String update(String json) { + checkName(json); + return sendPostRequest(UPDATE, json); + } + + public String patch(String json) { + JsonNode jsonNode = checkName(json); + ObjectNode objectNode = ((ObjectNode) jsonNode); + objectNode.put(ID_KEY, name); + objectNode.remove(NAME_KEY); + return sendPostRequest(PATCH, objectNode); + } + + protected void delete() { + sendPostRequest(DELETE, createJsonNodeWithNameAsID()); + } + + public void delete(boolean purge) { + if(purge) { + purge(); + } else { + delete(); + } + } + + protected void purge() { + sendPostRequest(PURGE, createJsonNodeWithNameAsID()); + } + +} diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANGroup.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANGroup.java new file mode 100644 index 0000000..404f8c9 --- /dev/null +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANGroup.java @@ -0,0 +1,34 @@ +package org.gcube.gcat.persistence.ckan; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANGroup extends CKAN { + + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.group_list + public static final String GROUP_LIST = CKAN.CKAN_API_PATH + "group_list"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.group_create + public static final String GROUP_CREATE = CKAN.CKAN_API_PATH + "group_create"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.group_show + public static final String GROUP_SHOW = CKAN.CKAN_API_PATH + "group_show"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.group_update + public static final String GROUP_UPDATE = CKAN.CKAN_API_PATH + "group_update"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.patch.group_patch + public static final String GROUP_PATCH = CKAN.CKAN_API_PATH + "group_patch"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.group_delete + public static final String GROUP_DELETE = CKAN.CKAN_API_PATH + "group_delete"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.group_purge + public static final String GROUP_PURGE = CKAN.CKAN_API_PATH + "group_purge"; + + public CKANGroup() { + super(); + LIST = GROUP_LIST; + CREATE = GROUP_CREATE; + READ = GROUP_SHOW; + UPDATE = GROUP_UPDATE; + PATCH = GROUP_PATCH; + DELETE = GROUP_DELETE; + PURGE = GROUP_PURGE; + } + +} diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANLicense.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANLicense.java new file mode 100644 index 0000000..fc79808 --- /dev/null +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANLicense.java @@ -0,0 +1,16 @@ +package org.gcube.gcat.persistence.ckan; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANLicense extends CKAN { + + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.license_list + public static final String LICENSES_LIST = CKAN.CKAN_API_PATH + "license_list"; + + public CKANLicense() { + super(); + LIST = LICENSES_LIST; + } + +} diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANOrganization.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANOrganization.java new file mode 100644 index 0000000..9427fb2 --- /dev/null +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANOrganization.java @@ -0,0 +1,106 @@ +package org.gcube.gcat.persistence.ckan; + +import java.util.Map; +import java.util.Set; + +import org.gcube.common.scope.impl.ScopeBean; +import org.gcube.datacatalogue.ckanutillibrary.shared.RolesCkanGroupOrOrg; +import org.gcube.gcat.utils.ContextUtility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import eu.trentorise.opendata.jackan.model.CkanOrganization; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANOrganization extends CKAN { + + private static Logger logger = LoggerFactory.getLogger(CKANOrganization.class); + + // CKAN Connector sanitize the Organization name as following + //organizationName.replaceAll(" ", "_").replace(".", "_").toLowerCase() + + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.organization_list + public static final String ORGANIZATION_LIST = CKAN.CKAN_API_PATH + "organization_list"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.organization_create + public static final String ORGANIZATION_CREATE = CKAN.CKAN_API_PATH + "organization_create"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.organization_show + public static final String ORGANIZATION_SHOW = CKAN.CKAN_API_PATH + "organization_show"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.organization_update + public static final String ORGANIZATION_UPDATE = CKAN.CKAN_API_PATH + "organization_update"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.patch.organization_patch + public static final String ORGANIZATION_PATCH = CKAN.CKAN_API_PATH + "organization_patch"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.organization_delete + public static final String ORGANIZATION_DELETE = CKAN.CKAN_API_PATH + "organization_delete"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.organization_purge + public static final String ORGANIZATION_PURGE = CKAN.CKAN_API_PATH + "organization_purge"; + + // see https://docs.ckan.org/en/latest/api/#ckan.logic.action.create.organization_member_create + public static final String ORGANIZATION_MEMBER_CREATE = CKAN.CKAN_API_PATH + "organization_member_create"; + + // https://docs.ckan.org/en/latest/api/index.html#ckan.logic.action.get.organization_list_for_user + public static final String ORGANIZATION_LIST_FOR_USER = CKAN.CKAN_API_PATH + "organization_list_for_user"; + + protected static final String USERNAME_KEY = "username"; + protected static final String ROLE_KEY = "role"; + + + public CKANOrganization() { + super(); + LIST = ORGANIZATION_LIST; + CREATE = ORGANIZATION_CREATE; + READ = ORGANIZATION_SHOW; + UPDATE = ORGANIZATION_UPDATE; + PATCH = ORGANIZATION_PATCH; + DELETE = ORGANIZATION_DELETE; + PURGE = ORGANIZATION_PURGE; + } + + protected static final String ORGANIZATION_PERMISSION_KEY = "permission"; + protected static final String ORGANIZATION_PERMISSION_VALUE_READ = "read"; + + public String getUserRole(String gCubeUsername) { + Map> rolesPerOrganization = dataCatalogue + .getUserRoleByOrganization(gCubeUsername, CKANUtility.getApiKey(gCubeUsername)); + if(rolesPerOrganization.containsKey(name)) { + Map map = rolesPerOrganization.get(name); + Set ckanOrganizations = map.keySet(); + for(CkanOrganization ckanOrganization : ckanOrganizations) { + return map.get(ckanOrganization).name().toLowerCase(); + } + } + return null; + } + + public void addUserToOrganisation(String gCubeUsername, String role, boolean force) { + String ckanUsername = CKANUtility.getCKANUsername(gCubeUsername); + String userRole = getUserRole(gCubeUsername); + if((userRole==null || force)) { + if(userRole!=null && userRole.toLowerCase().compareTo(role.toLowerCase())==0) { + logger.debug("User {} is already member of Organisation {} with role {}", ckanUsername, name, userRole); + return; + } + ObjectNode objectNode = mapper.createObjectNode(); + objectNode.put(ID_KEY, name); + objectNode.put(USERNAME_KEY, ckanUsername); + objectNode.put(ROLE_KEY, role); + sendPostRequest(ORGANIZATION_MEMBER_CREATE, getAsString(objectNode)); + logger.debug("User {} successfully added to Organisation {} with role {}", ckanUsername, name, role); + }else { + logger.debug("User {} is already member of Organisation {} with role {}", ckanUsername, name, userRole); + } + } + + public static String getCKANOrganizationName() { + String context = ContextUtility.getCurrentContext(); + return getCKANOrganizationName(context); + } + + public static String getCKANOrganizationName(String context) { + ScopeBean scopeBean = new ScopeBean(context); + return scopeBean.name().toLowerCase(); + } +} diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANPackage.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANPackage.java new file mode 100644 index 0000000..47a84ea --- /dev/null +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANPackage.java @@ -0,0 +1,346 @@ +package org.gcube.gcat.persistence.ckan; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.NotAllowedException; +import javax.ws.rs.OPTIONS; +import javax.ws.rs.PUT; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.scope.impl.ScopeBean; +import org.gcube.common.scope.impl.ScopeBean.Type; +import org.gcube.gcat.annotation.PURGE; +import org.gcube.gcat.oldutils.Validator; +import org.gcube.gcat.social.SocialService; +import org.gcube.gcat.utils.ContextUtility; +import org.gcube.gcat.utils.URIResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANPackage extends CKAN { + + private static final Logger logger = LoggerFactory.getLogger(CKANPackage.class); + + // see https://docs.ckan.org/en/latest/api/#ckan.logic.action.get.package_list + public static final String ITEM_LIST = CKAN.CKAN_API_PATH + "package_list"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.package_create + public static final String ITEM_CREATE = CKAN.CKAN_API_PATH + "package_create"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.package_show + public static final String ITEM_SHOW = CKAN.CKAN_API_PATH + "package_show"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.package_update + public static final String ITEM_UPDATE = CKAN.CKAN_API_PATH + "package_update"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.patch.package_patch + public static final String ITEM_PATCH = CKAN.CKAN_API_PATH + "package_patch"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.package_delete + public static final String ITEM_DELETE = CKAN.CKAN_API_PATH + "package_delete"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.dataset_purge + public static final String ITEM_PURGE = CKAN.CKAN_API_PATH + "dataset_purge"; + + protected static final String LICENSE_KEY = "license_id"; + + protected static final String ITEM_URL_KEY = "item_url"; + protected static final String AUTHOR_KEY = "author"; + protected static final String AUTHOR_EMAIL_KEY = "author_email"; + protected static final String OWNER_ORG_KEY = "owner_org"; + protected static final String RESOURCES_KEY = "resources"; + protected static final String TITLE_KEY = "title"; + + public static final String EXTRA_TYPES_KEY = "extras"; + public static final String EXTRA_TYPES_KEY_KEY = "key"; + public static final String EXTRA_TYPES_KEY_VALUE_SYSTEM_TYPE = "system:type"; + public static final String EXTRA_TYPES_VALUE_KEY = "value"; + + public static final String GROUPS_KEY = "groups"; + public static final String TAGS_KEY = "tags"; + + protected final List managedResources; + + protected String itemID; + + public CKANPackage() { + super(); + LIST = ITEM_LIST; + CREATE = ITEM_CREATE; + READ = ITEM_SHOW; + UPDATE = ITEM_UPDATE; + PATCH = ITEM_PATCH; + DELETE = ITEM_DELETE; + PURGE = ITEM_PURGE; + managedResources = new ArrayList(); + } + + public ObjectNode checkBaseInformation(String json) throws Exception { + ObjectNode objectNode = (ObjectNode) mapper.readTree(json); + + objectNode = (ObjectNode) checkName(objectNode); + + // We need to enforce the itemID to properly manage resource persistence + if(objectNode.has(ID_KEY)) { + itemID = objectNode.get(ID_KEY).asText(); + } + + // check license + String licenseId = null; + if(objectNode.has(LICENSE_KEY)) { + licenseId = objectNode.get(LICENSE_KEY).asText(); + } + if(licenseId == null || licenseId.isEmpty()) { + throw new BadRequestException( + "You must specify a license identifier for the item. License list can be retrieved using licence collection"); + } + + JsonNode userJsonNode = CKANUtility.getCKANUser(); + objectNode.put(AUTHOR_KEY, userJsonNode.get(CKANUser.NAME).asText()); + objectNode.put(AUTHOR_EMAIL_KEY, userJsonNode.get(CKANUser.EMAIL).asText()); + + // owner organization must be specified if the token belongs to a VRE + ScopeBean scopeBean = new ScopeBean(ContextUtility.getCurrentContext()); + String contextName = scopeBean.name(); + + String gotOrganization = null; + if(objectNode.has(OWNER_ORG_KEY)) { + gotOrganization = objectNode.get(OWNER_ORG_KEY).asText(); + } + + if(scopeBean.is(Type.VRE)) { + String organizationFromContext = contextName.toLowerCase().replace(" ", "_"); + if(gotOrganization != null) { + if(gotOrganization.compareTo(organizationFromContext) != 0) { + CKANOrganization ckanOrganization = new CKANOrganization(); + ckanOrganization.setName(organizationFromContext); + ckanOrganization.read(); + String organizationID = null; + if(ckanOrganization.result.has(ID_KEY)) { + organizationID = ckanOrganization.result.get(ID_KEY).asText(); + } + if(organizationID == null || gotOrganization.compareTo(organizationID) != 0) { + throw new BadRequestException( + "You can only publish in the Organization associate to the current VRE"); + } + } + } else { + objectNode.put(OWNER_ORG_KEY, organizationFromContext); + } + } else { + // TODO check if the requested organization context is a sub context of current context + // TODO check if the check is correct for PARTHENOS + if(gotOrganization == null) { + throw new BadRequestException("You must specify an Organization usign " + OWNER_ORG_KEY + " field"); + } + } + + return objectNode; + } + + protected JsonNode validateJson(String json) { + try { + // check base information (and set them if needed) + ObjectNode objectNode = checkBaseInformation(json); + + // Validating against profiles if any + // List profiles = Profile.getProfilesNames(); TODO RESTORE THIS LINE + List profiles = null; // TODO REMOVE THIS LINE + if(profiles != null && !profiles.isEmpty()) { + Validator.validateAgainstProfile(getAsString(objectNode), profiles); + } + + return objectNode; + } catch(BadRequestException e) { + throw e; + } catch(Exception e) { + throw new BadRequestException(e); + } + } + + // see https://docs.ckan.org/en/latest/api/#ckan.logic.action.get.package_list + @Override + public String list() { + return super.list(); + } + + protected void rollbackManagedResources() { + for(CKANResource ckanResource : managedResources) { + try { + ckanResource.rollback(); + }catch (Exception e) { + logger.error("Unable to rollback resource {} to the original value", ckanResource.getResourceID()); + } + + } + } + + protected ArrayNode createResources(ArrayNode resourcesToBeCreated) { + ArrayNode created = mapper.createArrayNode(); + for(JsonNode resourceNode : resourcesToBeCreated) { + CKANResource ckanResource = new CKANResource(itemID); + String json = ckanResource.create(getAsString(resourceNode)); + created.add(getAsJsonNode(json)); + managedResources.add(ckanResource); + } + return created; + } + + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.package_create + @Override + public String create(String json) { + try { + logger.debug("Going to create Item {}", json); + + JsonNode jsonNode = validateJson(json); + + ArrayNode resourcesToBeCreated = mapper.createArrayNode(); + if(jsonNode.has(RESOURCES_KEY)) { + resourcesToBeCreated = (ArrayNode) jsonNode.get(RESOURCES_KEY); + ((ObjectNode) jsonNode).remove(RESOURCES_KEY); + } + + super.create(getAsString(jsonNode)); + + this.itemID = result.get(ID_KEY).asText(); + ArrayNode created = createResources(resourcesToBeCreated); + ((ObjectNode) result).replace(RESOURCES_KEY, created); + + // Adding Item URL via Resolver + URIResolver uriResolver = new URIResolver(); + String catalogueItemURL = uriResolver.getCatalogueItemURL(name); + ((ObjectNode) result).put(ITEM_URL_KEY, catalogueItemURL); + + // Actions performed after a package has been correctly created on ckan. + String title = result.get(TITLE_KEY).asText(); + + ArrayNode arrayNode = (ArrayNode) result.get(TAGS_KEY); + SocialService packagePostActions = new SocialService(catalogueItemURL, name, arrayNode, title); + packagePostActions.start(); + + return getAsString(result); + } catch(WebApplicationException e) { + rollbackManagedResources(); + throw e; + } catch(Exception e) { + rollbackManagedResources(); + throw new InternalServerErrorException(e); + } + } + + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.package_update + @Override + public String update(String json) { + try { + JsonNode jsonNode = validateJson(json); + this.itemID = jsonNode.get(ID_KEY).asText(); + + read(); + + Map originalResources = new HashMap<>(); + ArrayNode originalResourcesarrayNode = (ArrayNode) result.get(RESOURCES_KEY); + if(originalResources!=null) { + for(JsonNode resourceNode : originalResourcesarrayNode) { + CKANResource ckanResource = new CKANResource(itemID); + ckanResource.setPreviousRepresentation(resourceNode); + String resourceID = ckanResource.getResourceID(); + originalResources.put(resourceID, ckanResource); + } + } + + + + if(jsonNode.has(RESOURCES_KEY)) { + ArrayNode resourcesToBeSend = mapper.createArrayNode(); + ArrayNode receivedResources = (ArrayNode) jsonNode.get(RESOURCES_KEY); + for(JsonNode resourceNode : receivedResources) { + CKANResource ckanResource = new CKANResource(itemID); + String resourceId = CKANResource.extractResourceID(resourceNode); + if(resourceId!=null) { + if(originalResources.containsKey(resourceId)) { + ckanResource = originalResources.get(resourceId); + originalResources.remove(resourceId); + }else { + throw new BadRequestException("The content cotains a resource with id " + resourceId + " which does not exists") ; + } + } + resourceNode = ckanResource.createOrUpdate(resourceNode); + resourcesToBeSend.add(resourceNode); + managedResources.add(ckanResource); + + } + ((ObjectNode) jsonNode).replace(RESOURCES_KEY, resourcesToBeSend); + } + + sendPostRequest(ITEM_UPDATE, getAsString(jsonNode)); + + for(String resourceId : originalResources.keySet()) { + CKANResource ckanResource = originalResources.get(resourceId); + ckanResource.deleteFile(); + } + + // Adding Item URL via Resolver + URIResolver uriResolver = new URIResolver(); + String catalogueItemURL = uriResolver.getCatalogueItemURL(name); + ((ObjectNode) result).put(ITEM_URL_KEY, catalogueItemURL); + + return getAsString(result); + } catch(WebApplicationException e) { + rollbackManagedResources(); + throw e; + } catch(Exception e) { + rollbackManagedResources(); + throw new InternalServerErrorException(e); + } + } + + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.patch.package_patch + @Override + public String patch(String json) { + String[] moreAllowed = new String[] {HEAD.class.getSimpleName(), GET.class.getSimpleName(), + PUT.class.getSimpleName(), DELETE.class.getSimpleName(), PURGE.class.getSimpleName()}; + throw new NotAllowedException(OPTIONS.class.getSimpleName(), moreAllowed); + } + + @Override + protected void delete() { + super.delete(); + } + + @Override + public void purge() { + try { + delete(); + } catch(WebApplicationException e) { + // If the item was deleted but not purged we obtain Not Found. This is accepted. The item has to be purged + // with SysAdmin right. + Status status = Status.fromStatusCode(e.getResponse().getStatusInfo().getStatusCode()); + if(status != Status.NOT_FOUND) { + throw e; + } + } + setApiKey(CKANUtility.getSysAdminAPI()); + read(); + if(result.has(RESOURCES_KEY)) { + itemID = result.get(ID_KEY).asText(); + ArrayNode arrayNode = (ArrayNode) result.get(RESOURCES_KEY); + for(JsonNode jsonNode : arrayNode) { + CKANResource ckanResource = new CKANResource(itemID); + ckanResource.setPreviousRepresentation(jsonNode); + ckanResource.deleteFile(); + } + } + super.purge(); + } + +} diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANResource.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANResource.java new file mode 100644 index 0000000..b112aef --- /dev/null +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANResource.java @@ -0,0 +1,409 @@ +package org.gcube.gcat.persistence.ckan; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.UUID; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.NotAllowedException; +import javax.ws.rs.OPTIONS; +import javax.ws.rs.PUT; +import javax.ws.rs.WebApplicationException; + +import org.gcube.gcat.utils.ApplicationMode; +import org.gcube.gcat.utils.ContextUtility; +import org.gcube.gcat.utils.HTTPCall; +import org.gcube.gcat.workspace.StorageHubManagement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANResource extends CKAN { + + private static final Logger logger = LoggerFactory.getLogger(CKANResource.class); + + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.resource_create + public static final String RESOURCE_CREATE = CKAN.CKAN_API_PATH + "resource_create"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.resource_show + public static final String RESOURCE_SHOW = CKAN.CKAN_API_PATH + "resource_show"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.resource_update + public static final String RESOURCE_UPDATE = CKAN.CKAN_API_PATH + "resource_update"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.patch.resource_patch + public static final String RESOURCE_PATCH = CKAN.CKAN_API_PATH + "resource_patch"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.resource_delete + public static final String RESOURCE_DELETE = CKAN.CKAN_API_PATH + "resource_delete"; + + /* TODO Remove this code ASAP. It requires a function from Storage HUB */ + private static final String URI_RESOLVER_STORAGE_HUB_HOST_PROD = "data.d4science.org"; + private static final String URI_RESOLVER_STORAGE_HUB_HOST_DEV = "data-d.d4science.org"; + + public static final String URI_RESOLVER_STORAGE_HUB_HOST; + public static final String URI_RESOLVER_STORAGE_HUB_PATH = "/shub/"; + + protected static final String URL_KEY = "url"; + + private static final String RESOURCES_KEY = "resources"; + private static final String PACKAGE_ID_KEY = "package_id"; + private static final String MIME_TYPE_KEY = "mimetype"; + private static final String REVISION_ID_KEY = "revision_id"; + + private static final String TEMP = "TEMP_"; + + + static { + String context = ContextUtility.getCurrentContext(); + if(context.startsWith("/gcube")) { + URI_RESOLVER_STORAGE_HUB_HOST = URI_RESOLVER_STORAGE_HUB_HOST_DEV; + } else { + URI_RESOLVER_STORAGE_HUB_HOST = URI_RESOLVER_STORAGE_HUB_HOST_PROD; + } + + } + /* TODO END Code to be Removed */ + + protected String itemID; + + public String getItemID() { + return itemID; + } + + protected String resourceID; + + protected Boolean persisted; + protected URL persistedURL; + + protected String mimeType; + + protected JsonNode previousRepresentation; + + protected StorageHubManagement storageHubManagement; + + public URL getPersistedURL() { + return persistedURL; + } + + public static String extractResourceID(JsonNode jsonNode) { + String resourceID = null; + if(jsonNode.has(ID_KEY)) { + resourceID = jsonNode.get(ID_KEY).asText(); + } + return resourceID; + } + + public String getResourceID() { + if(resourceID==null && previousRepresentation!=null) { + resourceID = CKANResource.extractResourceID(previousRepresentation); + } + return resourceID; + } + + public void setResourceID(String resourceID) { + this.resourceID = resourceID; + } + + public void setPreviousRepresentation(JsonNode jsonNode) { + previousRepresentation = jsonNode; + } + + public JsonNode getPreviousRepresentation() { + if(previousRepresentation==null && resourceID!=null) { + super.read(); + validate(result); + previousRepresentation = result; + } + return previousRepresentation; + } + + public CKANResource(String itemID) { + super(); + this.itemID = itemID; + CREATE = RESOURCE_CREATE; + READ = RESOURCE_SHOW; + UPDATE = RESOURCE_UPDATE; + PATCH = RESOURCE_PATCH; + DELETE = RESOURCE_DELETE; + PURGE = null; + persisted = null; + previousRepresentation = null; + } + + @Override + public String list() { + CKANPackage ckanPackage = new CKANPackage(); + ckanPackage.setName(itemID); + String itemJson = ckanPackage.read(); + JsonNode item = getAsJsonNode(itemJson); + JsonNode resources = item.get(RESOURCES_KEY); + return getAsString(resources); + } + + protected ObjectNode persistStorageFile(ObjectNode objectNode) { + + if(objectNode.has(URL_KEY)) { + String urlString = objectNode.get(URL_KEY).asText(); + + URL url; + try { + url = new URL(urlString); + } catch(MalformedURLException e) { + throw new BadRequestException(e); + } + + url = copyStorageResource(url); + + if(mimeType!=null) { + objectNode.put(MIME_TYPE_KEY, mimeType); + } + + objectNode.put(URL_KEY, url.toString()); + return objectNode; + } + + String error = String.format("The content must contains the %s property", URL_KEY); + throw new BadRequestException(error); + + } + + protected ObjectNode validate(String json) throws MalformedURLException { + JsonNode jsonNode = getAsJsonNode(json); + return validate(jsonNode); + } + + protected ObjectNode validate(JsonNode jsonNode) { + + ObjectNode objectNode = (ObjectNode) jsonNode; + + if(objectNode.has(PACKAGE_ID_KEY)) { + String packageId = objectNode.get(PACKAGE_ID_KEY).asText(); + if(packageId.compareTo(itemID) != 0) { + String error = String.format( + "Item ID %s does not match %s which is the value of %s contained in the representation.", + itemID, packageId, PACKAGE_ID_KEY); + throw new BadRequestException(error); + } + } else { + objectNode.put(PACKAGE_ID_KEY, itemID); + } + + if(objectNode.has(ID_KEY)) { + String gotId = objectNode.get(ID_KEY).asText(); + if(resourceID == null) { + resourceID = gotId; + } else { + if(resourceID.compareTo(gotId) != 0) { + String error = String.format( + "Resource ID %s does not match %s which is the value of %s contained in the representation.", + resourceID, gotId, ID_KEY); + throw new BadRequestException(error); + } + } + } else { + resourceID = TEMP + UUID.randomUUID().toString(); + logger.trace( + "The id of the resource with name {} for package {} has not been provided. It has been generated : {}", + name, itemID, resourceID); + } + + return objectNode; + } + + protected URL getFinalURL(String url) { + try { + URL urlURL = new URL(url); + return getFinalURL(urlURL); + } catch(MalformedURLException e) { + throw new BadRequestException(e); + } + } + + protected URL getFinalURL(URL url) { + HTTPCall httpCall = new HTTPCall(url.toString()); + httpCall.setgCubeTargetService(false); + URL finalURL = httpCall.getFinalURL(url); + return finalURL; + } + + protected boolean isStorageFile(URL url) { + if(url.getHost().compareTo(URI_RESOLVER_STORAGE_HUB_HOST) == 0) { + if(url.getPath().startsWith(URI_RESOLVER_STORAGE_HUB_PATH)) { + return true; + } + } + return false; + } + + /** + * Check if the URl is a workspace URL so that is has to copy the resource to guarantee + * the resource remain persistent + * @param url the URL to check + * @return the public URL of the copied resource if any. It return the original URL otherwise + */ + protected URL copyStorageResource(URL url) { + persistedURL = getFinalURL(url); + if(isStorageFile(persistedURL)) { + ApplicationMode applicationMode = new ApplicationMode(); + applicationMode.start(); + storageHubManagement = new StorageHubManagement(); + persistedURL = storageHubManagement.ensureResourcePersistence(persistedURL, itemID, resourceID, name); + mimeType = storageHubManagement.getMimeType(); + persisted = true; + applicationMode.end(); + } + return persistedURL; + } + + protected void deleteStorageResource(URL url) { + persistedURL = getFinalURL(url); + if(isStorageFile(persistedURL)) { + ApplicationMode applicationMode = new ApplicationMode(); + applicationMode.start(); + storageHubManagement = new StorageHubManagement(); + storageHubManagement.deleteResourcePersistence(persistedURL, itemID); + applicationMode.end(); + } + } + + public boolean isPersisted() { + if(persisted == null) { + persistedURL = getFinalURL(persistedURL); + if(isStorageFile(persistedURL)) { + ApplicationMode applicationMode = new ApplicationMode(); + applicationMode.start(); + storageHubManagement = new StorageHubManagement(); + persisted = storageHubManagement.isItemPersistedFile(persistedURL, itemID); + applicationMode.end(); + } + } + return persisted; + } + + protected String create(JsonNode jsonNode) { + try { + ObjectNode objectNode = validate(jsonNode); + objectNode = persistStorageFile(objectNode); + String ret = super.create(getAsString(objectNode)); + String gotResourceID = result.get(ID_KEY).asText(); + if(gotResourceID.compareTo(resourceID)!=0) { + resourceID = gotResourceID; + String revisionID = result.get(REVISION_ID_KEY).asText(); + storageHubManagement.renameFile(resourceID, revisionID); + } + return ret; + } catch(WebApplicationException e) { + throw e; + } catch(Exception e) { + throw new InternalServerErrorException(e); + } + } + + @Override + public String create(String json) { + JsonNode jsonNode = getAsJsonNode(json); + return create(jsonNode); + } + + @Override + public String read() { + return sendGetRequest(READ, getMapWithID(resourceID)); + } + + + protected String update(JsonNode jsonNode) { + ObjectNode resourceNode = (ObjectNode) jsonNode; + // This cannot be moved outside otherwise we don't + resourceNode = validate(resourceNode); + + getPreviousRepresentation(); + + String oldURL = previousRepresentation.get(CKANResource.URL_KEY).asText(); + String newURL = resourceNode.get(CKANResource.URL_KEY).asText(); + if(oldURL.compareTo(newURL)==0) { + logger.trace("The URL of the resource with id {} was not changed", resourceID); + }else { + logger.trace("The URL of resource with id {} has been changed the old URL was {}, the new URL is {}", resourceID, oldURL, newURL); + resourceNode = persistStorageFile(resourceNode); + /* + try { + URL urlOLD = new URL(oldURL); + deleteStorageResource(urlOLD); + }catch (Exception e) { + logger.error("Unable to remove old file at URL {}", oldURL); + } + */ + } + String ret = super.update(getAsString(resourceNode)); + String revisionID = result.get(REVISION_ID_KEY).asText(); + storageHubManagement.addRevisionID(resourceID, revisionID); + return ret; + } + + @Override + public String update(String json) { + JsonNode jsonNode = getAsJsonNode(json); + return update(jsonNode); + } + + @Override + public String patch(String json) { + String[] moreAllowed = new String[] {HEAD.class.getSimpleName(), GET.class.getSimpleName(), + PUT.class.getSimpleName(), DELETE.class.getSimpleName()}; + throw new NotAllowedException(OPTIONS.class.getSimpleName(), moreAllowed); + } + + @Override + public void delete() { + try { + deleteFile(); + super.delete(); + } catch(WebApplicationException e) { + throw e; + } catch(Exception e) { + throw new WebApplicationException(e); + } + } + + @Override + protected void purge() { + String[] moreAllowed = new String[] {HEAD.class.getSimpleName(), GET.class.getSimpleName(), + PUT.class.getSimpleName(), DELETE.class.getSimpleName()}; + throw new NotAllowedException(OPTIONS.class.getSimpleName(), moreAllowed); + } + + public JsonNode createOrUpdate(JsonNode jsonNode) { + ObjectNode resourceNode = (ObjectNode) jsonNode; + if(resourceNode.has(ID_KEY)) { + update(resourceNode); + }else { + create(resourceNode); + } + return result; + } + + public void deleteFile() { + try { + getPreviousRepresentation(); + URL url = new URL(previousRepresentation.get(URL_KEY).asText()); + deleteStorageResource(url); + } catch(Exception e) { + logger.error("Unable to delete resource {}", previousRepresentation!=null ? getAsString(previousRepresentation) : ""); + } + } + + public void rollback() { + if(previousRepresentation!=null) { + update(previousRepresentation); + }else { + delete(); + } + } + +} diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANUser.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANUser.java new file mode 100644 index 0000000..257c214 --- /dev/null +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANUser.java @@ -0,0 +1,54 @@ +package org.gcube.gcat.persistence.ckan; + +import org.gcube.gcat.utils.RandomString; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANUser extends CKAN { + + /* User Paths */ + // see https://docs.ckan.org/en/latest/api/#ckan.logic.action.get.user_list + public static final String USER_LIST = CKAN.CKAN_API_PATH + "user_list"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.create.user_create + public static final String USER_CREATE = CKAN.CKAN_API_PATH + "user_create"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.get.user_show + public static final String USER_SHOW = CKAN.CKAN_API_PATH + "user_show"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.update.user_update + public static final String USER_UPDATE = CKAN.CKAN_API_PATH + "user_update"; + // see http://docs.ckan.org/en/latest/api/#ckan.logic.action.delete.user_delete + public static final String USER_DELETE = CKAN.CKAN_API_PATH + "user_delete"; + + + public CKANUser() { + super(); + LIST = USER_LIST; + CREATE = USER_CREATE; + READ = USER_SHOW; + UPDATE = USER_UPDATE; + PATCH = null; + DELETE = USER_DELETE; + PURGE = null; + } + + public static final String NAME = "name"; + public static final String EMAIL = "email"; + public static final String PASSWORD = "password"; + + public String create() { + RandomString randomString = new RandomString(12); + ObjectNode objectNode = mapper.createObjectNode(); + objectNode.put(NAME, name); + objectNode.put(EMAIL, name+"@gcube.ckan.org"); + objectNode.put(PASSWORD, randomString.nextString()); + return create(getAsString(objectNode)); + } + + @Override + public void delete(boolean purge) { + this.delete(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/gcat/persistence/ckan/CKANUtility.java b/src/main/java/org/gcube/gcat/persistence/ckan/CKANUtility.java new file mode 100644 index 0000000..fe38a89 --- /dev/null +++ b/src/main/java/org/gcube/gcat/persistence/ckan/CKANUtility.java @@ -0,0 +1,101 @@ +package org.gcube.gcat.persistence.ckan; + +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response.Status; + +import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue; +import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueFactory; +import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueRunningCluster; +import org.gcube.datacatalogue.ckanutillibrary.server.utils.CatalogueUtilMethods; +import org.gcube.gcat.utils.ContextUtility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANUtility { + + private static final Logger logger = LoggerFactory.getLogger(CKANUtility.class); + + public static final String MEMBER_ROLE = "member"; + private static final String API_KEY = "apikey"; + + public static DataCatalogue getCatalogue() throws Exception { + String context = ContextUtility.getCurrentContext(); + logger.debug("Discovering ckan instance in context {}", context); + return DataCatalogueFactory.getFactory().getUtilsPerScope(context); + } + + public static String getSysAdminAPI() { + try { + DataCatalogueRunningCluster catalogueRunningInstance = new DataCatalogueRunningCluster( + ContextUtility.getCurrentContext()); + return catalogueRunningInstance.getSysAdminToken(); + } catch(Exception e) { + throw new InternalServerErrorException(e); + } + } + + protected static String getCKANUsername(String username) { + String ckanUsername = CatalogueUtilMethods.fromUsernameToCKanUsername(username); + return ckanUsername; + } + + protected static String getCKANUsername() { + return getCKANUsername(ContextUtility.getUsername()); + } + + public static JsonNode getCKANUser() { + return createCKANUser(getCKANUsername()); + } + + protected static JsonNode createCKANUser(String ckanUsername) { + CKANUser ckanUser = new CKANUser(); + ckanUser.setApiKey(getSysAdminAPI()); + try { + ckanUser.setName(ckanUsername); + ckanUser.read(); + } catch(WebApplicationException e) { + if(e.getResponse().getStatusInfo() == Status.NOT_FOUND) { + ckanUser.setName(ckanUsername); + ckanUser.create(); + }else { + throw e; + } + } + JsonNode jsonNode = ckanUser.getJsonNodeResult(); + addUserToOrganization(ckanUsername, MEMBER_ROLE, false); + return jsonNode; + } + + protected static void addUserToOrganization(String ckanUsername, String role, boolean force) { + CKANOrganization ckanOrganization = new CKANOrganization(); + ckanOrganization.setApiKey(getSysAdminAPI()); + String organizationName = CKANOrganization.getCKANOrganizationName(); + ckanOrganization.setName(organizationName); + ckanOrganization.addUserToOrganisation(ContextUtility.getUsername(), role, force); + } + + public static String getApiKey() throws Exception { + String ckanUsername = getCKANUsername(); + return getApiKey(ckanUsername); + } + + protected static String getApiKey(String ckanUsername) { + try { + String apiKey = getCatalogue().getApiKeyFromUsername(ckanUsername); + if(apiKey == null) { + JsonNode jsonNode = createCKANUser(ckanUsername); + apiKey = jsonNode.get(API_KEY).asText(); + } + return apiKey; + }catch (Exception e) { + throw new InternalServerErrorException(e); + } + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/BaseREST.java b/src/main/java/org/gcube/gcat/rest/BaseREST.java new file mode 100644 index 0000000..2bad7cf --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/BaseREST.java @@ -0,0 +1,45 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.gcube.common.authorization.library.provider.CalledMethodProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaseREST { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Context + private UriInfo uriInfo; + + public static final String GROUPS = "groups"; + public static final String ITEMS = "items"; + public static final String LICENSES = "licenses"; + public static final String NAMESPACES = "namespaces"; + public static final String ORGANIZATIONS = "organizations"; + public static final String PROFILES = "profiles"; + public static final String RESOURCES = "resources"; + public static final String USERS = "users"; + + public static final String PURGE_QUERY_PARAMETER = "purge"; + + public static final String APPLICATION_JSON_CHARSET_UTF_8 = MediaType.APPLICATION_JSON + ";charset=UTF-8"; + + protected static final String LOCATION_HEADER = "Location"; + + protected void setCalledMethod(String method) { + CalledMethodProvider.instance.set(method); + logger.info("{}", uriInfo.getAbsolutePath()); + } + + protected ResponseBuilder addLocation(ResponseBuilder responseBuilder, String id) { + return responseBuilder.header(LOCATION_HEADER, + String.format("%s/%s", uriInfo.getAbsolutePath().toString(), id) + ); + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/Group.java b/src/main/java/org/gcube/gcat/rest/Group.java new file mode 100644 index 0000000..21c2ec4 --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/Group.java @@ -0,0 +1,92 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.gcube.gcat.ResourceInitializer; +import org.gcube.gcat.annotation.PATCH; +import org.gcube.gcat.annotation.PURGE; +import org.gcube.gcat.persistence.ckan.CKANGroup; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Path(BaseREST.GROUPS) +public class Group extends REST { + + protected static final String GROUP_ID_PARAMETER = "GROUP_ID"; + + public Group() { + super(BaseREST.GROUPS, GROUP_ID_PARAMETER, CKANGroup.class); + } + + @GET + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String list() { + return super.list(); + } + + @POST + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response create(String json) { + return super.create(json); + } + + @GET + @Path("/{" + GROUP_ID_PARAMETER + "}") + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String read(@PathParam(GROUP_ID_PARAMETER) String id) { + return super.read(id); + } + + @PUT + @Path("/{" + GROUP_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String update(@PathParam(GROUP_ID_PARAMETER) String id, String json) { + return super.update(id, json); + } + + @PATCH + @Path("/{" + GROUP_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String patch(@PathParam(GROUP_ID_PARAMETER) String id, String json) { + return super.patch(id, json); + } + + @DELETE + @Path("/{" + GROUP_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response delete(@PathParam(GROUP_ID_PARAMETER) String id, + @QueryParam(BaseREST.PURGE_QUERY_PARAMETER) @DefaultValue("false") Boolean purge) { + return super.delete(id, purge); + } + + @PURGE + @Path("/{" + GROUP_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response purge(@PathParam(GROUP_ID_PARAMETER) String id) { + return delete(id, true); + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/Item.java b/src/main/java/org/gcube/gcat/rest/Item.java new file mode 100644 index 0000000..7cafb16 --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/Item.java @@ -0,0 +1,93 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.gcube.gcat.ResourceInitializer; +import org.gcube.gcat.annotation.PURGE; +import org.gcube.gcat.persistence.ckan.CKANPackage; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Path(BaseREST.ITEMS) +public class Item extends REST { + + public static final String ITEM_ID_PARAMETER = "ITEM_ID"; + + public Item() { + super(BaseREST.ITEMS, ITEM_ID_PARAMETER, CKANPackage.class); + } + + @GET + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String list() { + return super.list(); + } + + @POST + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response create(String json) { + return super.create(json); + } + + @GET + @Path("/{" + ITEM_ID_PARAMETER + "}") + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String read(@PathParam(ITEM_ID_PARAMETER) String id) { + return super.read(id); + } + + @PUT + @Path("/{" + ITEM_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String update(@PathParam(ITEM_ID_PARAMETER) String id, String json) { + return super.update(id, json); + } + + /* + @PATCH + @Path("/{" + ITEM_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String patch(@PathParam(ITEM_ID_PARAMETER) String id, String json) { + return super.patch(id, json); + } + */ + + @DELETE + @Path("/{" + ITEM_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response delete(@PathParam(ITEM_ID_PARAMETER) String id, + @QueryParam(BaseREST.PURGE_QUERY_PARAMETER) @DefaultValue("false") Boolean purge) { + return super.delete(id, purge); + } + + @PURGE + @Path("/{" + ITEM_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response purge(@PathParam(ITEM_ID_PARAMETER) String id) { + return super.purge(id); + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/License.java b/src/main/java/org/gcube/gcat/rest/License.java new file mode 100644 index 0000000..6213fe6 --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/License.java @@ -0,0 +1,27 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.gcube.gcat.ResourceInitializer; +import org.gcube.gcat.persistence.ckan.CKANLicense; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Path(BaseREST.LICENSES) +public class License extends REST { + + public License() { + super(BaseREST.LICENSES, null, CKANLicense.class); + } + + @GET + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String list() { + return super.list(); + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/Namespace.java b/src/main/java/org/gcube/gcat/rest/Namespace.java new file mode 100644 index 0000000..82398dd --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/Namespace.java @@ -0,0 +1,60 @@ +package org.gcube.gcat.rest; + +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.gcube.datacatalogue.metadatadiscovery.DataCalogueMetadataFormatReader; +import org.gcube.datacatalogue.metadatadiscovery.bean.jaxb.NamespaceCategory; +import org.gcube.gcat.ResourceInitializer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Path(BaseREST.NAMESPACES) +public class Namespace extends BaseREST { + + /** + * Returns the categories. + * @param context + * @return + * @throws Exception + */ + public static List getNamespaceCategories() throws Exception { + DataCalogueMetadataFormatReader reader = Profile.getDataCalogueMetadataFormatReader(); + return reader.getListOfNamespaceCategories(); + + } + + @GET + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public String list() { + setCalledMethod("GET /" + BaseREST.NAMESPACES); + + ObjectMapper mapper = new ObjectMapper(); + ArrayNode arrayNode = mapper.createArrayNode(); + + try { + List namespaces = Namespace.getNamespaceCategories(); + for(NamespaceCategory namespaceCategory : namespaces) { + ObjectNode namespace = mapper.createObjectNode(); + namespace.put("id", namespaceCategory.getId()); + namespace.put("title", namespaceCategory.getTitle()); + namespace.put("name", namespaceCategory.getNamespaceCategoryQName()); + namespace.put("description", namespaceCategory.getDescription()); + arrayNode.add(namespace); + } + return mapper.writeValueAsString(arrayNode); + } catch(Exception e) { + throw new InternalServerErrorException(e.getMessage()); + } + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/Organization.java b/src/main/java/org/gcube/gcat/rest/Organization.java new file mode 100644 index 0000000..7ac0bf8 --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/Organization.java @@ -0,0 +1,91 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.gcube.gcat.ResourceInitializer; +import org.gcube.gcat.annotation.PATCH; +import org.gcube.gcat.annotation.PURGE; +import org.gcube.gcat.persistence.ckan.CKANOrganization; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Path(BaseREST.ORGANIZATIONS) +public class Organization extends REST { + + public static final String ORGANIZATION_ID_PARAMETER = "ORGANIZATION_ID"; + + public Organization() { + super(BaseREST.ORGANIZATIONS, ORGANIZATION_ID_PARAMETER, CKANOrganization.class); + } + + @GET + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String list() { + return super.list(); + } + + @POST + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response create(String json) { + return super.create(json); + } + + @GET + @Path("/{" + ORGANIZATION_ID_PARAMETER + "}") + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String read(@PathParam(ORGANIZATION_ID_PARAMETER) String id) { + return super.read(id); + } + + @PUT + @Path("/{" + ORGANIZATION_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String update(@PathParam(ORGANIZATION_ID_PARAMETER) String id, String json) { + return super.update(id, json); + } + + @PATCH + @Path("/{" + ORGANIZATION_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String patch(@PathParam(ORGANIZATION_ID_PARAMETER) String id, String json) { + return super.patch(id, json); + } + + @DELETE + @Path("/{" + ORGANIZATION_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response delete(@PathParam(ORGANIZATION_ID_PARAMETER) String id, + @QueryParam(BaseREST.PURGE_QUERY_PARAMETER) @DefaultValue("false") Boolean purge) { + return super.delete(id, purge); + } + + @PURGE + @Path("/{" + ORGANIZATION_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public Response purge(@PathParam(ORGANIZATION_ID_PARAMETER) String id) { + return super.purge(id); + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/Profile.java b/src/main/java/org/gcube/gcat/rest/Profile.java new file mode 100644 index 0000000..8b243a7 --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/Profile.java @@ -0,0 +1,137 @@ +package org.gcube.gcat.rest; + +import java.util.ArrayList; +import java.util.List; + +import javax.cache.Cache; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.datacatalogue.metadatadiscovery.DataCalogueMetadataFormatReader; +import org.gcube.datacatalogue.metadatadiscovery.bean.MetadataProfile; +import org.gcube.gcat.ResourceInitializer; +import org.gcube.gcat.oldutils.CachesManager; +import org.json.JSONObject; +import org.json.XML; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Path(BaseREST.PROFILES) +public class Profile extends BaseREST { + + public static final String PROFILE_NAME_PARAMETER = "PROFILE_NAME"; + + public static DataCalogueMetadataFormatReader getDataCalogueMetadataFormatReader() throws Exception { + Cache readerCache = CachesManager.getReaderCache(); + String context = ScopeProvider.instance.get(); + DataCalogueMetadataFormatReader reader; + if(readerCache.containsKey(context)) + reader = (DataCalogueMetadataFormatReader) readerCache.get(context); + else { + reader = new DataCalogueMetadataFormatReader(); + readerCache.put(context, reader); + } + return reader; + } + + /** + * Returns the names of the metadata profiles in a given context + * @param context + * @return + * @throws Exception + */ + public static List getProfilesNames() throws Exception { + + DataCalogueMetadataFormatReader reader = getDataCalogueMetadataFormatReader(); + + List toReturn = new ArrayList(); + List listProfiles = reader.getListOfMetadataProfiles(); + + if(listProfiles != null && !listProfiles.isEmpty()) { + for(MetadataProfile profile : listProfiles) { + toReturn.add(profile.getName()); + } + } + + return toReturn; + } + + /** + * Returns the source xml of the metadata profile (specified via name) in a given context + * @param context + * @return + * @throws Exception + */ + public static String getProfileSource(String profileName) throws Exception { + + DataCalogueMetadataFormatReader reader = Profile.getDataCalogueMetadataFormatReader(); + + List listProfiles = reader.getListOfMetadataProfiles(); + String xmlToReturn = null; + + if(listProfiles != null && !listProfiles.isEmpty()) { + for(MetadataProfile profile : listProfiles) { + if(profile.getName().equals(profileName)) { + xmlToReturn = reader.getMetadataFormatForMetadataProfile(profile).getMetadataSource(); + break; + } + } + } + + return xmlToReturn; + } + + @GET + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public String list() { + setCalledMethod("GET /" + BaseREST.PROFILES); + + ObjectMapper mapper = new ObjectMapper(); + ArrayNode arrayNode = mapper.createArrayNode(); + + try { + List names = getProfilesNames(); + for(String name : names) { + arrayNode.add(name); + } + return mapper.writeValueAsString(arrayNode); + } catch(Exception e) { + throw new InternalServerErrorException(e.getMessage()); + } + } + + public static int PRETTY_PRINT_INDENT_FACTOR = 4; + + @GET + @Path("/{" + PROFILE_NAME_PARAMETER + "}") + @Produces({MediaType.APPLICATION_XML, ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8}) + public String read(@PathParam(PROFILE_NAME_PARAMETER) String id, + @DefaultValue(MediaType.APPLICATION_JSON) @HeaderParam("Accept") String accept) { + setCalledMethod("GET /" + BaseREST.PROFILES + "/{" + PROFILE_NAME_PARAMETER + "}"); + try { + String profile = Profile.getProfileSource(id); + if(accept.startsWith(MediaType.APPLICATION_XML)){ + return profile; + }else { + JSONObject xmlJSONObj = XML.toJSONObject(profile); + String jsonString = xmlJSONObj.toString(PRETTY_PRINT_INDENT_FACTOR); + return jsonString; + } + } catch(Exception e) { + throw new InternalServerErrorException(e.getMessage()); + } + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/REST.java b/src/main/java/org/gcube/gcat/rest/REST.java new file mode 100644 index 0000000..0ef15c4 --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/REST.java @@ -0,0 +1,87 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; + +import org.gcube.gcat.ResourceInitializer; +import org.gcube.gcat.persistence.ckan.CKAN; + +public class REST extends BaseREST { + + protected final String COLLECTION_PARAMETER; + protected final String ID_PARAMETER; + protected final Class reference; + + public REST(String collection_name, String id_name, Class reference) { + this.COLLECTION_PARAMETER = collection_name; + this.ID_PARAMETER = id_name; + this.reference = reference; + } + + protected C getInstance() { + try { + C ckan = reference.newInstance(); + return ckan; + } catch(Exception e) { + throw new InternalServerErrorException(); + } + } + + public String list() { + setCalledMethod("GET /" + COLLECTION_PARAMETER); + C ckan = getInstance(); + return ckan.list(); + } + + public Response create(String json) { + setCalledMethod("POST /" + COLLECTION_PARAMETER); + C ckan = getInstance(); + String ret = ckan.create(json); + + ResponseBuilder responseBuilder = Response.status(Status.CREATED).entity(ret); + responseBuilder = addLocation(responseBuilder, ckan.getName()); + return responseBuilder.type(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8).build(); + } + + public String read(String id) { + setCalledMethod("GET /" + COLLECTION_PARAMETER + "/{" + ID_PARAMETER + "}"); + C ckan = getInstance(); + ckan.setName(id); + return ckan.read(); + } + + public String update(String id, String json) { + setCalledMethod("PUT /" + COLLECTION_PARAMETER + "/{" + ID_PARAMETER + "}"); + C ckan = getInstance(); + ckan.setName(id); + return ckan.update(json); + } + + + public String patch(String id, String json) { + setCalledMethod("PATCH /" + COLLECTION_PARAMETER + "/{" + ID_PARAMETER + "}"); + C ckan = getInstance(); + ckan.setName(id); + return ckan.patch(json); + } + + public Response delete(String id, Boolean purge) { + if(purge) { + setCalledMethod("PURGE /" + COLLECTION_PARAMETER + "/{" + ID_PARAMETER + "}"); + } else { + setCalledMethod("DELETE /" + COLLECTION_PARAMETER + "/{" + ID_PARAMETER + "}"); + } + C ckan = getInstance(); + ckan.setName(id); + ckan.delete(purge); + return Response.status(Status.NO_CONTENT).build(); + } + + public Response purge(String id) { + return delete(id, true); + } + + +} diff --git a/src/main/java/org/gcube/gcat/rest/Resource.java b/src/main/java/org/gcube/gcat/rest/Resource.java new file mode 100644 index 0000000..9351947 --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/Resource.java @@ -0,0 +1,92 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +import org.gcube.gcat.ResourceInitializer; +import org.gcube.gcat.annotation.PATCH; +import org.gcube.gcat.persistence.ckan.CKANResource; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Path(BaseREST.ITEMS + "/{" + Resource.ITEM_ID_PARAMETER + "}/" + BaseREST.RESOURCES) +public class Resource extends BaseREST { + + protected static final String ITEM_ID_PARAMETER = "ITEM_ID"; + protected static final String RESOURCE_ID_PARAMETER = "RESOURCE_ID"; + + protected static final String COLLECTION = BaseREST.ITEMS + "/{" + Resource.ITEM_ID_PARAMETER + "}/" + BaseREST.RESOURCES; + + @GET + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public String list(@PathParam(ITEM_ID_PARAMETER) String itemID) { + setCalledMethod("GET /" + COLLECTION); + CKANResource ckanResource = new CKANResource(itemID); + ckanResource.setName(itemID); + return ckanResource.list(); + } + + @POST + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public String create(@PathParam(ITEM_ID_PARAMETER) String itemID, String json) { + setCalledMethod("POST /" + COLLECTION); + CKANResource ckanResource = new CKANResource(itemID); + return ckanResource.create(json); + } + + @GET + @Path("/{" + RESOURCE_ID_PARAMETER + "}") + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public String read(@PathParam(ITEM_ID_PARAMETER) String itemID, + @PathParam(RESOURCE_ID_PARAMETER) String resourceID) { + setCalledMethod("GET /" + COLLECTION + "/{" + RESOURCE_ID_PARAMETER + "}"); + CKANResource ckanResource = new CKANResource(itemID); + ckanResource.setName(resourceID); + return ckanResource.read(); + } + + @PUT + @Path("/{" + RESOURCE_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public String update(@PathParam(ITEM_ID_PARAMETER) String itemID, + @PathParam(RESOURCE_ID_PARAMETER) String resourceID, String json) { + setCalledMethod("PUT /" + COLLECTION + "/{" + RESOURCE_ID_PARAMETER + "}"); + CKANResource ckanResource = new CKANResource(itemID); + ckanResource.setName(resourceID); + return ckanResource.update(json); + } + + @PATCH + @Path("/{" + RESOURCE_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public String patch(@PathParam(ITEM_ID_PARAMETER) String itemID, + @PathParam(RESOURCE_ID_PARAMETER) String resourceID, String json) { + setCalledMethod("PATCH /" + COLLECTION + "/{" + RESOURCE_ID_PARAMETER + "}"); + CKANResource ckanResource = new CKANResource(itemID); + ckanResource.setName(resourceID); + return ckanResource.patch(json); + } + + @DELETE + @Path("/{" + RESOURCE_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public void delete(@PathParam(ITEM_ID_PARAMETER) String itemID, + @PathParam(RESOURCE_ID_PARAMETER) String resourceID) { + setCalledMethod("DELETE /" + COLLECTION + "/{" + RESOURCE_ID_PARAMETER + "}"); + CKANResource ckanResource = new CKANResource(itemID); + ckanResource.setName(resourceID); + ckanResource.delete(false); + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/ScienceCatalogueExceptionMapper.java b/src/main/java/org/gcube/gcat/rest/ScienceCatalogueExceptionMapper.java new file mode 100644 index 0000000..f5767f0 --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/ScienceCatalogueExceptionMapper.java @@ -0,0 +1,32 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +/** + * @author Luca Frosini (ISTI - CNR) + */ + @Provider +public class ScienceCatalogueExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(Exception exception) { + + Status status = Status.INTERNAL_SERVER_ERROR; + String exceptionMessage = exception.getMessage(); + MediaType mediaType = MediaType.TEXT_PLAIN_TYPE; + + + if(WebApplicationException.class.isAssignableFrom(exception.getClass())) { + Response gotResponse = ((WebApplicationException) exception).getResponse(); + status = Status.fromStatusCode(gotResponse.getStatusInfo().getStatusCode()); + } + + return Response.status(status).entity(exceptionMessage).type(mediaType).build(); + } + +} diff --git a/src/main/java/org/gcube/gcat/rest/User.java b/src/main/java/org/gcube/gcat/rest/User.java new file mode 100644 index 0000000..3c52a8e --- /dev/null +++ b/src/main/java/org/gcube/gcat/rest/User.java @@ -0,0 +1,69 @@ +package org.gcube.gcat.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +import org.gcube.gcat.ResourceInitializer; +import org.gcube.gcat.persistence.ckan.CKANUser; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +@Path(BaseREST.USERS) +public class User extends REST { + + protected static final String USER_ID_PARAMETER = "USER_ID"; + + public User() { + super(BaseREST.USERS, USER_ID_PARAMETER, CKANUser.class); + } + + + @GET + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String list() { + return super.list(); + } + + @POST + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public Response create(String json) { + return super.create(json); + } + + @GET + @Path("/{" + USER_ID_PARAMETER + "}") + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String read(@PathParam(USER_ID_PARAMETER) String id) { + return super.read(id); + } + + @PUT + @Path("/{" + USER_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Override + public String update(@PathParam(USER_ID_PARAMETER) String id, String json) { + return super.update(id, json); + } + + @DELETE + @Path("/{" + USER_ID_PARAMETER + "}") + @Consumes(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + @Produces(ResourceInitializer.APPLICATION_JSON_CHARSET_UTF_8) + public Response delete(@PathParam(USER_ID_PARAMETER) String id) { + return super.delete(id, false); + } + +} diff --git a/src/main/java/org/gcube/gcat/social/GcoreEndpointReaderSNL.java b/src/main/java/org/gcube/gcat/social/GcoreEndpointReaderSNL.java new file mode 100644 index 0000000..5b40ff8 --- /dev/null +++ b/src/main/java/org/gcube/gcat/social/GcoreEndpointReaderSNL.java @@ -0,0 +1,74 @@ +package org.gcube.gcat.social; + +import static org.gcube.resources.discovery.icclient.ICFactory.client; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.util.List; + +import org.gcube.common.resources.gcore.GCoreEndpoint; +import org.gcube.gcat.utils.ContextUtility; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Discover the Social Networking Service in the Infrastructure. + * @author Costantino Perciante (ISTI - CNR) + * @author Luca Frosini (ISTI - CNR) + */ +public class GcoreEndpointReaderSNL { + + private static final String RESOURCE = "jersey-servlet"; + private static final String SERVICE_NAME = "SocialNetworking"; + private static final String SERVICE_CLASSE = "Portal"; + + private static Logger logger = LoggerFactory.getLogger(GcoreEndpointReaderSNL.class); + private String serviceBasePath; + + /** + * Discover the gcore endpoint for the social networking service. + * @throws Exception the exception + */ + public GcoreEndpointReaderSNL() throws Exception { + + try { + SimpleQuery query = queryFor(GCoreEndpoint.class); + query.addCondition(String.format("$resource/Profile/ServiceClass/text() eq '%s'", SERVICE_CLASSE)); + query.addCondition("$resource/Profile/DeploymentData/Status/text() eq 'ready'"); + query.addCondition(String.format("$resource/Profile/ServiceName/text() eq '%s'", SERVICE_NAME)); + query.setResult( + "$resource/Profile/AccessPoint/RunningInstanceInterfaces//Endpoint[@EntryName/string() eq \"" + + RESOURCE + "\"]/text()"); + + DiscoveryClient client = client(); + List endpoints = client.submit(query); + if(endpoints == null || endpoints.isEmpty()) { + throw new Exception("Cannot retrieve the GCoreEndpoint SERVICE_NAME: " + SERVICE_NAME + + ", SERVICE_CLASSE: " + SERVICE_CLASSE + ", in scope: " + ContextUtility.getCurrentContext()); + } + + this.serviceBasePath = endpoints.get(0); + + if(serviceBasePath == null) + throw new Exception("Endpoint:" + RESOURCE + ", is null for SERVICE_NAME: " + SERVICE_NAME + + ", SERVICE_CLASSE: " + SERVICE_CLASSE + ", in scope: " + ContextUtility.getCurrentContext()); + + serviceBasePath = serviceBasePath.endsWith("/") ? serviceBasePath : serviceBasePath + "/"; + + } catch(Exception e) { + String error = "An error occurred during GCoreEndpoint discovery, SERVICE_NAME: " + SERVICE_NAME + + ", SERVICE_CLASSE: " + SERVICE_CLASSE + ", in scope: " + ContextUtility.getCurrentContext() + "."; + logger.error(error, e); + throw new Exception(error); + } + } + + /** + * @return the base path of the service + */ + public String getServiceBasePath() { + return serviceBasePath; + } + +} diff --git a/src/main/java/org/gcube/gcat/social/SocialService.java b/src/main/java/org/gcube/gcat/social/SocialService.java new file mode 100644 index 0000000..5343ad8 --- /dev/null +++ b/src/main/java/org/gcube/gcat/social/SocialService.java @@ -0,0 +1,197 @@ +package org.gcube.gcat.social; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.cache.Cache; +import javax.ws.rs.core.MediaType; + +import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue; +import org.gcube.gcat.oldutils.CachesManager; +import org.gcube.gcat.persistence.ckan.CKAN; +import org.gcube.gcat.utils.ApplicationMode; +import org.gcube.gcat.utils.ContextUtility; +import org.gcube.gcat.utils.HTTPCall; +import org.gcube.gcat.utils.HTTPCall.HTTPMETHOD; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class SocialService extends Thread { + + private static final Logger logger = LoggerFactory.getLogger(SocialService.class); + + public static final String ITEM_URL = "Item URL"; + + // https://wiki.gcube-system.org/gcube/Social_Networking_Service#Write_application_post_2 + protected static final String SOCIAL_SERVICE_WRITE_APPLICATION_POST_PATH = "/2/posts/write-post-app"; + // https://wiki.gcube-system.org/gcube/Social_Networking_Service + protected static final String SOCIAL_SERVICE_GET_USER_INFO_PATH = "2/users/get-profile"; + + // String.format(NOTIFICATION_MESSAGE, title, fullName, url) + protected static final String NOTIFICATION_MESSAGE = "Dear members,
The item '%s' has been just published by %s.
You can find it at: %s
"; + + protected static final String RESULT_KEY = "result"; + protected static final String FULLNAME_IN_PROFILE_KEY = "fullname"; + protected static final String SOCIAL_POST_TEXT_KEY = "text"; + protected static final String SOCIAL_POST_ENABLE_NOTIFICATION_KEY = "enable_notification"; + protected static final String SOCIAL_POST_RESPONSE_SUCCESS_KEY = "success"; + protected static final String SOCIAL_POST_RESPONSE_MESSAGE_KEY = "message"; + + protected String id; + protected String url; + protected String title; + protected List tags; + protected final GcoreEndpointReaderSNL socialService; + protected final ObjectMapper objectMapper; + + public SocialService() throws Exception { + super(); + this.socialService = new GcoreEndpointReaderSNL(); + this.objectMapper = new ObjectMapper(); + } + + public SocialService(String id, String url, List tags, String title) throws Exception { + this(); + this.id = id; + this.url = url; + this.tags = tags; + this.title = title; + } + + public SocialService(String id, String url, ArrayNode arrayNode, String title) throws Exception { + this(); + this.id = id; + this.url = url; + + this.tags = new ArrayList<>(); + if(arrayNode != null && arrayNode.size() > 0) { + for(int i = 0; i < arrayNode.size(); i++) { + String tagName = arrayNode.get(i).get("display_name").asText(); + tags.add(tagName); + } + } + + this.title = title; + } + + /** + * Execute the GET http request at this url, and return the result as string + * @return + * @throws Exception + */ + public JsonNode getGCubeUserProfile() throws Exception { + String username = ContextUtility.getUsername(); + return getGCubeUserProfile(username); + } + + public JsonNode getGCubeUserProfile(String username) throws Exception { + Cache userCache = CachesManager.getUserCache(); + + if(userCache.containsKey(username)) + return userCache.get(username); + else { + String socialServiceBasePath = socialService.getServiceBasePath(); + HTTPCall httpCall = new HTTPCall(socialServiceBasePath); + String response = httpCall.call(SOCIAL_SERVICE_GET_USER_INFO_PATH, HTTPMETHOD.GET, (Map) null, + MediaType.APPLICATION_JSON); + JsonNode jsonNode = objectMapper.readTree(response); + userCache.put(username, jsonNode); + return jsonNode; + } + } + + public String getFullName() throws Exception { + if(!ContextUtility.isApplication()) { + JsonNode jsonNode = getGCubeUserProfile(); + JsonNode result = jsonNode.get(RESULT_KEY); + return result.get(FULLNAME_IN_PROFILE_KEY).asText(); + } else { + return ContextUtility.getUsername(); + } + } + + @Override + public void run() { + + try { + DataCatalogue dataCatalogue = CKAN.getCatalogue(); + + if(!dataCatalogue.isSocialPostEnabled()) { + logger.info("Social Post are disabled in the context {}", ContextUtility.getCurrentContext()); + return; + } + logger.info("Going to send Social Post about the Item {} available at {}", id, url); + + boolean notifyUsers = dataCatalogue.isNotificationToUsersEnabled(); + // write notification post + sendSocialPost(notifyUsers); + + } catch(Exception e) { + logger.error("Error while executing post creation actions", e); + } + } + + public void sendSocialPost(boolean notifyUsers) { + + try { + String fullName = getFullName(); + + String basePath = socialService.getServiceBasePath(); + if(basePath == null) { + logger.info("Unable to write a post because there is no social networking service available"); + return; + } + basePath = basePath.endsWith("/") ? basePath : basePath + "/"; + + StringWriter messageWriter = new StringWriter(); + messageWriter.append(String.format(NOTIFICATION_MESSAGE, title, fullName, url)); + + for(String tag : tags) { + tag = tag.trim(); + tag = tag.replaceAll(" ", "_").replace("_+", "_"); + if(tag.endsWith("_")) { + tag = tag.substring(0, tag.length() - 1); + } + messageWriter.append(" #"); + messageWriter.append(tag); + } + String message = messageWriter.toString(); + + logger.debug("The post that is going to be written is {} " + message); + + ObjectNode objectNode = objectMapper.createObjectNode(); + objectNode.put(SOCIAL_POST_TEXT_KEY, message); + objectNode.put(SOCIAL_POST_ENABLE_NOTIFICATION_KEY, notifyUsers); + + // Do not use ApplicationMode class here because is a thread and change the current token could impact + // on the other threads. + HTTPCall httpCall = new HTTPCall(basePath); + httpCall.setgCubeTargetService(false); + httpCall.addHeader(org.gcube.common.authorization.client.Constants.TOKEN_HEADER_ENTRY, + ApplicationMode.getCatalogueApplicationToken()); + String response = httpCall.call(SOCIAL_SERVICE_WRITE_APPLICATION_POST_PATH, HTTPMETHOD.POST, + objectMapper.writeValueAsString(objectNode), MediaType.APPLICATION_JSON); + + JsonNode jsonNode = objectMapper.readTree(response); + if(jsonNode.get(SOCIAL_POST_RESPONSE_SUCCESS_KEY).asBoolean()) { + logger.info("Post written : {}", message); + } else { + logger.info("Failed to write the post {}. Reason {}", message, + jsonNode.get(SOCIAL_POST_RESPONSE_MESSAGE_KEY).asText()); + } + } catch(Exception e) { + logger.error("Unable to send Social Post", e); + } + + } +} diff --git a/src/main/java/org/gcube/gcat/utils/ApplicationMode.java b/src/main/java/org/gcube/gcat/utils/ApplicationMode.java new file mode 100644 index 0000000..dfbcfe1 --- /dev/null +++ b/src/main/java/org/gcube/gcat/utils/ApplicationMode.java @@ -0,0 +1,92 @@ +package org.gcube.gcat.utils; + +import java.io.InputStream; +import java.util.Properties; + +import javax.ws.rs.WebApplicationException; + +import org.gcube.common.authorization.client.Constants; +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.provider.ClientInfo; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class ApplicationMode { + + private static final Logger logger = LoggerFactory.getLogger(ApplicationMode.class); + + private static final String PROPERTY_FILENAME = "config.properties"; + private static final String TOKEN_VARNAME = "TOKEN"; + private static final String CATALOGUE_APPLICATION_TOKEN; + + public static String getCatalogueApplicationToken() { + return CATALOGUE_APPLICATION_TOKEN; + } + + static { + try { + Properties properties = new Properties(); + InputStream input = Constants.class.getClassLoader().getResourceAsStream(PROPERTY_FILENAME); + // load a properties file + properties.load(input); + CATALOGUE_APPLICATION_TOKEN = properties.getProperty(TOKEN_VARNAME); + }catch (Exception e) { + throw new WebApplicationException(e); + } + } + + + + private final String originalToken; + + public ApplicationMode() { + String applicationtoken = SecurityTokenProvider.instance.get(); + if(applicationtoken.compareTo(CATALOGUE_APPLICATION_TOKEN)!=0) { + this.originalToken = applicationtoken; + }else { + logger.warn("You are already in application Mode. Operation on this instance will not have any effect."); + this.originalToken = null; + } + } + + public static void setToken(String token) { + SecurityTokenProvider.instance.set(token); + ScopeProvider.instance.set(ContextUtility.getCurrentContext()); + try { + AuthorizationEntry authorizationEntry = Constants.authorizationService().get(token); + ClientInfo clientInfo = authorizationEntry.getClientInfo(); + logger.debug("User : {} - Type : {}", clientInfo.getId(), clientInfo.getType().name()); + String qualifier = authorizationEntry.getQualifier(); + Caller caller = new Caller(clientInfo, qualifier); + AuthorizationProvider.instance.set(caller); + }catch (Exception e) { + logger.error("Unable to set Caller"); + } + + } + + + public synchronized void start() { + if(originalToken!=null) { + setToken(CATALOGUE_APPLICATION_TOKEN); + }else { + logger.warn("You are already in application Mode. start() does not provide any effect."); + } + } + + public synchronized void end() { + if(originalToken!=null) { + setToken(originalToken); + }else { + logger.warn("You are already in application Mode. end() does not provide any effect."); + } + } + +} diff --git a/src/main/java/org/gcube/gcat/utils/Constants.java b/src/main/java/org/gcube/gcat/utils/Constants.java new file mode 100644 index 0000000..a45f7ff --- /dev/null +++ b/src/main/java/org/gcube/gcat/utils/Constants.java @@ -0,0 +1,10 @@ +package org.gcube.gcat.utils; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class Constants { + + public static final String CATALOGUE_NAME = "ScienceCatalogue"; + +} diff --git a/src/main/java/org/gcube/gcat/utils/ContextUtility.java b/src/main/java/org/gcube/gcat/utils/ContextUtility.java new file mode 100644 index 0000000..2757822 --- /dev/null +++ b/src/main/java/org/gcube/gcat/utils/ContextUtility.java @@ -0,0 +1,83 @@ +package org.gcube.gcat.utils; + +import javax.ws.rs.InternalServerErrorException; + +import org.gcube.common.authorization.client.Constants; +import org.gcube.common.authorization.client.exceptions.ObjectNotFound; +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.ClientType; +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.provider.ClientInfo; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class ContextUtility { + + private static Logger logger = LoggerFactory.getLogger(ContextUtility.class); + + public static void setContext(String token) throws ObjectNotFound, Exception{ + SecurityTokenProvider.instance.set(token); + AuthorizationEntry authorizationEntry = Constants.authorizationService().get(token); + ClientInfo clientInfo = authorizationEntry.getClientInfo(); + logger.debug("User : {} - Type : {}", clientInfo.getId(), clientInfo.getType().name()); + String qualifier = authorizationEntry.getQualifier(); + Caller caller = new Caller(clientInfo, qualifier); + AuthorizationProvider.instance.set(caller); + ScopeProvider.instance.set(getCurrentContext()); + } + + public static String getCurrentContext() { + try { + String token = SecurityTokenProvider.instance.get(); + return Constants.authorizationService().get(token).getContext(); + }catch (Exception e) { + String context = ScopeProvider.instance.get(); + if(context!=null) { + return context; + } + throw new InternalServerErrorException(e); + } + } + + public static ClientInfo getClientInfo() { + try { + Caller caller = AuthorizationProvider.instance.get(); + if(caller!=null){ + return caller.getClient(); + }else{ + String token = SecurityTokenProvider.instance.get(); + AuthorizationEntry authorizationEntry = Constants.authorizationService().get(token); + return authorizationEntry.getClientInfo(); + } + }catch (Exception e) { + throw new InternalServerErrorException(e); + } + } + + private static final String GET_USERNAME_ERROR = "Unable to retrieve user"; + + public static String getUsername() { + try { + return getClientInfo().getId(); + } catch (Exception e) { + logger.error(GET_USERNAME_ERROR); + throw new InternalServerErrorException(GET_USERNAME_ERROR, e); + } + } + + public static boolean isApplication() { + try { + ClientInfo clientInfo = getClientInfo(); + return clientInfo.getType() == ClientType.EXTERNALSERVICE ; + }catch (Exception e) { + throw new InternalServerErrorException(e); + } + } + +} diff --git a/src/main/java/org/gcube/gcat/utils/HTTPCall.java b/src/main/java/org/gcube/gcat/utils/HTTPCall.java new file mode 100644 index 0000000..71fa716 --- /dev/null +++ b/src/main/java/org/gcube/gcat/utils/HTTPCall.java @@ -0,0 +1,313 @@ +package org.gcube.gcat.utils; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.scope.api.ScopeProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HTTPCall { + + private static final Logger logger = LoggerFactory.getLogger(HTTPCall.class); + + protected static final String USER_AGENT_KEY = "User-Agent"; + protected static final String USER_AGENT_NAME = Constants.CATALOGUE_NAME; + + public enum HTTPMETHOD { + HEAD, GET, POST, PUT, DELETE; + + @Override + public String toString() { + return this.name(); + } + } + + public static final String PATH_SEPARATOR = "/"; + public static final String PARAM_STARTER = "?"; + public static final String PARAM_EQUALS = "="; + public static final String PARAM_SEPARATOR = "&"; + public static final String UTF8 = "UTF-8"; + + protected final String address; + protected final String userAgent; + + protected Map headers; + + /** + * When the target service is a gCube Service it adds the HTTP header + * to provide gCube authorization token and/or scope + */ + protected boolean gCubeTargetService; + + public boolean isgCubeTargetService() { + return gCubeTargetService; + } + + public void setgCubeTargetService(boolean gCubeTargetService) { + this.gCubeTargetService = gCubeTargetService; + } + + public HTTPCall(String address) { + this(address, HTTPCall.USER_AGENT_NAME); + } + + protected HTTPCall(String address, String userAgent) { + this.address = address; + this.userAgent = userAgent; + this.gCubeTargetService = true; + this.headers = new HashMap<>(); + addHeader(USER_AGENT_KEY, this.userAgent); + } + + public void addHeader(String key, String value) { + headers.put(key, value); + } + + protected String getParametersDataString(Map parameters) throws UnsupportedEncodingException { + + if(parameters == null) { + return null; + } + + StringBuilder result = new StringBuilder(); + boolean first = true; + for(String key : parameters.keySet()) { + if(first) { + first = false; + } else { + result.append(PARAM_SEPARATOR); + } + + result.append(URLEncoder.encode(key, UTF8)); + result.append(PARAM_EQUALS); + result.append(URLEncoder.encode(parameters.get(key), UTF8)); + + } + + return result.toString(); + } + + protected URL getURL(String address, String path, String urlParameters) throws MalformedURLException { + + StringWriter stringWriter = new StringWriter(); + stringWriter.append(address); + + if(address.endsWith(PATH_SEPARATOR)) { + if(path.startsWith(PATH_SEPARATOR)) { + path = path.substring(1); + } + } else { + if(path.compareTo("") != 0 && !path.startsWith(PATH_SEPARATOR)) { + stringWriter.append(PATH_SEPARATOR); + } + } + + stringWriter.append(path); + + if(urlParameters != null) { + stringWriter.append(PARAM_STARTER); + stringWriter.append(urlParameters); + } + + return getURL(stringWriter.toString()); + } + + protected URL getURL(String urlString) throws MalformedURLException { + URL url = new URL(urlString); + if(url.getProtocol().compareTo("https") == 0) { + url = new URL(url.getProtocol(), url.getHost(), url.getDefaultPort(), url.getFile()); + } + return url; + } + + protected HttpURLConnection getConnection(String path, String urlParameters, HTTPMETHOD method, String body, + InputStream inputStream, String contentType) throws Exception { + URL url = getURL(address, path, urlParameters); + return getConnection(url, method, body, inputStream, contentType); + } + + public URL getFinalURL(URL url) { + try { + URL finalURL = url; + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setInstanceFollowRedirects(false); + + int responseCode = connection.getResponseCode(); + String responseMessage = connection.getResponseMessage(); + + if(responseCode >= Status.BAD_REQUEST.getStatusCode()) { + Status status = Status.fromStatusCode(responseCode); + throw new WebApplicationException(responseMessage, status); + } + + if(responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_SEE_OTHER + || responseCode == Status.TEMPORARY_REDIRECT.getStatusCode() || responseCode == 308) { + + finalURL = getURL(connection.getHeaderField("Location")); + finalURL = getFinalURL(finalURL); + } + + return finalURL; + + }catch (WebApplicationException e) { + throw e; + }catch (Exception e) { + throw new InternalServerErrorException(e); + } + + } + + + protected HttpURLConnection getConnection(URL url, HTTPMETHOD method, String body, InputStream inputStream, + String contentType) throws Exception { + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + if(gCubeTargetService) { + if(SecurityTokenProvider.instance.get() == null) { + if(ScopeProvider.instance.get() == null) { + throw new RuntimeException("Null Token and Scope. Please set your token first."); + } + connection.setRequestProperty("gcube-scope", ScopeProvider.instance.get()); + } else { + connection.setRequestProperty(org.gcube.common.authorization.client.Constants.TOKEN_HEADER_ENTRY, + SecurityTokenProvider.instance.get()); + } + } + + connection.setDoOutput(true); + + connection.setRequestProperty("Content-type", contentType); + for(String key : headers.keySet()) { + connection.setRequestProperty(key, headers.get(key)); + } + + connection.setRequestMethod(method.toString()); + + if(inputStream != null && (method == HTTPMETHOD.POST || method == HTTPMETHOD.PUT)) { + + DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + byte[] buffer = new byte[1024]; + + int len; + while((len = inputStream.read(buffer)) > 0) { + wr.write(buffer, 0, len); + } + wr.flush(); + wr.close(); + } + + if(body != null && (method == HTTPMETHOD.POST || method == HTTPMETHOD.PUT)) { + + DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); + wr.writeBytes(body); + wr.flush(); + wr.close(); + } + + int responseCode = connection.getResponseCode(); + String responseMessage = connection.getResponseMessage(); + logger.trace("{} {} : {} - {}", method, connection.getURL(), responseCode, responseMessage); + + // 308 Permanent Redirect https://tools.ietf.org/html/rfc7538#section-3 + if(responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_SEE_OTHER + || responseCode == Status.TEMPORARY_REDIRECT.getStatusCode() || responseCode == 308) { + + URL redirectURL = getURL(connection.getHeaderField("Location")); + + logger.trace("{} is going to be redirect to {}", url.toString(), redirectURL.toString()); + + connection = getConnection(redirectURL, method, body, inputStream, contentType); + } + + return connection; + + } + + protected StringBuilder getStringBuilder(InputStream inputStream) throws IOException { + StringBuilder result = new StringBuilder(); + try(BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while((line = reader.readLine()) != null) { + result.append(line); + } + } + + return result; + } + + public String call(String path, HTTPMETHOD method, Map parameters, String contentType) { + return call(path, method, parameters, null, contentType); + } + + public String call(String path, HTTPMETHOD method, String body, String contentType) { + return call(path, method, null, body, contentType); + } + + public String call(String path, HTTPMETHOD method, Map parameters, String body, String contentType) { + return call(path, method, body, null, parameters, contentType); + } + + public String call(String path, HTTPMETHOD method, InputStream inputStream, Map parameters, + String contentType) { + return call(path, method, null, inputStream, parameters, contentType); + } + + private String call(String path, HTTPMETHOD method, String body, InputStream inputStream, + Map parameters, String contentType) { + HttpURLConnection connection; + try { + String urlParameters = getParametersDataString(parameters); + connection = getConnection(path, urlParameters, method, body, inputStream, contentType); + int responseCode = connection.getResponseCode(); + String responseMessage = connection.getResponseMessage(); + logger.info("{} {} : {} - {}", method, connection.getURL(), responseCode, responseMessage); + + if(responseCode >= Status.BAD_REQUEST.getStatusCode()) { + try { + StringBuilder result = getStringBuilder(connection.getErrorStream()); + String res = result.toString(); + logger.trace("Server returned content : {}", res); + return res; + }catch (Exception e) { + Status status = Status.fromStatusCode(responseCode); + throw new WebApplicationException(responseMessage, status); + } + } + StringBuilder result = getStringBuilder(connection.getInputStream()); + String res = result.toString(); + logger.trace("Server returned content : {}", res); + + connection.disconnect(); + + return res; + + } catch(WebApplicationException e) { + throw e; + } catch(Exception e) { + throw new InternalServerErrorException(e); + } + + } + +} diff --git a/src/main/java/org/gcube/gcat/utils/RandomString.java b/src/main/java/org/gcube/gcat/utils/RandomString.java new file mode 100644 index 0000000..5efc74d --- /dev/null +++ b/src/main/java/org/gcube/gcat/utils/RandomString.java @@ -0,0 +1,36 @@ +package org.gcube.gcat.utils; + +import java.util.Random; + +/** + * @author Lucio Lelii (ISTI - CNR) + */ +public class RandomString { + + private static final char[] symbols; + + static { + StringBuilder tmp = new StringBuilder(); + for (char ch = '0'; ch <= '9'; ++ch) + tmp.append(ch); + for (char ch = 'a'; ch <= 'z'; ++ch) + tmp.append(ch); + symbols = tmp.toString().toCharArray(); + } + + private final Random random = new Random(); + + private final char[] buf; + + public RandomString(int length) { + if (length < 1) + throw new IllegalArgumentException("length < 1: " + length); + buf = new char[length]; + } + + public String nextString() { + for (int idx = 0; idx < buf.length; ++idx) + buf[idx] = symbols[random.nextInt(symbols.length)]; + return new String(buf); + } +} diff --git a/src/main/java/org/gcube/gcat/utils/URIResolver.java b/src/main/java/org/gcube/gcat/utils/URIResolver.java new file mode 100644 index 0000000..7d7377b --- /dev/null +++ b/src/main/java/org/gcube/gcat/utils/URIResolver.java @@ -0,0 +1,49 @@ +package org.gcube.gcat.utils; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; + +import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue; +import org.gcube.datacatalogue.ckanutillibrary.server.utils.url.EntityContext; +import org.gcube.gcat.persistence.ckan.CKAN; +import org.gcube.gcat.utils.HTTPCall.HTTPMETHOD; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class URIResolver { + + private static final String CATALOGUE_CONTEXT = "gcube_scope"; + private static final String ENTITY_TYPE = "entity_context"; + private static final String ENTITY_NAME = "entity_name"; + private static final String CATALOGUE_PLAIN_URL = "clear_url"; + + protected ObjectMapper objectMapper; + + public URIResolver() { + this.objectMapper = new ObjectMapper(); + } + + public String getCatalogueItemURL(String name) { + try { + DataCatalogue dataCatalogue = CKAN.getCatalogue(); + String uriResolverURL = dataCatalogue.getUriResolverUrl(); + + HTTPCall httpCall = new HTTPCall(uriResolverURL); + + ObjectNode requestContent = objectMapper.createObjectNode(); + requestContent.put(CATALOGUE_CONTEXT, ContextUtility.getCurrentContext()); + requestContent.put(ENTITY_TYPE, EntityContext.PRODUCT.toString()); + requestContent.put(ENTITY_NAME, name); + requestContent.put(CATALOGUE_PLAIN_URL, true); + + String url = httpCall.call("", HTTPMETHOD.POST, objectMapper.writeValueAsString(requestContent), + MediaType.APPLICATION_JSON); + + return url; + } catch(Exception e) { + throw new WebApplicationException(e); + } + } + +} diff --git a/src/main/java/org/gcube/gcat/workspace/StorageHubManagement.java b/src/main/java/org/gcube/gcat/workspace/StorageHubManagement.java new file mode 100644 index 0000000..5cff1a2 --- /dev/null +++ b/src/main/java/org/gcube/gcat/workspace/StorageHubManagement.java @@ -0,0 +1,295 @@ +package org.gcube.gcat.workspace; + +import java.io.StringWriter; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.InternalServerErrorException; + +import org.gcube.common.homelibrary.home.Home; +import org.gcube.common.homelibrary.home.HomeLibrary; +import org.gcube.common.homelibrary.home.HomeManager; +import org.gcube.common.homelibrary.home.HomeManagerFactory; +import org.gcube.common.homelibrary.home.User; +import org.gcube.common.homelibrary.home.workspace.Workspace; +import org.gcube.common.storagehub.client.StreamDescriptor; +import org.gcube.common.storagehub.client.dsl.ContainerType; +import org.gcube.common.storagehub.client.dsl.FileContainer; +import org.gcube.common.storagehub.client.dsl.FolderContainer; +import org.gcube.common.storagehub.client.dsl.ItemContainer; +import org.gcube.common.storagehub.client.dsl.ListResolver; +import org.gcube.common.storagehub.client.dsl.ListResolverTyped; +import org.gcube.common.storagehub.client.dsl.OpenResolver; +import org.gcube.common.storagehub.client.dsl.StorageHubClient; +import org.gcube.common.storagehub.model.Metadata; +import org.gcube.common.storagehub.model.items.AbstractFileItem; +import org.gcube.common.storagehub.model.items.Item; +import org.gcube.gcat.utils.Constants; +import org.gcube.gcat.utils.ContextUtility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StorageHubManagement { + + private static final Logger logger = LoggerFactory.getLogger(StorageHubManagement.class); + + public static final String ORIGINAL_URL = "OriginalURL"; + public static final String ORIGINAL_NAME = "OriginalName"; + + public static final String CATALOGUE_ITEM_ID = "CatalogueItemID"; + public static final String CATALOGUE_RESOURCE_ID = "CatalogueResourceID"; + public static final String CATALOGUE_RESOURCE_REVISION_ID = "CatalogueResourceRevisionID"; + + public static final String CATALOGUE_FOLDER_DESCRIPTION = "Catalogue Folder used to persist Resources"; + + protected final StorageHubClient storageHubClient; + + protected FileContainer createdFile; + protected String mimeType; + + public String getMimeType() { + return mimeType; + } + + public StorageHubManagement() { + storageHubClient = new StorageHubClient(); + } + + protected void recursiveList(FolderContainer folder, int level) { + ListResolverTyped listResolverTyped = folder.list(); + List> containers = listResolverTyped.getContainers(); + for(ItemContainer itemContainer : containers) { + Item item = itemContainer.get(); + String name = item.getName(); + ContainerType containerType = itemContainer.getType(); + StringWriter indent = new StringWriter(level + 1); + for(int i = 0; i < level + 1; i++) { + indent.append('-'); + } + logger.debug("{} {} {} (ID:{})", indent.toString(), containerType, name, itemContainer.getId()); + switch(containerType) { + case FOLDER: + FolderContainer folderContainer = (FolderContainer) itemContainer; + //if(item.getName().compareTo("553095a0-a14a-4e41-b014-2e6f3a1aeac7")!=0) + recursiveList(folderContainer, level + 1); + break; + + case FILE: + break; + + case GENERIC_ITEM: + break; + + default: + break; + } + } + } + + protected FolderContainer getWorkspaceRoot() { + try { + return storageHubClient.getWSRoot(); + }catch (Exception e) { + String username = ContextUtility.getUsername(); + logger.info("Unable to obtain the Workspace Root for {}. Going to create it.", username); + try { + HomeManagerFactory factory = HomeLibrary.getHomeManagerFactory(); + HomeManager manager = factory.getHomeManager(); + User user = manager.createUser(username); + @SuppressWarnings("deprecation") + Home home = manager.getHome(user); + Workspace ws = home.getWorkspace(); + ws.getRoot(); + return storageHubClient.getWSRoot(); + }catch (Exception ex) { + logger.info("Unable to create the Workspace Root for {}.", username); + throw new InternalServerErrorException(e); + } + } + } + + + protected FolderContainer getOrCreateFolder(FolderContainer parent, String name, String description) { + try { + FolderContainer destinationFolder = null; + ListResolverTyped listResolverTyped = parent.list(); + List> containers = listResolverTyped.getContainers(); + for(ItemContainer itemContainer : containers) { + if(itemContainer instanceof FolderContainer) { + if(itemContainer.get().getName().compareTo(name) == 0) { + destinationFolder = (FolderContainer) itemContainer; + } + } + } + if(destinationFolder == null) { + destinationFolder = parent.newFolder(name, description); + } + return destinationFolder; + } catch(Exception e) { + throw new InternalServerErrorException(e); + } + } + + public static String getFolderName() { + String currentContext = ContextUtility.getCurrentContext(); + String folderName = currentContext.replaceFirst("/", "").replace("/", "_"); + return folderName; + } + + protected FolderContainer getCatalogueFolder() { + FolderContainer destinationFolder = getWorkspaceRoot(); + String folderName = getFolderName(); + destinationFolder = getOrCreateFolder(destinationFolder, folderName, ContextUtility.getCurrentContext() + " folder"); + destinationFolder = getOrCreateFolder(destinationFolder, Constants.CATALOGUE_NAME, CATALOGUE_FOLDER_DESCRIPTION); + return destinationFolder; + } + + protected FolderContainer getDestinationFolder(AbstractFileItem item) { + FolderContainer destinationFolder = getCatalogueFolder(); + mimeType = item.getContent().getMimeType(); + String[] splittedMimeType = mimeType.split("/"); + for(String name : splittedMimeType) { + try { + destinationFolder = getOrCreateFolder(destinationFolder, name, + "Automatic Folder Created using mimetype"); + } catch(Exception e) { + throw new InternalServerErrorException(e); + } + } + return destinationFolder; + } + + protected FileContainer copyFile(FileContainer sourceFileContainer, FolderContainer destinationFolder, + String copiedFilename) { + StreamDescriptor streamDescriptor = sourceFileContainer.download(""); + FileContainer createdFile = destinationFolder.uploadFile(streamDescriptor.getStream(), copiedFilename, + sourceFileContainer.get().getDescription()); + /* + FileContainer copiedFile = sourceFileContainer.copy(destinationFolder, copiedFilename); + */ + return createdFile; + } + + protected ItemContainer getContainerByStorageURL(URL url) { + URL finalURL = url; + String path = finalURL.getPath(); + String fileID = path.substring(path.lastIndexOf('/') + 1); + + OpenResolver openResolver = storageHubClient.open(fileID); + ItemContainer itemContainer = openResolver.asItem(); + Item item = itemContainer.get(); + if(item instanceof AbstractFileItem) { + logger.debug("The resource with ID {} is a file", fileID); + return openResolver.asFile(); + } + return itemContainer; + } + + protected boolean isItemPersistedFile(FileContainer fileContainer, FolderContainer destinationFolder, String itemID) { + // Checking if the file is already a file of the science catalogue folder of the scope related to such an item. + ListResolver listResolver = fileContainer.getAnchestors(); + List> itemContainers = listResolver.getContainers(); + for(ItemContainer itemContainer : itemContainers) { + if(itemContainer.getId().compareTo(destinationFolder.getId()) == 0) { + Metadata metadata = fileContainer.get().getPropertyMap(); + Map map = metadata.getValues(); + if(map.get(CATALOGUE_ITEM_ID).toString().compareTo(itemID) == 0) { + return true; + } + } + } + return false; + } + + public boolean isItemPersistedFile(URL url, String itemID) { + ItemContainer itemContainer = getContainerByStorageURL(url); + FileContainer fileContainer; + if(itemContainer instanceof FileContainer) { + fileContainer = (FileContainer) itemContainer; + }else { + return false; + } + FolderContainer destinationFolder = getDestinationFolder(fileContainer.get()); + return isItemPersistedFile(fileContainer, destinationFolder, itemID); + } + + protected void tree(FolderContainer folderContainer) throws Exception { + logger.debug("{} (ID:{})", folderContainer.get().getName(), folderContainer.getId()); + recursiveList(folderContainer, 0); + } + + protected FileContainer getFileByID(String id) throws Exception { + OpenResolver openResolver = storageHubClient.open(id); + FileContainer fileContainer = (FileContainer) openResolver.asFile(); + return fileContainer; + } + + protected void deleteFileByID(String id) throws Exception { + FileContainer fileContainer = getFileByID(id); + fileContainer.delete(); + } + + + public URL ensureResourcePersistence(URL url, String itemID, String resourceID, String name) { + ItemContainer itemContainer = getContainerByStorageURL(url); + FileContainer fileContainer; + if(itemContainer instanceof FileContainer) { + fileContainer = (FileContainer) itemContainer; + }else { + return url; + } + + FolderContainer destinationFolder = getDestinationFolder(fileContainer.get()); + + if(isItemPersistedFile(fileContainer, destinationFolder, itemID)) { + return url; + } + + createdFile = copyFile(fileContainer, destinationFolder, resourceID); + + Map map = new HashMap<>(); + map.put(ORIGINAL_URL, url.toString()); + map.put(ORIGINAL_NAME, name); + map.put(CATALOGUE_ITEM_ID, itemID); + map.put(CATALOGUE_RESOURCE_ID, resourceID); + Metadata metadata = new Metadata(map); + createdFile.setMetadata(metadata); + + URL finalURL = createdFile.getPublicLink(); + logger.debug("Original File URL was {} - Final URL to ensure persistency is {}", url, finalURL); + return finalURL; + } + + public void deleteResourcePersistence(URL url, String itemID) { + FileContainer fileContainer = (FileContainer) getContainerByStorageURL(url); + FolderContainer destinationFolder = getDestinationFolder(fileContainer.get()); + if(isItemPersistedFile(fileContainer, destinationFolder, itemID)) { + fileContainer.delete(); + } + } + + protected void addRevisionID(FileContainer fileContainer, String resourceID, String revisionID) { + Metadata metadata = fileContainer.get().getPropertyMap(); + Map map = metadata.getValues(); + map.put(CATALOGUE_RESOURCE_ID, resourceID); + map.put(CATALOGUE_RESOURCE_REVISION_ID, revisionID); + metadata.setValues(map); + fileContainer.setMetadata(metadata);; + } + + public void addRevisionID(String resourceID, String revisionID) { + addRevisionID(createdFile, resourceID, revisionID); + } + + public void renameFile(String resourceID, String revisionID) { + renameFile(createdFile, resourceID); + addRevisionID(createdFile, resourceID, revisionID); + } + + protected void renameFile(FileContainer fileContainer, String newName) { + fileContainer.rename(newName); + } + +} diff --git a/src/main/webapp/WEB-INF/LICENSE b/src/main/webapp/WEB-INF/LICENSE new file mode 100644 index 0000000..9724bfe --- /dev/null +++ b/src/main/webapp/WEB-INF/LICENSE @@ -0,0 +1,316 @@ +gCube System - License +------------------------------------------------------------ + +European Union Public Licence V. 1.1 + + +EUPL © the European Community 2007 + + +This European Union Public Licence (the “EUPL”) applies to the Work or Software +(as defined below) which is provided under the terms of this Licence. Any use of +the Work, other than as authorised under this Licence is prohibited (to the +extent such use is covered by a right of the copyright holder of the Work). + +The Original Work is provided under the terms of this Licence when the Licensor +(as defined below) has placed the following notice immediately following the +copyright notice for the Original Work: + +Licensed under the EUPL V.1.1 + +or has expressed by any other mean his willingness to license under the EUPL. + + + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- The Licence: this Licence. + +- The Original Work or the Software: the software distributed and/or + communicated by the Licensor under this Licence, available as Source Code and + also as Executable Code as the case may be. + +- Derivative Works: the works or software that could be created by the Licensee, + based upon the Original Work or modifications thereof. This Licence does not + define the extent of modification or dependence on the Original Work required + in order to classify a work as a Derivative Work; this extent is determined by + copyright law applicable in the country mentioned in Article 15. + +- The Work: the Original Work and/or its Derivative Works. + +- The Source Code: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- The Executable Code: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- The Licensor: the natural or legal person that distributes and/or communicates + the Work under the Licence. + +- Contributor(s): any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- The Licensee or “You”: any natural or legal person who makes any usage of the + Software under the terms of the Licence. + +- Distribution and/or Communication: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, on-line or off-line, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + + + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a world-wide, royalty-free, non-exclusive, +sub-licensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, reproduce the Work, modify +- the Original Work, and make Derivative Works based upon the Work, communicate +- to the public, including the right to make available or display the Work or +- copies thereof to the public and perform publicly, as the case may be, the +- Work, distribute the Work or copies thereof, lend and rent the Work or copies +- thereof, sub-license rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + + + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute and/or communicate the Work. + + + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Original Work or Software, of the exhaustion of those rights or of other +applicable limitations thereto. + + + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: the Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes and/or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes and/or communicates copies of the +Original Works or Derivative Works based upon the Original Work, this +Distribution and/or Communication will be done under the terms of this Licence +or of a later version of this Licence unless the Original Work is expressly +distributed only under this version of the Licence. The Licensee (becoming +Licensor) cannot offer or impose any additional terms or conditions on the Work +or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes and/or Communicates Derivative +Works or copies thereof based upon both the Original Work and another work +licensed under a Compatible Licence, this Distribution and/or Communication can +be done under the terms of this Compatible Licence. For the sake of this clause, +“Compatible Licence” refers to the licences listed in the appendix attached to +this Licence. Should the Licensee’s obligations under the Compatible Licence +conflict with his/her obligations under this Licence, the obligations of the +Compatible Licence shall prevail. + +Provision of Source Code: When distributing and/or communicating copies of the +Work, the Licensee will provide a machine-readable copy of the Source Code or +indicate a repository where this Source will be easily and freely available for +as long as the Licensee continues to distribute and/or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + + + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + + + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +contributors. It is not a finished work and may therefore contain defects or +“bugs” inherent to this type of software development. + +For the above reason, the Work is provided under the Licence on an “as is” basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + + + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such +damage. However, the Licensor will be liable under statutory product liability +laws as far such laws apply to the Work. + + + +9. Additional agreements + +While distributing the Original Work or Derivative Works, You may choose to +conclude an additional agreement to offer, and charge a fee for, acceptance of +support, warranty, indemnity, or other liability obligations and/or services +consistent with this Licence. However, in accepting such obligations, You may +act only on your own behalf and on your sole responsibility, not on behalf of +the original Licensor or any other Contributor, and only if You agree to +indemnify, defend, and hold each Contributor harmless for any liability incurred +by, or claims asserted against such Contributor by the fact You have accepted +any such warranty or additional liability. + + + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon “I agree” +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution and/or Communication by You of the Work or copies thereof. + + + +11. Information to the public + +In case of any Distribution and/or Communication of the Work by means of +electronic communication by You (for example, by offering to download the Work +from a remote location) the distribution channel or media (for example, a +website) must at least provide to the public the information requested by the +applicable law regarding the Licensor, the Licence and the way it may be +accessible, concluded, stored and reproduced by the Licensee. + + + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + + + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work licensed hereunder. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed and/or reformed so as necessary to make +it valid and enforceable. + +The European Commission may publish other linguistic versions and/or new +versions of this Licence, so far this is required and reasonable, without +reducing the scope of the rights granted by the Licence. New versions of the +Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + + + +14. Jurisdiction + +Any litigation resulting from the interpretation of this License, arising +between the European Commission, as a Licensor, and any Licensee, will be +subject to the jurisdiction of the Court of Justice of the European Communities, +as laid down in article 238 of the Treaty establishing the European Community. + +Any litigation arising between Parties, other than the European Commission, and +resulting from the interpretation of this License, will be subject to the +exclusive jurisdiction of the competent court where the Licensor resides or +conducts its primary business. + + + +15. Applicable Law + +This Licence shall be governed by the law of the European Union country where +the Licensor resides or has his registered office. + +This licence shall be governed by the Belgian law if: + +- a litigation arises between the European Commission, as a Licensor, and any +- Licensee; the Licensor, other than the European Commission, has no residence +- or registered office inside a European Union country. + + +=== + + +Appendix + + + +“Compatible Licences” according to article 5 EUPL are: + + +- GNU General Public License (GNU GPL) v. 2 + +- Open Software License (OSL) v. 2.1, v. 3.0 + +- Common Public License v. 1.0 + +- Eclipse Public License v. 1.0 + +- Cecill v. 2.0 \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/README b/src/main/webapp/WEB-INF/README new file mode 100644 index 0000000..b97142b --- /dev/null +++ b/src/main/webapp/WEB-INF/README @@ -0,0 +1,76 @@ +The gCube System - gCube Catalogue Service +-------------------------------------------------- + +This service allows any client to publish on the gCube Catalogue. + + +This software is part of the gCube Framework (https://www.gcube-system.org/): an +open-source software toolkit used for building and operating Hybrid Data +Infrastructures enabling the dynamic deployment of Virtual Research Environments +by favouring the realisation of reuse oriented policies. + +The projects leading to this software have received funding from a series of +European Union programmes including: +* the Sixth Framework Programme for Research and Technological Development - +DILIGENT (grant no. 004260); +* the Seventh Framework Programme for research, technological development and +demonstration - D4Science (grant no. 212488), D4Science-II (grant no. +239019),ENVRI (grant no. 283465), EUBrazilOpenBio (grant no. 288754), iMarine +(grant no. 283644); +* the H2020 research and innovation programme - BlueBRIDGE (grant no. 675680), +EGIEngage (grant no. 654142), ENVRIplus (grant no. 654182), Parthenos (grant +no. 654119), SoBigData (grant no. 654024), AGINFRA PLUS (grant no. 731001). + + +Version +-------------------------------------------------- + +1.0.0-SNAPSHOT (2018-12-04) + +Please see the file named "changelog.xml" in this directory for the release notes. + + +Authors +-------------------------------------------------- + +* Luca Frosini (luca.frosini-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). +* Costantino Perciante (costantino.perciante@isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Maintainers +----------- + +* Luca Frosini (luca.frosini-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Download information +-------------------------------------------------- + +Source code is available from SVN: + https://svn.d4science.research-infrastructures.eu/gcube/trunk/data-catalogue/gcat + +Binaries can be downloaded from the gCube website: + https://www.gcube-system.org/ + + +Installation +-------------------------------------------------- + +Installation documentation is available on-line in the gCube Wiki: + https://wiki.gcube-system.org/gcube/index.php + +Documentation +-------------------------------------------------- + +Documentation is available on-line in the gCube Wiki: + https://wiki.gcube-system.org/gcube/index.php + +Support +-------------------------------------------------- + +Bugs and support requests can be reported in the gCube issue tracking tool: + https://support.d4science.org/projects/gcube/ + + +Licensing +-------------------------------------------------- + +This software is licensed under the terms you may find in the file named "LICENSE" in this directory. \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/changelog.xml b/src/main/webapp/WEB-INF/changelog.xml new file mode 100644 index 0000000..9e89cd1 --- /dev/null +++ b/src/main/webapp/WEB-INF/changelog.xml @@ -0,0 +1,31 @@ + + + + + + + + Complete refactoring of code + Item Port type is now RESTful (old methods are still allowed) + Added update support for Item #11516 + Changed caching mechanism from ehcache API to JSR-107. Ehcache is still used as runtime library. + Solved random NullPointer Exception on catalogue-ws related to old caching mechanism #11466 + Fixed normalization of the organization name #12506 + Added the possibility to deny social post on catalogue-ws #12514 + + + Item purge method enhanced + + + Minor fixes while checking user's permissions + Namespaces are no longer transparently managed + Fixed 'default' value for metadata + Improved exception handling + + + First Release + + diff --git a/src/main/webapp/WEB-INF/descriptor.xml b/src/main/webapp/WEB-INF/descriptor.xml new file mode 100644 index 0000000..997d367 --- /dev/null +++ b/src/main/webapp/WEB-INF/descriptor.xml @@ -0,0 +1,31 @@ + + servicearchive + + tar.gz + + / + + + /home/lucafrosini/workspace/gcat/distro + / + true + + README + LICENSE + changelog.xml + profile.xml + + 755 + true + + + + + target/gcat-1.0.0-SNAPSHOT.war + /gcat + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/gcube-app.xml b/src/main/webapp/WEB-INF/gcube-app.xml new file mode 100644 index 0000000..e44ed56 --- /dev/null +++ b/src/main/webapp/WEB-INF/gcube-app.xml @@ -0,0 +1,10 @@ + + + + gcat + DataCatalogue + 1.0.0-SNAPSHOT + This service allows any client to publish on the gCube Catalogue. + + + diff --git a/src/main/webapp/WEB-INF/profile.xml b/src/main/webapp/WEB-INF/profile.xml new file mode 100644 index 0000000..56eb87e --- /dev/null +++ b/src/main/webapp/WEB-INF/profile.xml @@ -0,0 +1,27 @@ + + + + + Service + + This service allows any client to publish on the gCube Catalogue. + DataCatalogue + gcat + 1.0.0 + +
+ This service allows any client to publish on the gCube Catalogue. + gcat + 1.0.0-SNAPSHOT + + org.gcube.data-publishing + gcat + 1.0.0-SNAPSHOT + + + gcat-1.0.0-SNAPSHOT.war + +
+
+
+
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..c392a96 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,11 @@ + + + + + org.gcube.datacatalogue.sciencecatalogue.ResourceInitializer + + + org.gcube.datacatalogue.sciencecatalogue.ResourceInitializer + /* + + \ No newline at end of file diff --git a/src/test/java/org/gcube/gcat/ContextTest.java b/src/test/java/org/gcube/gcat/ContextTest.java new file mode 100644 index 0000000..78034ec --- /dev/null +++ b/src/test/java/org/gcube/gcat/ContextTest.java @@ -0,0 +1,122 @@ +/** + * + */ +package org.gcube.gcat; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import org.gcube.common.authorization.client.Constants; +import org.gcube.common.authorization.client.exceptions.ObjectNotFound; +import org.gcube.common.authorization.library.AuthorizationEntry; +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.provider.ClientInfo; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Luca Frosini (ISTI - CNR) + * + */ +public class ContextTest { + + private static final Logger logger = LoggerFactory.getLogger(ContextTest.class); + + protected static final String PROPERTIES_FILENAME = "token.properties"; + + private static final String GCUBE_VARNAME = "GCUBE"; + public static final String GCUBE; + + private static final String GCUBE_DEVNEXT_VARNAME = "GCUBE_DEVNEXT"; + public static final String GCUBE_DEVNEXT; + + private static final String GCUBE_DEVNEXT_NEXTNEXT_VARNAME = "GCUBE_DEVNEXT_NEXTNEXT"; + public static final String GCUBE_DEVNEXT_NEXTNEXT; + + public static final String GCUBE_DEVSEC_VARNAME = "GCUBE_DEVSEC"; + public static final String GCUBE_DEVSEC; + + public static final String GCUBE_DEVSEC_DEVVRE_VARNAME = "GCUBE_DEVSEC_DEVVRE"; + public static final String GCUBE_DEVSEC_DEVVRE; + + + private static final String GCUBE_DEVNEXT_ANOTHER_USER_VARNAME = "GCUBE_DEVNEXT_ANOTHER_USER"; + public static final String GCUBE_DEVNEXT_ANOTHER_USER; + + public static final String DEFAULT_TEST_SCOPE; + + public static final String GCUBE_PRE_PROD_PREVRE_VARNAME = "GCUBE_PRE_PROD_PREVRE"; + public static final String GCUBE_PRE_PROD_PREVRE; + + public static final String GCUBE_PRE_PROD_PARTHENOS_REGISTRY_VARNAME = "GCUBE_PRE_PROD_PARTHENOS_REGISTRY"; + public static final String GCUBE_PRE_PROD_PARTHENOS_REGISTRY; + + public static final String ROOT_VARNAME = "ROOT"; + public static final String ROOT; + + static { + Properties properties = new Properties(); + InputStream input = ContextTest.class.getClassLoader().getResourceAsStream(PROPERTIES_FILENAME); + + try { + // load the properties file + properties.load(input); + } catch (IOException e) { + throw new RuntimeException(e); + } + + GCUBE = properties.getProperty(GCUBE_VARNAME); + + GCUBE_DEVNEXT = properties.getProperty(GCUBE_DEVNEXT_VARNAME); + GCUBE_DEVNEXT_NEXTNEXT = properties.getProperty(GCUBE_DEVNEXT_NEXTNEXT_VARNAME); + + GCUBE_DEVSEC = properties.getProperty(GCUBE_DEVSEC_VARNAME); + GCUBE_DEVSEC_DEVVRE = properties.getProperty(GCUBE_DEVSEC_DEVVRE_VARNAME); + + GCUBE_DEVNEXT_ANOTHER_USER = properties.getProperty(GCUBE_DEVNEXT_ANOTHER_USER_VARNAME); + + GCUBE_PRE_PROD_PARTHENOS_REGISTRY = properties.getProperty(GCUBE_PRE_PROD_PARTHENOS_REGISTRY_VARNAME); + GCUBE_PRE_PROD_PREVRE = properties.getProperty(GCUBE_PRE_PROD_PREVRE_VARNAME); + ROOT = properties.getProperty(ROOT_VARNAME); + + DEFAULT_TEST_SCOPE = GCUBE_PRE_PROD_PREVRE; + + } + + public static String getCurrentScope(String token) throws ObjectNotFound, Exception{ + AuthorizationEntry authorizationEntry = Constants.authorizationService().get(token); + String context = authorizationEntry.getContext(); + logger.info("Context of token {} is {}", token, context); + return context; + } + + + public static void setContext(String token) throws ObjectNotFound, Exception{ + SecurityTokenProvider.instance.set(token); + AuthorizationEntry authorizationEntry = Constants.authorizationService().get(token); + ClientInfo clientInfo = authorizationEntry.getClientInfo(); + logger.debug("User : {} - Type : {}", clientInfo.getId(), clientInfo.getType().name()); + String qualifier = authorizationEntry.getQualifier(); + Caller caller = new Caller(clientInfo, qualifier); + AuthorizationProvider.instance.set(caller); + ScopeProvider.instance.set(getCurrentScope(token)); + } + + @BeforeClass + public static void beforeClass() throws Exception{ + setContext(DEFAULT_TEST_SCOPE); + } + + @AfterClass + public static void afterClass() throws Exception{ + SecurityTokenProvider.instance.reset(); + ScopeProvider.instance.reset(); + } + +} diff --git a/src/test/java/org/gcube/gcat/persistence/ckan/CKANLicenseTest.java b/src/test/java/org/gcube/gcat/persistence/ckan/CKANLicenseTest.java new file mode 100644 index 0000000..182225d --- /dev/null +++ b/src/test/java/org/gcube/gcat/persistence/ckan/CKANLicenseTest.java @@ -0,0 +1,28 @@ +package org.gcube.gcat.persistence.ckan; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.persistence.ckan.CKANLicense; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +public class CKANLicenseTest extends ContextTest { + + private static Logger logger = LoggerFactory.getLogger(CKANLicenseTest.class); + + @Test + public void list() throws Exception { + CKANLicense license = new CKANLicense(); + String ret = license.list(); + ObjectMapper mapper = new ObjectMapper(); + JsonNode gotList = mapper.readTree(ret); + Assert.assertTrue(gotList instanceof ArrayNode); + logger.debug("List :\n{}", mapper.writeValueAsString(gotList)); + } + +} diff --git a/src/test/java/org/gcube/gcat/persistence/ckan/CKANOrganizationTest.java b/src/test/java/org/gcube/gcat/persistence/ckan/CKANOrganizationTest.java new file mode 100644 index 0000000..0e8567c --- /dev/null +++ b/src/test/java/org/gcube/gcat/persistence/ckan/CKANOrganizationTest.java @@ -0,0 +1,23 @@ +package org.gcube.gcat.persistence.ckan; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.persistence.ckan.CKANOrganization; +import org.gcube.gcat.persistence.ckan.CKANUtility; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CKANOrganizationTest extends ContextTest { + + private static Logger logger = LoggerFactory.getLogger(CKANOrganizationTest.class); + + @Test + public void getUserRole() throws Exception { + CKANOrganization ckanOrganization = new CKANOrganization(); + ckanOrganization.setApiKey(CKANUtility.getSysAdminAPI()); + ckanOrganization.setName(CKANOrganization.getCKANOrganizationName()); + String ret = ckanOrganization.getUserRole("luca.frosini"); + logger.debug("{}", ret); + } + +} diff --git a/src/test/java/org/gcube/gcat/persistence/ckan/CKANPackageTest.java b/src/test/java/org/gcube/gcat/persistence/ckan/CKANPackageTest.java new file mode 100644 index 0000000..fe32959 --- /dev/null +++ b/src/test/java/org/gcube/gcat/persistence/ckan/CKANPackageTest.java @@ -0,0 +1,185 @@ +package org.gcube.gcat.persistence.ckan; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.persistence.ckan.CKAN; +import org.gcube.gcat.persistence.ckan.CKANPackage; +import org.gcube.gcat.persistence.ckan.CKANResource; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANPackageTest extends ContextTest { + + private static Logger logger = LoggerFactory.getLogger(CKANPackageTest.class); + + private static final String NOTES_KEY = "notes"; + private static final String URL_KEY = "url"; + private static final String PRIVATE_KEY = "private"; + + private static final String ITEM_NAME_VALUE = "restful_transaction_model"; + private static final String LICENSE_VALUE = "CC-BY-SA-4.0"; + private static final String EXTRAS_TYPE_VALUE_VALUE = "EmptyType"; + + + @Test + public void list() throws Exception { + CKANPackage ckanPackage = new CKANPackage(); + ObjectMapper mapper = new ObjectMapper(); + String ret = ckanPackage.list(); + JsonNode gotList = mapper.readTree(ret); + Assert.assertTrue(gotList instanceof ArrayNode); + logger.debug("List :\n{}", mapper.writeValueAsString(gotList)); + } + + /* + * DEV + * Workspace(luca.frosini) > RESTful Transaction Model.pdf + * https://data-d.d4science.org/shub/e03355cd-058a-4e3a-8d5b-cf3676a46840 + * URL url = new URL("https://goo.gl/fSZH1o"); + * + * + * Workspace(luca.frosini) > RESTful Transaction Model v 1.0.pdf + * https://data-d.d4science.org/shub/8ad187c4-8f94-4c47-a780-706d0d33c1bb + * URL url = new URL("https://goo.gl/6vZSmp"); + * + * + * Workspace(luca.frosini) > RESTful Transaction Model v 1.1.pdf + * https://data-d.d4science.org/shub/7b6628e4-b397-4df9-b387-2d643ae0095c + * URL url = new URL("https://goo.gl/jvXMcd"); + * + */ + + + protected CKANPackage createPackage(ObjectMapper mapper) throws Exception { + + ObjectNode itemObjectNode = mapper.createObjectNode(); + itemObjectNode.put(CKAN.NAME_KEY, ITEM_NAME_VALUE); + itemObjectNode.put(CKANPackage.TITLE_KEY, "RESTful Transaction Model"); + itemObjectNode.put(CKANPackage.LICENSE_KEY, LICENSE_VALUE); + itemObjectNode.put(PRIVATE_KEY, false); + itemObjectNode.put(NOTES_KEY, "A research of Luca Frosini"); + itemObjectNode.put(URL_KEY, "http://www.d4science.org"); + + ArrayNode tagArrayNode = itemObjectNode.putArray(CKANPackage.TAGS_KEY); + ObjectNode tagNode = mapper.createObjectNode(); + tagNode.put(CKANPackage.NAME_KEY, "REST"); + tagArrayNode.add(tagNode); + + ArrayNode resourceArrayNode = itemObjectNode.putArray(CKANPackage.RESOURCES_KEY); + ObjectNode resourceNode = mapper.createObjectNode(); + resourceNode.put(CKANResource.NAME_KEY, "RESTful Transaction Model"); + // Workspace(luca.frosini) > RESTful Transaction Model v 1.0.pdf + resourceNode.put(CKANResource.URL_KEY, "https://goo.gl/6vZSmp"); + resourceArrayNode.add(resourceNode); + + ArrayNode extraArrayNode = itemObjectNode.putArray(CKANPackage.EXTRA_TYPES_KEY); + ObjectNode extraNode = mapper.createObjectNode(); + extraNode.put(CKANPackage.EXTRA_TYPES_KEY_KEY, CKANPackage.EXTRA_TYPES_KEY_VALUE_SYSTEM_TYPE); + extraNode.put(CKANPackage.EXTRA_TYPES_VALUE_KEY, EXTRAS_TYPE_VALUE_VALUE); + extraArrayNode.add(extraNode); + + CKANPackage ckanPackage = new CKANPackage(); + ckanPackage.setName(ITEM_NAME_VALUE); + //ckanPackage.setApiKey(CKANUtility.getSysAdminAPI()); + String createdItem = ckanPackage.create(mapper.writeValueAsString(itemObjectNode)); + logger.debug(createdItem); + + return ckanPackage; + } + + @Test + public void create() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + createPackage(mapper); + } + + @Test + public void createReadUpdateUpdatePurge() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + createPackage(mapper); + + CKANPackage ckanPackage = new CKANPackage(); + ckanPackage.setName(ITEM_NAME_VALUE); + String readItem = ckanPackage.read(); + + JsonNode readItemObjectNode = mapper.readTree(readItem); + String readID = readItemObjectNode.get(CKANPackage.NAME_KEY).asText(); + Assert.assertNotNull(readID); + + Assert.assertTrue(ITEM_NAME_VALUE.compareTo(readID)==0); + + String updatedNotes = "A research of Luca Frosini made during the PhD"; + ((ObjectNode) readItemObjectNode).put(NOTES_KEY, updatedNotes); + + ArrayNode resources = (ArrayNode) readItemObjectNode.get(CKANPackage.RESOURCES_KEY); + ObjectNode objectNode = (ObjectNode) resources.get(0); + // Workspace(luca.frosini) > RESTful Transaction Model v 1.1.pdf + objectNode.put(CKANResource.URL_KEY, "https://goo.gl/jvXMcd"); + resources.set(0, objectNode); + + ((ObjectNode) readItemObjectNode).replace(CKANPackage.RESOURCES_KEY, resources); + + ckanPackage = new CKANPackage(); + ckanPackage.setName(ITEM_NAME_VALUE); + String updatedItem = ckanPackage.update(mapper.writeValueAsString(readItemObjectNode)); + logger.trace(updatedItem); + JsonNode updatedItemObjectNode = mapper.readTree(updatedItem); + String gotUpdatedNotes = updatedItemObjectNode.get(NOTES_KEY).asText(); + Assert.assertTrue(gotUpdatedNotes.compareTo(updatedNotes)==0); + + + ckanPackage = new CKANPackage(); + ckanPackage.setName(ITEM_NAME_VALUE); + ((ObjectNode) updatedItemObjectNode).remove(CKANPackage.RESOURCES_KEY); + String secondUpdateItem = ckanPackage.update(mapper.writeValueAsString(updatedItemObjectNode)); + logger.trace(secondUpdateItem); + + /* + ObjectNode patchObjectNode = mapper.createObjectNode(); + String patchedNotes = updatedNotes + " in October 2018"; + patchObjectNode.put(NOTES_KEY, patchedNotes); + patchObjectNode.put(CKANPackage.NAME_KEY, ITEM_NAME_VALUE); + + ckanPackage = new CKANPackage(); + ckanPackage.setName(ITEM_NAME_VALUE); + String patchedItem = ckanPackage.patch(mapper.writeValueAsString(patchObjectNode)); + logger.trace(patchedItem); + JsonNode patchedItemObjectNode = mapper.readTree(patchedItem); + String gotPatchedNotes = patchedItemObjectNode.get(NOTES_KEY).asText(); + Assert.assertTrue(gotPatchedNotes.compareTo(patchedNotes)==0); + */ + + ckanPackage = new CKANPackage(); + ckanPackage.setName(ITEM_NAME_VALUE); + ckanPackage.purge(); + logger.debug("Item {} purge successfully", ITEM_NAME_VALUE); + + } + + @Test + //(expected = NotFoundException.class) + public void read() throws Exception { + CKANPackage ckanPackage = new CKANPackage(); + ckanPackage.setName(ITEM_NAME_VALUE); + String ret = ckanPackage.read(); + logger.debug(ret); + } + + @Test + //(expected = NotFoundException.class) + public void delete() throws Exception { + CKANPackage ckanPackage = new CKANPackage(); + ckanPackage.setName(ITEM_NAME_VALUE); + ckanPackage.delete(true); + } + +} diff --git a/src/test/java/org/gcube/gcat/persistence/ckan/CKANResourceTest.java b/src/test/java/org/gcube/gcat/persistence/ckan/CKANResourceTest.java new file mode 100644 index 0000000..19a03a9 --- /dev/null +++ b/src/test/java/org/gcube/gcat/persistence/ckan/CKANResourceTest.java @@ -0,0 +1,70 @@ +package org.gcube.gcat.persistence.ckan; + +import java.net.URL; +import java.util.UUID; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.persistence.ckan.CKANResource; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class CKANResourceTest extends ContextTest { + + private static final Logger logger = LoggerFactory.getLogger(CKANResourceTest.class); + + /* + * PROD + * Workspace(luca.frosini) > RESTful Transaction Model.pdf + * https://data.d4science.org/shub/ab4daec8-2ce4-43d7-9d37-00b2051e3530 + * URL url = new URL("https://goo.gl/bFME6Q"); + */ + + /* + * DEV + * Workspace(luca.frosini) > RESTful Transaction Model.pdf + * https://data-d.d4science.org/shub/e03355cd-058a-4e3a-8d5b-cf3676a46840 + * URL url = new URL("https://goo.gl/fSZH1o"); + * + * + * Workspace(luca.frosini) > RESTful Transaction Model v 1.0.pdf + * https://data-d.d4science.org/shub/5df695ea-986a-437d-abaf-ed1542a7f5af + * URL url = new URL("https://goo.gl/7ofQwn"); + * + * + * Workspace(luca.frosini) > RESTful Transaction Model v 1.1.pdf + * https://data-d.d4science.org/shub/7b6628e4-b397-4df9-b387-2d643ae0095c + * URL url = new URL("https://goo.gl/jvXMcd"); + * + */ + + @Test + public void testCopyStorageResource() throws Exception { + URL url = new URL("https://goo.gl/fSZH1o"); + + String itemID = UUID.randomUUID().toString(); + CKANResource ckanResource = new CKANResource(itemID); + ckanResource.resourceID = UUID.randomUUID().toString(); + URL finalURL = ckanResource.copyStorageResource(url); + logger.debug("Initial URL is {} - Final URL is {}", url, finalURL); + ckanResource.deleteStorageResource(finalURL); + } + + // @Test + public void testCreate() throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + ObjectNode objectNode = objectMapper.createObjectNode(); + objectNode.put(CKANResource.URL_KEY, "https://data.d4science.org/shub/ab4daec8-2ce4-43d7-9d37-00b2051e3530"); + objectNode.put(CKANResource.ID_KEY, "8422b5d4-626b-46f2-9121-bbc6d443071d"); + + CKANResource ckanResource = new CKANResource("f9221556-4b1f-49f2-8951-a4c30d71cd37"); + String json = ckanResource.getAsString(objectNode); + logger.debug("Going to create Resource {}", json); + ckanResource.create(json); + + } + +} diff --git a/src/test/java/org/gcube/gcat/persistence/ckan/CKANUserTest.java b/src/test/java/org/gcube/gcat/persistence/ckan/CKANUserTest.java new file mode 100644 index 0000000..b3ec109 --- /dev/null +++ b/src/test/java/org/gcube/gcat/persistence/ckan/CKANUserTest.java @@ -0,0 +1,69 @@ +package org.gcube.gcat.persistence.ckan; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.persistence.ckan.CKANUser; +import org.gcube.gcat.persistence.ckan.CKANUtility; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class CKANUserTest extends ContextTest { + + private static Logger logger = LoggerFactory.getLogger(CKANUserTest.class); + + private static final String USERNAME = "pippo"; + + private CKANUser getCKANUser() { + CKANUser user = new CKANUser(); + user.setApiKey(CKANUtility.getSysAdminAPI()); + user.setName(USERNAME); + return user; + } + + @Test + public void list() throws Exception { + CKANUser ckanUser = getCKANUser(); + String ret = ckanUser.list(); + logger.debug("{}", ret); + } + + @Test + public void create() throws Exception { + CKANUser ckanUser = getCKANUser(); + String ret = ckanUser.create(); + logger.debug("{}", ret); + } + + @Test + public void read() throws Exception { + CKANUser ckanUser = getCKANUser(); + String ret = ckanUser.read(); + logger.debug("{}", ret); + } + + public final static String DISPLAY_NAME = "display_name"; + + @Test + public void update() throws Exception { + CKANUser ckanUser = getCKANUser(); + String ret = ckanUser.read(); + ObjectMapper mapper = new ObjectMapper(); + JsonNode readUser = mapper.readTree(ret); + ((ObjectNode) readUser).put(CKANUser.EMAIL, USERNAME+"@gcube.ckan.org"); + ((ObjectNode) readUser).put("state", "active"); + ret = ckanUser.update(ckanUser.getAsString(readUser)); + logger.debug("{}", ret); + } + + @Test + public void delete() throws Exception { + CKANUser ckanUser = getCKANUser(); + ckanUser.delete(false); + } + + +} diff --git a/src/test/java/org/gcube/gcat/persistence/ckan/CKANUtilityTest.java b/src/test/java/org/gcube/gcat/persistence/ckan/CKANUtilityTest.java new file mode 100644 index 0000000..1de38f1 --- /dev/null +++ b/src/test/java/org/gcube/gcat/persistence/ckan/CKANUtilityTest.java @@ -0,0 +1,37 @@ +package org.gcube.gcat.persistence.ckan; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.persistence.ckan.CKANUtility; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Luca Frosini (ISTI - CNR) + */ +public class CKANUtilityTest extends ContextTest { + + private static Logger logger = LoggerFactory.getLogger(CKANPackageTest.class); + + private static final String USERNAME = "luca_frosini"; + + @Test + public void testGetApiKey() throws Exception { + String ckanAPI = CKANUtility.getApiKey(USERNAME); + logger.debug("User {} has the following API key {}", USERNAME, ckanAPI); + } + + @Test + public void testAddUserToOrganization() throws Exception { + CKANUtility.addUserToOrganization(USERNAME, CKANUtility.MEMBER_ROLE, true); + } + + @Test + public void testAddSpecialUserToOrganization() throws Exception { + CKANUtility.addUserToOrganization("luca_frosini", "editor", true); + } + + + +} + diff --git a/src/test/java/org/gcube/gcat/rest/NamespaceTest.java b/src/test/java/org/gcube/gcat/rest/NamespaceTest.java new file mode 100644 index 0000000..4bebb54 --- /dev/null +++ b/src/test/java/org/gcube/gcat/rest/NamespaceTest.java @@ -0,0 +1,20 @@ +package org.gcube.gcat.rest; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.rest.Namespace; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NamespaceTest extends ContextTest { + + private static Logger logger = LoggerFactory.getLogger(NamespaceTest.class); + + @Test + public void list() throws Exception { + Namespace namespace = new Namespace(); + String ret = namespace.list(); + logger.debug("{}", ret); + } + +} diff --git a/src/test/java/org/gcube/gcat/rest/ProfileTest.java b/src/test/java/org/gcube/gcat/rest/ProfileTest.java new file mode 100644 index 0000000..b0fbc0e --- /dev/null +++ b/src/test/java/org/gcube/gcat/rest/ProfileTest.java @@ -0,0 +1,56 @@ +package org.gcube.gcat.rest; + +import java.util.Iterator; + +import javax.ws.rs.core.MediaType; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.rest.Profile; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +public class ProfileTest extends ContextTest { + + private static Logger logger = LoggerFactory.getLogger(ProfileTest.class); + + @Test + public void list() throws Exception { + Profile profile = new Profile(); + String ret = profile.list(); + logger.debug("{}", ret); + } + + + @Test + public void read() throws Exception { + String profileID = "SoBigData.eu: Dataset Metadata NextNext"; + Profile profile = new Profile(); + String ret = profile.read(profileID, MediaType.APPLICATION_XML); + logger.debug("XML :\n{}", ret); + ret = profile.read(profileID, MediaType.APPLICATION_JSON); + logger.debug("JSON : \n{}", ret); + } + + @Test + public void listRead() throws Exception { + Profile profile = new Profile(); + String ret = profile.list(); + ObjectMapper mapper = new ObjectMapper(); + ArrayNode arrayNode = (ArrayNode) mapper.readTree(ret); + logger.debug("Found {} profiles", arrayNode.size()); + Iterator iterator = arrayNode.iterator(); + while(iterator.hasNext()) { + String profileID = iterator.next().asText(); + ret = profile.read(profileID, MediaType.APPLICATION_XML); + logger.debug("XML :\n{}", ret); + ret = profile.read(profileID, MediaType.APPLICATION_JSON); + logger.debug("JSON : \n{}", ret); + } + } + +} diff --git a/src/test/java/org/gcube/gcat/social/SocialServiceTest.java b/src/test/java/org/gcube/gcat/social/SocialServiceTest.java new file mode 100644 index 0000000..1957cdf --- /dev/null +++ b/src/test/java/org/gcube/gcat/social/SocialServiceTest.java @@ -0,0 +1,24 @@ +package org.gcube.gcat.social; + +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.social.SocialService; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class SocialServiceTest extends ContextTest { + + private static final Logger logger = LoggerFactory.getLogger(SocialServiceTest.class); + + @Test + public void testGetUserProfile() throws Exception { + SocialService socialService = new SocialService(); + JsonNode jsonNode = socialService.getGCubeUserProfile(); + ObjectMapper objectMapper = new ObjectMapper(); + logger.debug("gCube User Profile is {}", objectMapper.writeValueAsString(jsonNode)); + } + +} diff --git a/src/test/java/org/gcube/gcat/workspace/StorageHubManagementTest.java b/src/test/java/org/gcube/gcat/workspace/StorageHubManagementTest.java new file mode 100644 index 0000000..6264f98 --- /dev/null +++ b/src/test/java/org/gcube/gcat/workspace/StorageHubManagementTest.java @@ -0,0 +1,106 @@ +package org.gcube.gcat.workspace; + +import java.util.List; +import java.util.Map; + +import org.gcube.common.homelibrary.home.Home; +import org.gcube.common.homelibrary.home.HomeLibrary; +import org.gcube.common.homelibrary.home.HomeManager; +import org.gcube.common.homelibrary.home.HomeManagerFactory; +import org.gcube.common.homelibrary.home.User; +import org.gcube.common.homelibrary.home.workspace.Workspace; +import org.gcube.common.homelibrary.home.workspace.WorkspaceFolder; +import org.gcube.common.homelibrary.home.workspace.WorkspaceItem; +import org.gcube.common.storagehub.client.dsl.FileContainer; +import org.gcube.common.storagehub.client.dsl.FolderContainer; +import org.gcube.common.storagehub.client.dsl.ItemContainer; +import org.gcube.common.storagehub.client.dsl.ListResolver; +import org.gcube.common.storagehub.model.Metadata; +import org.gcube.common.storagehub.model.items.Item; +import org.gcube.common.storagehub.model.service.Version; +import org.gcube.gcat.ContextTest; +import org.gcube.gcat.utils.ApplicationMode; +import org.gcube.gcat.utils.ContextUtility; +import org.gcube.gcat.workspace.StorageHubManagement; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StorageHubManagementTest extends ContextTest { + + private static final Logger logger = LoggerFactory.getLogger(StorageHubManagementTest.class); + + @Test + public void testHL() throws Exception { + ApplicationMode applicationMode = new ApplicationMode(); + applicationMode.start(); + String username = ContextUtility.getUsername(); + HomeManagerFactory factory = HomeLibrary.getHomeManagerFactory(); + HomeManager manager = factory.getHomeManager(); + User user = manager.createUser(username); + @SuppressWarnings("deprecation") + Home home = manager.getHome(user); + Workspace ws = home.getWorkspace(); + WorkspaceFolder workspaceFolder = ws.getRoot(); + List workspaceItems = workspaceFolder.getChildren(true); + for(WorkspaceItem workspaceItem : workspaceItems) { + logger.debug("{} {}{}", workspaceFolder.getType(), workspaceItem.getName(), workspaceItem.isHidden()? " (hidden)":""); + } + } + + @Test + public void myTest() throws Exception { + String folderName = StorageHubManagement.getFolderName(); + logger.debug(folderName); + } + + @Test + public void test() throws Exception { + ApplicationMode applicationMode = new ApplicationMode(); + applicationMode.start(); + StorageHubManagement storageHubManagement = new StorageHubManagement(); + FolderContainer root = storageHubManagement.getWorkspaceRoot(); + storageHubManagement.tree(root); + } + + @Test + public void listFolders() throws Exception { + ApplicationMode applicationMode = new ApplicationMode(); + applicationMode.start(); + StorageHubManagement storageHubManagement = new StorageHubManagement(); + FolderContainer root = storageHubManagement.getWorkspaceRoot(); + storageHubManagement.getCatalogueFolder(); + storageHubManagement.tree(root); + applicationMode.end(); + } + + // @Test + public void deleteFile() throws Exception { + StorageHubManagement storageHubManagement = new StorageHubManagement(); + String id = ""; + storageHubManagement.deleteFileByID(id); + } + + @Test + public void getFileInfo() throws Exception { + StorageHubManagement storageHubManagement = new StorageHubManagement(); + String id = ""; + FileContainer fileContainer = storageHubManagement.getFileByID(id); + logger.debug("StorageHub ID {} - File Name {}", id, fileContainer.get().getName()); + ListResolver listResolver = fileContainer.getAnchestors(); + List> itemContainers = listResolver.getContainers(); + for(ItemContainer itemContainer : itemContainers) { + logger.debug("{}", itemContainer.get().getName()); + } + + Metadata metadata = fileContainer.get().getPropertyMap(); + Map map = metadata.getValues(); + logger.debug("{}", map); + + List versions = fileContainer.getVersions(); + for(Version version : versions){ + logger.debug("Version {} {}", version.getId(), version.getName()); + } + } + +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..1e138b1 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,19 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{0}: %msg%n + + + + + + + + + + + + \ No newline at end of file