diff --git a/pom.xml b/pom.xml index 9fc3f01..6d62d9d 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.gcube.datacatalogue catalogue-util-library - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT Ckan utility library @@ -27,11 +27,11 @@ data-catalogue - 0.4.2 9.4.1208.jre7 UTF-8 UTF-8 2.7.0 + 4.4.1 @@ -39,7 +39,7 @@ org.gcube.distribution maven-portal-bom - LATEST + 3.6.0 pom import @@ -53,12 +53,40 @@ provided - - eu.trentorise.opendata - jackan - ${jackanVersion} - compile + org.gcube.common + gcube-jackson-core + 2.6.0 + + + + org.gcube.common + gcube-jackson-annotations + 2.6.0 + + + + org.gcube.common + gcube-jackson-databind + 2.6.0 + + + + com.google.code.findbugs + jsr305 + 3.0.0 + + + + org.apache.httpcomponents + fluent-hc + ${apache.http.version} + + + + org.apache.httpcomponents + httpclient + 4.4.1 @@ -68,9 +96,9 @@ - org.gcube.data-publishing + org.gcube.data-catalogue gcat-client - [1.0.0, 2.0.0) + [2.0.0-SNAPSHOT, 3.0.0) compile @@ -121,11 +149,13 @@ json-simple compile + org.apache.solr solr-solrj [4.10.2,5.0.0) + org.slf4j slf4j-log4j12 diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/ExtendCkanClient.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/ExtendCkanClient.java index ea07127..bddf4be 100644 --- a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/ExtendCkanClient.java +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/ExtendCkanClient.java @@ -11,21 +11,22 @@ import java.util.Map; import javax.annotation.Nullable; +import org.apache.http.client.fluent.Request; +import org.apache.http.client.fluent.Response; +import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; +import org.gcube.datacatalogue.ckanutillibrary.jackan.CkanClient; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroup; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanOrganization; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResponse; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.CkanException; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.JackanException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; -import eu.trentorise.opendata.jackan.CkanClient; -import eu.trentorise.opendata.jackan.exceptions.CkanException; -import eu.trentorise.opendata.jackan.exceptions.JackanException; -import eu.trentorise.opendata.jackan.internal.org.apache.http.client.fluent.Request; -import eu.trentorise.opendata.jackan.internal.org.apache.http.client.fluent.Response; -import eu.trentorise.opendata.jackan.model.CkanGroup; -import eu.trentorise.opendata.jackan.model.CkanOrganization; -import eu.trentorise.opendata.jackan.model.CkanResponse; + /** diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/MarshUnmarshCkanObject.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/MarshUnmarshCkanObject.java index 69959e8..022ee1d 100644 --- a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/MarshUnmarshCkanObject.java +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/MarshUnmarshCkanObject.java @@ -2,14 +2,14 @@ package org.gcube.datacatalogue.ckanutillibrary.ckan; import java.io.IOException; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.gcube.com.fasterxml.jackson.core.JsonParseException; +import org.gcube.com.fasterxml.jackson.core.JsonProcessingException; +import org.gcube.com.fasterxml.jackson.databind.JsonMappingException; +import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDataset; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroup; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResource; -import eu.trentorise.opendata.jackan.model.CkanDataset; -import eu.trentorise.opendata.jackan.model.CkanGroup; -import eu.trentorise.opendata.jackan.model.CkanResource; /** @@ -98,11 +98,13 @@ public class MarshUnmarshCkanObject { * To json value resource. * * @param resource the resource + * @param method the method * @return the string * @throws JsonProcessingException the json processing exception */ - public static String toJsonValueResource(CkanResource resource) throws JsonProcessingException{ - return ExtendCkanClient.getObjectMapper().writeValueAsString(resource); + public static String toJsonValueResource(CkanResource resource, METHOD method) throws JsonProcessingException{ + ObjectMapper om = getObjectMapper(method, CkanResource.class); + return om.writeValueAsString(resource); } diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/PatchedCkan.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/PatchedCkan.java index b7501fd..519907c 100644 --- a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/PatchedCkan.java +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/ckan/PatchedCkan.java @@ -1,11 +1,11 @@ package org.gcube.datacatalogue.ckanutillibrary.ckan; -import eu.trentorise.opendata.jackan.CkanClient; -import eu.trentorise.opendata.jackan.exceptions.CkanException; -import eu.trentorise.opendata.jackan.exceptions.JackanException; -import eu.trentorise.opendata.jackan.model.CkanGroup; -import eu.trentorise.opendata.jackan.model.CkanOrganization; -import eu.trentorise.opendata.jackan.model.CkanResponse; +import org.gcube.datacatalogue.ckanutillibrary.jackan.CkanClient; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroup; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanOrganization; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResponse; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.CkanException; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.JackanException; /** * The Interface PatchedCkan. diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CheckedCkanClient.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CheckedCkanClient.java new file mode 100644 index 0000000..06017df --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CheckedCkanClient.java @@ -0,0 +1,285 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.jackan; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.gcube.datacatalogue.ckanutillibrary.server.utils.GenericUtils.checkNotEmpty; +import static org.gcube.datacatalogue.ckanutillibrary.server.utils.GenericUtils.isNotEmpty; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; + +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDataset; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDatasetBase; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroup; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanLicense; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanOrganization; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResource; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResourceBase; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.CkanNotFoundException; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.CkanValidationException; + +/** + * This client performs additional checks when writing to CKAN to ensure written + * content is correct. For this reason it might do additional calls and results + * of validation might be different from default Ckan ones. But if Ckan actually + * performed all the checks it should do there wouldn't be any need of this + * class as well.. + * + *

+ * Note: For {@code create} operations, this client fails if item is already + * existing on the server. This behaviour is different from ckan default + * {@code upsert} behaviour (update an existing) to prevent misuse. + *

+ * + * @author David Leoni + * @since 0.4.1 + */ +public class CheckedCkanClient extends CkanClient { + + protected CheckedCkanClient() { + super(); + } + + protected CheckedCkanClient(String url) { + super(url); + } + + public CheckedCkanClient(String catalogUrl, @Nullable String ckanToken) { + super(catalogUrl, ckanToken); + } + + /** + * Returns a builder instance. The builder is not threadsafe and you can use + * one builder instance to build only one client instance. + */ + public static CkanClient.Builder builder() { + return CkanClient.newBuilder(new CheckedCkanClient()); + } + + private void checkUrl(String url, String prependedErrorMessage) { + try { + new URL(url).toURI(); + } catch (MalformedURLException | URISyntaxException ex) { + throw new CkanValidationException(String.valueOf(prependedErrorMessage) + " -- Ill-formed url:" + url, this, + ex); + } + } + + private void checkUuid(String uuid, String prependedErrorMessage) { + try { + UUID.fromString(uuid); + } catch (Exception ex) { + throw new CkanValidationException(String.valueOf(prependedErrorMessage) + " -- Ill-formed uuid:" + uuid, + this, ex); + } + + } + + /** + * {@inheritDoc} + * + *

+ * NOTE: In CheckedCkanClient {@code create} operations fail if item already + * exists. This is different from Ckan default behaviour, which updates + * items if they already exist. + *

+ */ + @Override + public synchronized CkanOrganization createOrganization(CkanOrganization org) { + if (org.getId() != null) { + + checkUuid(org.getId(), + "Jackan validation failed! Tried to create organization with invalid id:" + org.getId()); + + try { + getOrganization(org.getId()); + throw new CkanValidationException( + "Jackan validation failed! Tried to create organization with existing id! " + org.getId(), + this); + } catch (CkanNotFoundException ex) { + + } + } + + return super.createOrganization(org); + } + + /** + * {@inheritDoc} + * + *

+ * NOTE: In CheckedCkanClient {@code create} operations fail if item already + * exists. This is different from Ckan default behaviour, which updates + * items if they already exist. + *

+ */ + @Override + public synchronized CkanResource createResource(CkanResourceBase resource) { + + if (resource.getId() != null) { + checkUuid(resource.getId(), + "Jackan validation failed! Tried to create resource with invalid id:" + resource.getId()); + + try { + getResource(resource.getId()); + throw new CkanValidationException( + "Jackan validation failed! Tried to create resource with existing id! " + resource.getId(), + this); + } catch (CkanNotFoundException ex) { + + } + } + + // Only check for URL if no file is given + // If a file is given, the URL field is statically set to "upload" + if (resource.getUpload() == null) + checkUrl(resource.getUrl(), + "Jackan validation error! Tried to create resource " + resource.getId() + " with wrong url!"); + + return super.createResource(resource); + } + + @Override + public synchronized CkanResource updateResource(CkanResourceBase resource) { + + checkUrl(resource.getUrl(), + "Jackan validation error! Tried to update resource " + resource.getId() + " with wrong url!"); + + return super.updateResource(resource); + } + + @Override + public synchronized CkanResource patchUpdateResource(CkanResourceBase resource) { + + checkUrl(resource.getUrl(), + "Jackan validation error! Tried to patch update resource " + resource.getId() + " with wrong url!"); + + return super.patchUpdateResource(resource); + } + + /** + * {@inheritDoc} + * + *

+ * NOTE: In CheckedCkanClient {@code create} operations fail if item already + * exists. This is different from Ckan default behaviour, which updates + * items if they already exist. + *

+ */ + @Override + public synchronized CkanGroup createGroup(CkanGroup group) { + + if (group.getId() != null) { + + checkUuid(group.getId(), + "Jackan validation failed! Tried to create group with invalid id:" + group.getId()); + + try { + getGroup(group.getId()); + throw new CkanValidationException( + "Jackan validation failed! Tried to create group with existing id! " + group.getId(), this); + } catch (CkanNotFoundException ex) { + + } + } + + return super.createGroup(group); + } + + private void checkGroupsExist(Iterable groups, String prependedErrorMessage) { + if (groups != null) { + for (CkanGroup group : groups) { + checkNotNull(group, String.valueOf(prependedErrorMessage) + " -- Found null group! "); + checkNotEmpty(group.idOrName(), + String.valueOf(prependedErrorMessage) + " -- Found group with both id and name invalid!"); + + try { + getGroup(group.idOrName()); + } catch (CkanNotFoundException ex) { + throw new CkanValidationException( + prependedErrorMessage + " -- Tried to refer to non existing group " + group.idOrName(), + this, ex); + } + } + } + + } + + private void checkLicenseExist(@Nullable String licenseId, String prependedErrorMessage) { + if (isNotEmpty(licenseId)) { + List licenseList = getLicenseList(); + boolean found = false; + for (CkanLicense lic : licenseList) { + if (licenseId.equals(lic.getId())) { + found = true; + break; + } + } + if (!found) { + throw new CkanValidationException(String.valueOf(prependedErrorMessage) + " -- licenseId '" + licenseId + + "' doesn't belong to allowed licenses: " + licenseList.toString(), this); + } + } + } + + /** + * {@inheritDoc} + * + *

+ * NOTE: In CheckedCkanClient {@code create} operations fail if item already + * exists. This is different from Ckan default behaviour, which updates + * items if they already exist. + *

+ */ + @Override + public synchronized CkanDataset createDataset(CkanDatasetBase dataset) { + + checkGroupsExist(dataset.getGroups(), "Jackan validation error when creating dataset " + dataset.getName()); + + checkLicenseExist(dataset.getLicenseId(), "Jackan validation error when creating dataset " + dataset.getName()); + + return super.createDataset(dataset); + } + + @Override + public synchronized CkanDataset updateDataset(CkanDatasetBase dataset) { + + checkGroupsExist(dataset.getGroups(), "Jackan validation error when updating dataset " + dataset.getName()); + + checkLicenseExist(dataset.getLicenseId(), "Jackan validation error updating dataset " + dataset.getName()); + + return super.updateDataset(dataset); + } + + @Override + public synchronized CkanDataset patchUpdateDataset(CkanDatasetBase dataset) { + + checkGroupsExist(dataset.getGroups(), + "Jackan validation error when patch updating dataset " + dataset.getName()); + + checkLicenseExist(dataset.getLicenseId(), + "Jackan validation error when patch updating dataset " + dataset.getName()); + + return super.patchUpdateDataset(dataset); + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CkanClient.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CkanClient.java new file mode 100644 index 0000000..f5f8ca4 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CkanClient.java @@ -0,0 +1,2011 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.jackan; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.gcube.datacatalogue.ckanutillibrary.server.utils.GenericUtils.checkNotEmpty; +import static org.gcube.datacatalogue.ckanutillibrary.server.utils.GenericUtils.removeTrailingSlash; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import javax.annotation.Nullable; + +import org.apache.http.client.fluent.Request; +import org.apache.http.client.fluent.Response; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.gcube.com.fasterxml.jackson.annotation.JsonIgnore; +import org.gcube.com.fasterxml.jackson.annotation.JsonInclude.Include; +import org.gcube.com.fasterxml.jackson.databind.DeserializationFeature; +import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; +import org.gcube.com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDataset; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDatasetBase; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDatasetRelationship; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanError; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroup; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroupOrgBase; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanLicense; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanOrganization; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanPair; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResource; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResourceBase; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResponse; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanTag; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanTagBase; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanUser; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanUserBase; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanVocabulary; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanVocabularyBase; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.CkanAuthorizationException; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.CkanException; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.CkanNotFoundException; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.CkanValidationException; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.JackanException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Charsets; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.io.CharStreams; + +/** + * Client to access a ckan instance. Threadsafe. + *

+ * The client is a thin wrapper upon Ckan api, thus one method call should + * correspond to only one web api call. This means sometimes to get a full + * object from Ckan, you will need to do a second call. + *

+ *

+ * You can create clients either with constructors or the + * {@link CkanClient#builder() builder()} method if you need to set more + * connection parameters (i.e. proxy, timeout, ..). + *

+ *

+ * For writing to Ckan you might want to use {@link CheckedCkanClient} which + * does additional checks to ensure written content is correct. + *

+ * + * @author David Leoni, Ivan Tankoyeu + * + */ +public class CkanClient { + + /** + * CKAN uses timestamps like '1970-01-01T01:00:00.000010' in UTC timezone, + * has precision up to microsecond and doesn't append 'Z' to timestamps. The + * format respects + * ISO 8601 + * standard. In Jackan we store it as {@link java.sql.Timestamp} or + * {@code null} if parse is not successful. + * + * @see #parseTimestamp(java.lang.String) + * @see #formatTimestamp(java.sql.Timestamp) + * + * @since 0.4.1 + */ + public static final String CKAN_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS"; + + /** + * Found pattern "2013-12-17T00:00:00" in resource.date_modified in + * dati.toscana: + * http://dati.toscana.it/api/3/action/package_show?id=alluvioni_bacreg See + * also ckan issue + * 874 and ckan pull + * 2519 + * + * @since 0.4.1 + */ + public static final String CKAN_NO_MILLISECS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; + + /** + * Notice that even for the same api version (at least for versions up to 3 + * included) different CKAN instances can behave quite differently, either + * for differences in software or custom server permissions. + */ + public static final ImmutableList SUPPORTED_API_VERSIONS = ImmutableList.of(3); + + /** Default timeout in millisecs */ + public static final int DEFAULT_TIMEOUT = 15000; + + /** + * Sometimes we get back Python "None" as a string instead of proper JSON + * null + * + * @since 0.4.1 + */ + public static final String NONE = "None"; + + private static final Logger LOG = LoggerFactory.getLogger(CkanClient.class.getName()); + + private static final String COULDNT_JSONIZE = "Couldn't jsonize the provided "; + + @Nullable + private static ObjectMapper objectMapper; + + private static final Map OBJECT_MAPPERS_FOR_POSTING = new HashMap(); + + private String catalogUrl; + + @Nullable + private String ckanToken; + + @Nullable + private String proxy; + + /** connection timeout in millisecs */ + private int timeout; + + @JsonSerialize(as = CkanResourceBase.class) + private abstract static class CkanResourceForPosting { + } + + @JsonSerialize(as = CkanDatasetBase.class) + private abstract static class CkanDatasetForPosting { + } + + @JsonSerialize(as = CkanGroupOrgBase.class) + private abstract static class CkanGroupOrgForPosting { + } + + @JsonSerialize(as = GroupForDatasetPosting.class) + abstract static class GroupForDatasetPosting extends CkanGroupOrgBase { + + @JsonIgnore + @Override + public List getUsers() { + return super.getUsers(); + } + } + + @JsonSerialize(as = CkanUserBase.class) + private abstract static class CkanUserForPosting { + } + + @JsonSerialize(as = CkanTagBase.class) + private abstract static class CkanTagForPosting { + } + + /** + * Configures the provided Jackson ObjectMapper exactly as the internal JSON + * mapper used for reading operations. If you want to perform + * create/update/delete operations, use + * {@link #configureObjectMapperForPosting(com.fasterxml.jackson.databind.ObjectMapper, java.lang.Class) } + * instead. + * + * @param om + * a Jackson object mapper + * @since 0.4.1 + */ + public static void configureObjectMapper(ObjectMapper om) { + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + om.registerModule(new JackanModule()); + } + + /** + * Configures the provided Jackson ObjectMapper for create/update/delete + * operations of Ckan objects. For reading and generic + * serialization/deserialization of Ckan objects, use + * {@link #configureObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) } + * instead. For future compatibility you will need a different object mapper + * for each class you want to post to ckan. DO NOT call + * {@link #configureObjectMapper(com.fasterxml.jackson.databind.ObjectMapper) } + * on the mapper prior to this call. + * + * @param om + * a Jackson object mapper + * @param clazz + * the class of the objects you wish to create/update/delete. + * @since 0.4.1 + */ + public static void configureObjectMapperForPosting(ObjectMapper om, Class clazz) { + configureObjectMapper(om); + om.setSerializationInclusion(Include.NON_NULL); + om.addMixInAnnotations(CkanResource.class, CkanResourceForPosting.class); + om.addMixInAnnotations(CkanDataset.class, CkanDatasetForPosting.class); + om.addMixInAnnotations(CkanOrganization.class, CkanGroupOrgForPosting.class); + if (CkanDatasetBase.class.isAssignableFrom(clazz)) { + // little fix for + // https://github.com/opendatatrentino/jackan/issues/19 + om.addMixInAnnotations(CkanGroup.class, GroupForDatasetPosting.class); + } else { + om.addMixInAnnotations(CkanGroup.class, CkanGroupOrgForPosting.class); + } + + om.addMixInAnnotations(CkanUser.class, CkanUserForPosting.class); + om.addMixInAnnotations(CkanTag.class, CkanTagForPosting.class); + } + + /** + * Retrieves the Jackson object mapper configured for creation/update + * operations. Internally, Object mapper is initialized at first call. + * + * @param clazz + * the class you want to post. For generic class, just put + * Object.class + * @since 0.4.1 + */ + static ObjectMapper getObjectMapperForPosting(Class clazz) { + checkNotNull(clazz, "Invalid class! If you don't know the class just use Object.class"); + + if (OBJECT_MAPPERS_FOR_POSTING.get(clazz.getName()) == null) { + LOG.info("Generating ObjectMapper for posting class {0}", clazz); + ObjectMapper om = new ObjectMapper(); + configureObjectMapperForPosting(om, clazz); + OBJECT_MAPPERS_FOR_POSTING.put(clazz.getName(), om); + } + + return OBJECT_MAPPERS_FOR_POSTING.get(clazz.getName()); + } + + /** + * Retrieves the Jackson object mapper for reading operations. Internally, + * Object mapper is initialized at first call. + */ + static ObjectMapper getObjectMapper() { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + configureObjectMapper(objectMapper); + } + return objectMapper; + } + + /** + * The timeout expressed in milliseconds. By default it is + * {@link #DEFAULT_TIMEOUT}. + */ + public int getTimeout() { + return timeout; + } + + protected CkanClient() { + this.timeout = DEFAULT_TIMEOUT; + this.catalogUrl = ""; + } + + /** + * Creates a Ckan client with null token + * + * @param catalogUrl + * the catalog url i.e. http://data.gov.uk. Internally, it will + * be stored in a normalized format (to avoid i.e. trailing + * slashes). + */ + public CkanClient(String catalogUrl) { + this(); + checkNotEmpty(catalogUrl, "invalid ckan catalog url"); + this.catalogUrl = removeTrailingSlash(catalogUrl); + } + + /** + * Creates a Ckan client with null token + * + * @param catalogUrl + * the catalog url i.e. http://data.gov.uk. Internally, it will + * be stored in a normalized format (to avoid i.e. trailing + * slashes). + * @param ckanToken + * the token used for authorization in ckan api + * + */ + public CkanClient(String catalogUrl, @Nullable String ckanToken) { + this(catalogUrl); + this.ckanToken = ckanToken; + } + + /** + * Returns a new client builder. + * + * The builder is not threadsafe and you can use one builder instance to + * build only one client instance. + * + */ + public static CkanClient.Builder builder() { + return new Builder(new CkanClient()); + } + + /** + * Builder for the client. The builder is not threadsafe and you can use one + * builder instance to build only one client instance. + * + * @author David Leoni + * + */ + public static class Builder { + private CkanClient client; + private boolean created; + + protected CkanClient getClient() { + return client; + } + + protected boolean getCreated() { + return created; + } + + protected void checkNotCreated() { + if (created) { + throw new IllegalStateException("Builder was already used to create a client!"); + } + } + + protected Builder(CkanClient client) { + checkNotNull(client); + this.client = client; + this.created = false; + } + + /** + * Sets the catalog url i.e. http://data.gov.uk. + * + * Internally, it will be stored in a normalized format (to avoid i.e. + * trailing slashes). + */ + public Builder setCatalogUrl(String catalogUrl) { + checkNotCreated(); + checkNotEmpty(catalogUrl, "invalid ckan catalog url"); + this.client.catalogUrl = removeTrailingSlash(catalogUrl); + return this; + } + + /** + * Sets the private token string for ckan repository + */ + public Builder setCkanToken(@Nullable String ckanToken) { + checkNotCreated(); + this.client.ckanToken = ckanToken; + return this; + } + + /** + * Sets the proxy used to perform GET and POST calls + * + * @param proxyUri the proxy used by the client, usually in a format with + * address and port like {@code my.own-proxy.org:1234} + */ + public Builder setProxy(@Nullable String proxyUri) { + checkNotCreated(); + + if (proxyUri == null) { + this.client.proxy = null; + } else { + this.client.proxy = removeTrailingSlash(proxyUri); + } + + return this; + } + + /** + * Sets the connection timeout expressed as number of milliseconds. Must + * be greater than zero, otherwise IllegalArgumentException is thrown. + * + * @throws IllegalArgumentException + * is value is less than 1. + */ + public Builder setTimeout(int timeout) { + checkNotCreated(); + checkArgument(timeout > 0, "Timeout must be > 0 ! Found instead %s", timeout); + this.client.timeout = timeout; + return this; + } + + public CkanClient build() { + checkNotCreated(); + checkNotEmpty(this.client.catalogUrl, "Invalid catalog url!"); + this.created = true; + return this.client; + } + } + + @Override + public String toString() { + String maskedToken = ckanToken == null ? null : "*****MASKED_TOKEN*******"; + return "CkanClient{" + "catalogURL=" + catalogUrl + ", ckanToken=" + maskedToken + '}'; + } + + /** + * Calculates a full url out of the provided params + * + * @param path + * something like /api/3/package_show + * @param params + * list of key, value parameters. They must be not be url + * encoded. i.e. "id","laghi-monitorati-trento" + * @return the full url to be called. + * @throws JackanException + * if there is any error building the url + */ + private String calcFullUrl(String path, Object[] params) { + checkNotNull(path); + + try { + StringBuilder sb = new StringBuilder().append(catalogUrl) + .append(path); + for (int i = 0; i < params.length; i += 2) { + sb.append(i == 0 ? "?" : "&") + .append(URLEncoder.encode(params[i].toString(), "UTF-8")) + .append("=") + .append(URLEncoder.encode(params[i + 1].toString(), "UTF-8")); + } + return sb.toString(); + } catch (Exception ex) { + throw new JackanException("Error while building url to perform GET! \n path: " + path + " \n params: " + + Arrays.toString(params), ex); + } + } + + /** + * Configures the request. Should work both for GETs and POSTs. + */ + protected Request configureRequest(Request request) { + if (ckanToken != null) { + request.addHeader("Authorization", ckanToken); + } + if (proxy != null) { + request.viaProxy(proxy); + } + request.socketTimeout(this.timeout) + .connectTimeout(this.timeout); + + return request; + } + + /** + * Performs HTTP GET on server. If {@link CkanResponse#isSuccess()} is false + * throws {@link CkanException}. + * + * @param + * @param responseType + * a descendant of CkanResponse + * @param path + * something like /api/3/package_show + * @param params + * list of key, value parameters. They must be not be url + * encoded. i.e. "id","laghi-monitorati-trento" + * @throws CkanException + * on error + */ + private T getHttp(Class responseType, String path, Object... params) { + + checkNotNull(responseType); + checkNotNull(path); + + String fullUrl = calcFullUrl(path, params); + + T ckanResponse; + String returnedText; + + try { + LOG.info("getting {0}", fullUrl); + Request request = Request.Get(fullUrl); + + configureRequest(request); + + Response response = request.execute(); + + InputStream stream = response.returnResponse() + .getEntity() + .getContent(); + + try (InputStreamReader reader = new InputStreamReader(stream, Charsets.UTF_8)) { + returnedText = CharStreams.toString(reader); + } + } catch (Exception ex) { + throw new CkanException("Error while performing GET. Request url was: " + fullUrl, this, ex); + } + try { + ckanResponse = getObjectMapper().readValue(returnedText, responseType); + } catch (Exception ex) { + throw new CkanException( + "Couldn't interpret json returned by the server! Returned text was: " + returnedText, this, ex); + } + + if (!ckanResponse.isSuccess()) { + throwCkanException("Error while performing GET. Request url was: " + fullUrl, ckanResponse); + } + return ckanResponse; + } + + /** + * Throws {@link CkanException} or a subclass of it according to + * {@link CkanError#getType()} + * + * @throws CkanException + * @since 0.4.1 + */ + protected void throwCkanException(String msg, T ckanResponse) { + if (ckanResponse.getError() != null && ckanResponse.getError() + .getType() != null) { + switch (ckanResponse.getError() + .getType()) { + case CkanError.NOT_FOUND_ERROR: + throw new CkanNotFoundException(msg, ckanResponse, this); + case CkanError.VALIDATION_ERROR: + throw new CkanValidationException(msg, ckanResponse, this); + case CkanError.AUTHORIZATION_ERROR: + throw new CkanAuthorizationException(msg, ckanResponse, this); + } + + } + + throw new CkanException(msg, ckanResponse, this); + } + + /** + * + * POSTs a body via HTTP. If {@link CkanResponse#isSuccess()} is false + * throws {@link CkanException}. + * + * @param responseType + * a descendant of CkanResponse + * @param path + * something like /api/3/action/package_create + * @param body + * the body of the POST + * @param contentType + * @param params + * list of key, value parameters. They must be not be url + * encoded. i.e. "id","laghi-monitorati-trento" + * @throws CkanException + * on error + */ + private T postHttp(Class responseType, String path, String body, + ContentType contentType, Object... params) { + checkNotNull(responseType); + checkNotNull(path); + checkNotNull(body); + checkNotNull(contentType); + + String fullUrl = calcFullUrl(path, params); + + T ckanResponse; + String returnedText; + + try { + LOG.info("Posting to url {0}", fullUrl); + LOG.info("Sending body:{0}", body); + Request request = Request.Post(fullUrl); + + configureRequest(request); + + Response response = request.bodyString(body, contentType) + .execute(); + + InputStream stream = response.returnResponse() + .getEntity() + .getContent(); + + try (InputStreamReader reader = new InputStreamReader(stream, Charsets.UTF_8)) { + returnedText = CharStreams.toString(reader); + } + } catch (Exception ex) { + throw new CkanException("Error while performing a POST! Request url is:" + fullUrl, this, ex); + } + + try { + ckanResponse = getObjectMapper().readValue(returnedText, responseType); + } catch (Exception ex) { + throw new CkanException( + "Couldn't interpret json returned by the server! Returned text was: " + returnedText, this, ex); + } + + if (!ckanResponse.isSuccess()) { + throwCkanException("Error while performing a POST! Request url is:" + fullUrl, ckanResponse); + } + return ckanResponse; + + } + + /** + * Update the data associated to the given resource. + * + * @param responseType + * a descendant of CkanResponse + * @param path + * something like /api/3/action/package_create + * @param resource + * a {@link CkanResourceBase} object with a data file set in the + * {@link CkanResourceBase#upload} field + * + * @throws CkanException + */ + private T postHttpResourceFile(Class responseType, String path, + CkanResourceBase resource) { + checkNotNull(responseType); + checkNotNull(path); + checkNotNull(resource); + checkNotNull(resource.getUpload()); + checkNotNull(resource.getPackageId()); + + String fullUrl = calcFullUrl(path, new Object[] {}); + + T ckanResponse; + String returnedText; + + try { + LOG.info("Posting to url {0}", fullUrl); + Request request = Request.Post(fullUrl); + + configureRequest(request); + + MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create() + .addBinaryBody("upload", resource.getUpload(), + ContentType.create( + "application/octet-stream", + Charset.forName("UTF-8")), + resource.getUpload() + .getName()) + .addTextBody("size", resource.getSize(), + ContentType.TEXT_PLAIN) + .addTextBody("id", resource.getId(), + ContentType.TEXT_PLAIN) + .addTextBody("url", "upload", + ContentType.TEXT_PLAIN) + .addTextBody("package_id", + resource.getPackageId(), + ContentType.TEXT_PLAIN); + + if (resource.getFormat() != null) + entityBuilder.addTextBody("format", resource.getFormat(), ContentType.TEXT_PLAIN); + if (resource.getMimetype() != null) + entityBuilder.addTextBody("mimetype", resource.getMimetype(), ContentType.TEXT_PLAIN); + if (resource.getLastModified() != null) + entityBuilder.addTextBody("last_modified", resource.getLastModified(), ContentType.TEXT_PLAIN); + + entityBuilder.setCharset(Charset.forName("UTF-8")); + + Response response = request.body(entityBuilder.build()) + .execute(); + + InputStream stream = response.returnResponse() + .getEntity() + .getContent(); + + try (InputStreamReader reader = new InputStreamReader(stream, Charsets.UTF_8)) { + returnedText = CharStreams.toString(reader); + } + } catch (Exception ex) { + throw new CkanException("Error while performing a POST! Request url is:" + fullUrl, this, ex); + } + + try { + ckanResponse = getObjectMapper().readValue(returnedText, responseType); + } catch (Exception ex) { + throw new CkanException( + "Couldn't interpret json returned by the server! Returned text was: " + returnedText, this, ex); + } + + if (!ckanResponse.isSuccess()) { + throwCkanException("Error while performing a POST! Request url is:" + fullUrl, ckanResponse); + } + return ckanResponse; + + } + + /** + * Returns the catalog URL (normalized). + */ + public String getCatalogUrl() { + return catalogUrl; + } + + /** + * Returns the private CKAN token. + */ + public String getCkanToken() { + return ckanToken; + } + + private static void checkCatalogUrl(String catalogUrl) { + checkNotEmpty(catalogUrl, "invalid catalog url"); + } + + /** + * Returns the URL of dataset page in the catalog website. + * + * Valid URLs have this format with the name: http://dati.trentino.it/dataset/impianti-di-risalita- + * vivifiemme-2013 + * + * @param datasetIdOrName + * either the dataset's {@link CkanDataset#getId() alphanumerical + * id} (preferred as it is more stable) or the + * {@link CkanDataset#getName() dataset name} + * + * @param catalogUrl + * i.e. http://dati.trentino.it + */ + public static String makeDatasetUrl(String catalogUrl, String datasetIdOrName) { + checkCatalogUrl(catalogUrl); + checkNotEmpty(datasetIdOrName, "invalid dataset identifier"); + return removeTrailingSlash(catalogUrl) + "/dataset/" + datasetIdOrName; + } + + /** + * + * Returns the URL of resource page in the catalog website. + * + * Valid URLs have this format with the dataset name + * 'impianti-di-risalita-vivifiemme-2013': http://dati.trentino.it/dataset/impianti-di-risalita- + * vivifiemme-2013/resource/779d1d9d-9370-47f4-a194-1b0328c32f02 + * + * @param catalogUrl + * i.e. http://dati.trentino.it + * @param datasetIdOrName + * either the dataset's alphanumerical {@link CkanDataset#getId() + * id} (preferred as it is more stable) or the + * {@link CkanDataset#getName() dataset name} + * + * @param resourceId + * the {@link CkanResource#getId() alphanumerical id} of the + * resource (DON'T use {@link CkanResource#getName() resource + * name}) + */ + public static String makeResourceUrl(String catalogUrl, String datasetIdOrName, String resourceId) { + checkCatalogUrl(catalogUrl); + checkNotEmpty(datasetIdOrName, "invalid dataset identifier"); + checkNotEmpty(resourceId, "invalid resource id"); + return makeDatasetUrl(catalogUrl, datasetIdOrName) + "/resource/" + resourceId; + } + + /** + * + * Reconstructs the URL of group page in the catalog website. + * + * Valid URLs have this format with the group name + * 'gestione-del-territorio': + * + * http://dati.trentino.it/group/gestione-del-territorio + * + * @param catalogUrl + * i.e. http://dati.trentino.it + * @param groupIdOrName + * the group's alphanumerical id (preferred as more stable) or + * the group name as in {@link CkanGroup#getName()}. + */ + public static String makeGroupUrl(String catalogUrl, String groupIdOrName) { + checkCatalogUrl(catalogUrl); + checkNotEmpty(groupIdOrName, "invalid group identifier"); + return removeTrailingSlash(catalogUrl) + "/group/" + groupIdOrName; + } + + /** + * + * Given some organization parameters, reconstruct the URL of organization + * page in the catalog website. + * + * Valid URLs have this format with the organization name + * 'comune-di-trento': + * + * http://dati.trentino.it/organization/comune-di-trento + * + * @param catalogUrl + * i.e. http://dati.trentino.it + * @param orgIdOrName + * the organization's alphanumerical id (preferred as more + * stable), + * or the name as in {@link CkanOrganization#getName()} + */ + public static String makeOrganizationUrl(String catalogUrl, String orgIdOrName) { + checkCatalogUrl(catalogUrl); + checkNotEmpty(orgIdOrName, "invalid organization identifier"); + return removeTrailingSlash(catalogUrl) + "/organization/" + orgIdOrName; + } + + /** + * Returns list of dataset names like i.e. limestone-pavement-orders + * + * @throws JackanException + * on error + */ + public synchronized List getDatasetList() { + return getHttp(DatasetListResponse.class, "/api/3/action/package_list").result; + } + + /** + * + * @param limit + * @param offset + * Starts with 0 included. getDatasetList(1,0) will return + * exactly one dataset, if catalog is not empty. + * @return list of data names like i.e. limestone-pavement-orders + * @throws JackanException + * on error + */ + public synchronized List getDatasetList(int limit, int offset) { + return getHttp(DatasetListResponse.class, "/api/3/action/package_list", "limit", limit, "offset", + offset).result; + } + + /** + * Returns the list of available licenses in the ckan catalog. + */ + public synchronized List getLicenseList() { + return getHttp(LicenseListResponse.class, "/api/3/action/license_list").result; + } + + /** + * Returns the latest api version supported by the catalog + * + * @throws JackanException + * on error + */ + public synchronized int getApiVersion() { + for (int i = 5; i >= 1; i--) { // this is demential. But /api always + // gives { "version": 1} .... + try { + return getApiVersion(i); + } catch (Exception ex) { + + } + } + throw new CkanException("Error while getting api version!", this); + } + + /** + * Returns the given api number + * + * @throws CkanException + * on error + */ + private synchronized int getApiVersion(int number) { + String fullUrl = catalogUrl + "/api/" + number; + LOG.info("getting {0}", fullUrl); + try { + Request request = Request.Get(fullUrl); + configureRequest(request); + String json = request.execute() + .returnContent() + .asString(); + + return getObjectMapper().readValue(json, ApiVersionResponse.class).version; + } catch (Exception ex) { + throw new CkanException("Error while fetching api version!", this, ex); + } + + } + + /** + * Fetches the dataset from ckan. Returned dataset will have resources with + * at least all of the fields of {@link CkanResourceBase} + * + * @param idOrName + * either the dataset name (i.e. certified-products) or the + * alphanumerical id (i.e. 22eea137-9fc3-4222-a716-bac22cc2039a) + * + * @throws CkanException + * on error + * @throws CkanNotFoundException + * if dataset is missing + * + */ + public synchronized CkanDataset getDataset(String idOrName) { + checkNotNull(idOrName, "Need a valid id or name!"); + + CkanDataset cd = getHttp(DatasetResponse.class, "/api/3/action/package_show", "id", idOrName).result; + for (CkanResource cr : cd.getResources()) { + cr.setPackageId(cd.getId()); + } + return cd; + } + + /** + * @throws CkanException + * on error + */ + public synchronized List getUserList() { + return getHttp(UserListResponse.class, "/api/3/action/user_list").result; + } + + /** + * @param id + * i.e. 'admin' + * @throws CkanException + * on error + * @throws CkanNotFoundException + * if user is missing + */ + public synchronized CkanUser getUser(String id) { + checkNotNull(id, "Need a valid id!"); + + return getHttp(UserResponse.class, "/api/3/action/user_show", "id", id).result; + } + + /** + * Creates ckan user on the server. + * + * @param user + * ckan user object with the minimal set of parameters required. + * See + * {@link CkanUserBase#CkanUserBase(java.lang.String, java.lang.String, java.lang.String) + * this constructor} + * @return the newly created user + * @throws JackanException + */ + public synchronized CkanUser createUser(CkanUserBase user) { + checkNotNull(user, "Need a valid user!"); + + checkToken("Tried to create user" + user.getName()); + + ObjectMapper om = CkanClient.getObjectMapperForPosting(CkanUserBase.class); + String json = null; + + try { + json = om.writeValueAsString(user); + } catch (IOException e) { + throw new CkanException(COULDNT_JSONIZE + user.getClass() + .getSimpleName(), + this, e); + + } + + return postHttp(UserResponse.class, "/api/3/action/user_create", json, ContentType.APPLICATION_JSON).result; + } + + /** + * @param id + * The alphanumerical id of the resource, such as + * d0892ada-b8b9-43b6-81b9-47a86be126db. + * + * @throws CkanException + * on error + * @throws CkanNotFoundException + * if resource is missing + */ + public synchronized CkanResource getResource(String id) { + checkNotNull(id, "Need a valid id!"); + return getHttp(ResourceResponse.class, "/api/3/action/resource_show", "id", id).result; + } + + /** + * Creates ckan resource on the server. + * + * @param resource + * ckan resource object with the minimal set of parameters + * required. See + * {@link CkanResource#CkanResource(String, String)} + * @return the newly created resource + * @throws JackanException + * @since 0.4.1 + */ + public synchronized CkanResource createResource(CkanResourceBase resource) { + checkNotNull(resource, "Need a valid resource!"); + + checkToken("Tried to create resource " + resource.getName()); + + ObjectMapper om = CkanClient.getObjectMapperForPosting(CkanResourceBase.class); + String json = null; + try { + json = om.writeValueAsString(resource); + } catch (IOException e) { + throw new CkanException(COULDNT_JSONIZE + resource.getClass() + .getSimpleName(), + this, e); + + } + + if (resource.getUpload() == null) { + return postHttp(ResourceResponse.class, "/api/3/action/resource_create", json, + ContentType.APPLICATION_JSON).result; + } else { + // Could not find a way to create a resource with an attached file + // without enumerating all the resource + // fields so doing it in two steps : + // First, create the resource as usual + // Then, update it with the file + CkanResource resourceResponse = postHttp(ResourceResponse.class, "/api/3/action/resource_create", json, + ContentType.APPLICATION_JSON).result; + resource.setId(resourceResponse.getId()); + return postHttpResourceFile(ResourceResponse.class, "/api/3/action/resource_update", resource).result; + } + } + + /** + * Updates a resource on the server using a straight {@code resource_update} + * call. Null fields will not be sent and thus won't get updated, but be + * careful about custom fields of {@link CkanResourceBase#getOthers()}, if + * not sent they will be erased on the server! To prevent this behaviour, + * see {@link #patchUpdateResource(CkanResourceBase) } + * + * @throws CkanException + * on error + * @since 0.4.1 + */ + public synchronized CkanResource updateResource(CkanResourceBase resource) { + checkNotNull(resource, "Need a valid resource!"); + checkToken("Tried to update resource" + resource.getName()); + + String json = null; + try { + json = getObjectMapperForPosting(CkanResourceBase.class).writeValueAsString(resource); + } catch (IOException ex) { + throw new CkanException(COULDNT_JSONIZE + resource.getClass() + .getSimpleName(), + this, ex); + + } + + return postHttp(ResourceResponse.class, "/api/3/action/resource_update", json, + ContentType.APPLICATION_JSON).result; + + } + + /** + * Jackan specific. Patches a resource on the ckan server using a + * {@code resource_update} call. Todo: this is a temporary solution until we + * implement new {@code patch} api of CKAN 2.3 + * + * @param resource + * ckan resource object. Fields set to {@code null} won't be + * updated on the server. Items present in lists such as + * {@link CkanResourceBase#getOthers() others} will be added to + * existing ones on the server. To support this behaviour + * provided {@code resource} might be patched with latest + * metadata from the server prior sending it for update. + * + * @see #updateResource(CkanResourceBase) + * @throws CkanException + * on error + * @since 0.4.1 + */ + public synchronized CkanResource patchUpdateResource(CkanResourceBase resource) { + checkNotNull(resource, "Need a valid resource!"); + checkToken("Tried to update resource" + resource.getName()); + + CkanResource origResource = getResource(resource.getId()); + // others + Map newOthers = new HashMap(); + + if (origResource.getOthers() != null) { + newOthers.putAll(origResource.getOthers()); + } + if (resource.getOthers() != null) { + newOthers.putAll(resource.getOthers()); + } + resource.setOthers(newOthers); + + String json = null; + try { + json = getObjectMapperForPosting(CkanResourceBase.class).writeValueAsString(resource); + } catch (IOException ex) { + throw new CkanException(COULDNT_JSONIZE + resource.getClass() + .getSimpleName(), + this, ex); + + } + + return postHttp(ResourceResponse.class, "/api/3/action/resource_update", json, + ContentType.APPLICATION_JSON).result; + + } + + /** + * Update the data file associated to this resource. + * + * @param resource + * a {@link CkanResourceBase} object with a data file set in the + * {@link CkanResourceBase#upload} field + * @throws CkanException + * + * @since 0.4.3 + * + */ + public synchronized CkanResource updateResourceData(CkanResourceBase resource) { + checkNotNull(resource); + checkNotNull(resource.getUpload(), "Need a non null file in resource.getUpload() !"); + checkToken("Tried to update resource" + resource.getName()); + + return postHttpResourceFile(ResourceResponse.class, "/api/3/action/resource_update", resource).result; + } + + /** + * + * Marks a resource as {@code 'deleted'}. + * + * Note this will just set resource state to + * {@link eu.trentorise.opendata.jackan.model.CkanState#deleted} and make it + * inaccessible from the website and api. + * + * @param id + * The alphanumerical id of the resource, such as + * d0892ada-b8b9-43b6-81b9-47a86be126db. + * + * @throws CkanException + * on error + * @throws CkanNotFoundException + * if trying to delete non-existing resource + * @since 0.4.1 + */ + public synchronized void deleteResource(String id) { + checkNotNull(id, "Need a valid id!"); + checkToken("Tried to delete resource with id " + id); + + String json = "{\"id\":\"" + id + "\"}"; + postHttp(ResourceResponse.class, "/api/3/action/resource_delete", json, ContentType.APPLICATION_JSON); + } + + /** + * Returns the groups present in Ckan. + * + * Notice that organizations will not be returned by this method. To + * get them, use {@link #getOrganizationList() } instead. + * + * @throws CkanException + * on error + */ + public synchronized List getGroupList() { + return getHttp(GroupListResponse.class, "/api/3/action/group_list", "all_fields", "True").result; + } + + /** + * Return group names, like i.e. management-of-territory + * + * @throws CkanException + * on error + */ + public synchronized List getGroupNames() { + return getHttp(GroupNamesResponse.class, "/api/3/action/group_list").result; + } + + /** + * Returns a Ckan group. Do not pass an organization id, to get organization + * use {@link #getOrganization(java.lang.String) } instead. + * + * @param idOrName + * either the group name (i.e. hospitals-in-trento-district) or + * the group alphanumerical id (i.e. + * 55bb5fbd-7a7c-4eb8-8b1a-1192a5504421) + * @throws CkanException + * on error + * @throws CkanNotFoundException + * if group is missing + */ + public synchronized CkanGroup getGroup(String idOrName) { + checkNotNull(idOrName, "Need a valid id or name!"); + return getHttp(GroupResponse.class, "/api/3/action/group_show", "id", idOrName, "include_datasets", + "false").result; + } + + /** + * Returns the organizations present in CKAN. + * + * @see #getGroupList() + * + * @throws CkanException + * on error + */ + public synchronized List getOrganizationList() { + return getHttp(OrganizationListResponse.class, "/api/3/action/organization_list", "all_fields", "True").result; + } + + /** + * Returns all the resource formats available in the catalog. + * + * @throws CkanException + * on error + */ + public synchronized Set getFormats() { + return getHttp(FormatListResponse.class, "/api/3/action/format_autocomplete", "q", "", "limit", "1000").result; + } + + /** + * @throws CkanException + * on error + */ + public synchronized List getOrganizationNames() { + return getHttp(GroupNamesResponse.class, "/api/3/action/organization_list").result; + } + + /** + * Returns a Ckan organization. + * + * @param idOrName + * either the name of organization (i.e. culture-and-education) + * or the alphanumerical id (i.e. + * 232cad97-ecf2-447d-9656-63899023887f). Do not pass it a group + * id. + * @throws CkanException + * on error + * @throws CkanNotFoundException + * if organization is missing + */ + public synchronized CkanOrganization getOrganization(String idOrName) { + checkNotNull(idOrName, "Need a valid id or name!"); + return getHttp(OrganizationResponse.class, "/api/3/action/organization_show", "id", idOrName, + "include_datasets", "false").result; + } + + /** + * Creates CkanTag on the server. + * + * @param tag + * Ckan tag without id + * @return the newly created tag + * @throws JackanException + */ + public synchronized CkanTag createTag(CkanTagBase tag) { + checkNotNull(tag, "Need a valid tag!"); + + checkToken("Tried to create tag" + tag.getName()); + + String json = null; + try { + json = getObjectMapperForPosting(CkanTagBase.class).writeValueAsString(tag); + } catch (IOException e) { + throw new CkanException(COULDNT_JSONIZE + tag.getClass() + .getSimpleName(), + this, e); + + } + TagResponse response = postHttp(TagResponse.class, "/api/3/action/tag_create", json, + ContentType.APPLICATION_JSON); + return response.result; + } + + /** + * Returns a list of tags names, i.e. "gp-practice-earnings","Aid Project + * Evaluation", "tourism-satellite-account". We think names SHOULD be + * lowercase with minuses instead of spaces, but in some cases they aren't. + * + * @throws CkanException + * on error + */ + public synchronized List getTagList() { + return getHttp(TagListResponse.class, "/api/3/action/tag_list", "all_fields", "True").result; + } + + /** + * Returns tags containing the string given in query. + * + * @param query + * @throws CkanException + * on error + */ + public synchronized List getTagNamesList(String query) { + checkNotNull(query, "Need a valid query!"); + return getHttp(TagNamesResponse.class, "/api/3/action/tag_list", "query", query).result; + } + + /** + * @throws CkanException + * on error + */ + public synchronized List getTagNamesList() { + return getHttp(TagNamesResponse.class, "/api/3/action/tag_list").result; + } + + /** + * Creates CkanVocabulary on the server. + * + * @param vocabulary + * Ckan vocabulary without id + * @return the newly created vocabulary + * @throws JackanException + */ + public synchronized CkanVocabulary createVocabulary(CkanVocabularyBase vocabulary) { + checkNotNull(vocabulary, "Need a valid vocabulary!"); + + checkToken("Tried to create vocabulary" + vocabulary.getName()); + + String json = null; + try { + json = getObjectMapperForPosting(CkanVocabularyBase.class).writeValueAsString(vocabulary); + } catch (IOException e) { + throw new CkanException(COULDNT_JSONIZE + vocabulary.getClass() + .getSimpleName(), + this, e); + + } + VocabularyResponse response = postHttp(VocabularyResponse.class, "/api/3/action/vocabulary_create", json, + ContentType.APPLICATION_JSON); + return response.result; + } + + /** + * Search datasets containing provided text in the metadata + * + * @param text + * The query string + * @param limit + * maximum results to return + * @param offset + * search begins from offset. Starts from 0, so that offset 0 + * limit 1 returns exactly 1 result, if there is a matching + * dataset) + * @throws JackanException + * on error + */ + public synchronized SearchResults searchDatasets(String text, int limit, int offset) { + return searchDatasets(CkanQuery.filter() + .byText(text), + limit, offset); + } + + /** + * @param fqPrefix + * either "" or " AND " + * @param list + * list of names of ckan objects + */ + private static String appendNamesList(String fqPrefix, String key, List list, StringBuilder fq) { + checkNotNull(fqPrefix, "Need a valid prefix!"); + checkNotNull(key, "Need a valid key!"); + checkNotNull(list, "Need a valid list!"); + checkNotNull(fq, "Need a valid string builder!"); + + if (list.size() > 0) { + fq.append(fqPrefix) + .append("("); + String prefix = ""; + for (String n : list) { + fq.append(prefix) + .append(key) + .append(":"); + fq.append('"' + n + '"'); + prefix = " AND "; + } + fq.append(")"); + return " AND "; + } else { + return ""; + } + + } + + /** + * Parses a {@link #CKAN_TIMESTAMP_PATTERN Ckan timestamp} into a Java Timestamp. + * For resilience, it also accepts patterns without fractional part and with + * only millisecs. + * + * @throws IllegalArgumentException + * if timestamp can't be parsed. + * @see #formatTimestamp(java.sql.Timestamp) for the inverse process. + * @since 0.4.1 + */ + // NOTE: + // - Timestamp.valueOf can't be used as it is locale timezone dependent... + // - new Timestamp(long) internally stores time without millisecs, and millisecs go to nano part + public static Timestamp parseTimestamp(String timestamp) { + if (timestamp == null) { + throw new IllegalArgumentException("Found null timestamp!"); + } + + if (NONE.equals(timestamp)) { + throw new IllegalArgumentException("Found timestamp with 'None' inside!"); + } + + String[] tokens = timestamp.split("\\."); + String withoutFractional; + + int nanoSecs; + + if (tokens.length == 2) { + withoutFractional = tokens[0]; + + int factor; + if (tokens[1].length() == 6){ + factor = 1000; + } else if (tokens[1].length() == 3){ + factor = 1000000; + } else { + throw new IllegalArgumentException("Couldn0t parse timestamp:" + timestamp + + " ! unsupported fractional length: " + tokens[1].length()); + } + try { + nanoSecs = Integer.parseInt(tokens[1]) * factor; + + } catch (NumberFormatException ex){ + throw new IllegalArgumentException("Couldn0t parse timestamp:" + timestamp + + " ! invalid fractional part:" + tokens[1]); + } + + } else if (tokens.length == 1){ + withoutFractional = timestamp; + nanoSecs = 0; + } else { + throw new IllegalArgumentException("Error while parsing timestamp:" + timestamp); + } + + try { + DateFormat formatter = new SimpleDateFormat(CKAN_NO_MILLISECS_PATTERN); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + long time = formatter.parse(withoutFractional).getTime(); + Timestamp ret = new Timestamp(time); + ret.setNanos(nanoSecs); + return ret; + + } catch (ParseException ex) { + throw new IllegalArgumentException("Error while parsing timestamp:" + timestamp, ex); + } + + } + + /** + * Formats a timestamp according to {@link #CKAN_TIMESTAMP_PATTERN}, with + * precision up to microseconds. + * + * @throws IllegalArgumentException + * if timestamp can't be parsed. + * @see #parseTimestamp(java.lang.String) for the inverse process. + * @since 0.4.1 + */ + // NOTE: Timestamp.toString can't be used as it is locale timezone dependent... + public static String formatTimestamp(Timestamp timestamp) { + + if (timestamp == null) { + throw new IllegalArgumentException("Found null timestamp!"); + } + DateFormat formatter = new SimpleDateFormat(CKAN_NO_MILLISECS_PATTERN); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + String withoutFractional = formatter.format(timestamp); + + int micros = timestamp.getNanos() / 1000; + String microsString = Strings.padStart(Integer.toString(micros), 6, '0'); + + return withoutFractional + "." + microsString; + + } + + /** + * @params s a string to encode in a format suitable for URLs. + */ + private static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8") + .replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException ex) { + throw new JackanException("Unsupported encoding", ex); + } + } + + /** + * Search datasets according to the provided query. + * + * @param query + * The query object + * @param limit + * maximum results to return + * @param offset + * search begins from offset + * @throws CkanException + * on error + */ + public synchronized SearchResults searchDatasets(CkanQuery query, int limit, int offset) { + checkNotNull(query, "Need a valid query!"); + + StringBuilder params = new StringBuilder(); + + params.append("rows=") + .append(limit) + .append("&start=") + .append(offset); + + if (query.getText() + .length() > 0) { + params.append("&q="); + params.append(urlEncode(query.getText())); + } + + StringBuilder fq = new StringBuilder(); + String fqPrefix = ""; + + fqPrefix = appendNamesList(fqPrefix, "groups", query.getGroupNames(), fq); + fqPrefix = appendNamesList(fqPrefix, "organization", query.getOrganizationNames(), fq); + fqPrefix = appendNamesList(fqPrefix, "tags", query.getTagNames(), fq); + fqPrefix = appendNamesList(fqPrefix, "license_id", query.getLicenseIds(), fq); + + if (fq.length() > 0) { + params.append("&fq=") + .append(urlEncode(fq.insert(0, "(") + .append(")") + .toString())); + } + + DatasetSearchResponse dsr; + dsr = getHttp(DatasetSearchResponse.class, "/api/3/action/package_search?" + params.toString()); + + for (CkanDataset ds : dsr.result.getResults()) { + for (CkanResource cr : ds.getResources()) { + cr.setPackageId(ds.getId()); + } + } + + return dsr.result; + } + + /** + * @msg the prepended error message. + * @throws CkanException + */ + private void checkToken(@Nullable String prependedErrorMessage) { + if (ckanToken == null) { + throw new CkanException(String.valueOf(prependedErrorMessage) + ", but ckan token was not set!", this); + } + } + + /** + * Creates CkanDataset on the server. Will also create eventual resources + * present in the dataset. + * + * @param dataset + * Ckan dataset without id + * @return the newly created dataset + * @throws CkanException + * @since 0.4.1 + */ + public synchronized CkanDataset createDataset(CkanDatasetBase dataset) { + checkNotNull(dataset, "Need a valid dataset!"); + + checkToken("Tried to create dataset" + dataset.getName()); + + String json = null; + try { + + json = getObjectMapperForPosting(CkanDatasetBase.class).writeValueAsString(dataset); + } catch (IOException e) { + throw new CkanException(COULDNT_JSONIZE + dataset.getClass() + .getSimpleName(), + this, e); + } + + DatasetResponse response = postHttp(DatasetResponse.class, "/api/3/action/package_create", json, + ContentType.APPLICATION_JSON); + + return response.result; + } + + /** + * Updates a dataset on the ckan server using a straight + * {@code package_update} call. Null fields will not be sent and thus won't + * get updated, but be careful about list fields, if not sent they will be + * erased on the server! To prevent this behaviour, see + * {@link #patchUpdateDataset(CkanDatasetBase)} + * + * @throws CkanException + * on error + * @since 0.4.1 + */ + public synchronized CkanDataset updateDataset(CkanDatasetBase dataset) { + checkNotNull(dataset, "Need a valid dataset!"); + + checkToken("Tried to update dataset" + dataset.getName()); + + String json = null; + try { + json = getObjectMapperForPosting(CkanDatasetBase.class).writeValueAsString(dataset); + } catch (IOException ex) { + throw new CkanException(COULDNT_JSONIZE + dataset.getClass() + .getSimpleName(), + this, ex); + + } + + return postHttp(DatasetResponse.class, "/api/3/action/package_update", json, + ContentType.APPLICATION_JSON).result; + + } + + public static List extrasMapToList(Map map) { + ArrayList ret = new ArrayList(); + + for (String key : map.keySet()) { + ret.add(new CkanPair(key, map.get(key))); + } + return ret; + } + + private void mergeResources(@Nullable List resourcesToMerge, List targetResources) { + if (resourcesToMerge != null) { + for (CkanResource resourceToMerge : resourcesToMerge) { + boolean replaced = false; + for (int i = 0; i < targetResources.size(); i++) { + CkanResource targetRes = targetResources.get(i); + if (resourceToMerge.getId() != null && resourceToMerge.getId() + .equals(targetRes.getId())) { + targetResources.set(i, resourceToMerge); + replaced = true; + break; + } + } + if (!replaced) { + targetResources.add(resourceToMerge); + } + } + } + } + + private void mergeGroups(@Nullable List groupsToMerge, List targetGroups) { + if (groupsToMerge != null) { + for (CkanGroup groupToMerge : groupsToMerge) { + boolean replaced = false; + for (int i = 0; i < targetGroups.size(); i++) { + CkanGroup targetRes = targetGroups.get(i); + if (groupToMerge.getId() != null && groupToMerge.getId() + .equals(targetRes.getId())) { + targetGroups.set(i, groupToMerge); + replaced = true; + break; + } + } + if (!replaced) { + targetGroups.add(groupToMerge); + } + } + } + } + + private void mergeRelationships(@Nullable List relationshipsToMerge, + List targetDatasetRelationships) { + if (relationshipsToMerge != null) { + for (CkanDatasetRelationship relationshipToMerge : relationshipsToMerge) { + boolean replaced = false; + for (int i = 0; i < targetDatasetRelationships.size(); i++) { + CkanDatasetRelationship targetRes = targetDatasetRelationships.get(i); + if (relationshipToMerge.getId() != null && relationshipToMerge.getId() + .equals(targetRes.getId())) { + targetDatasetRelationships.set(i, relationshipToMerge); + replaced = true; + break; + + } + } + if (!replaced) { + targetDatasetRelationships.add(relationshipToMerge); + } + } + } + } + + private void mergeTags(@Nullable List tagsToMerge, List targetTags) { + if (tagsToMerge != null) { + for (CkanTag tagToMerge : tagsToMerge) { + boolean replaced = false; + for (int i = 0; i < targetTags.size(); i++) { + CkanTag targetRes = targetTags.get(i); + if (tagToMerge.getId() != null && tagToMerge.getId() + .equals(targetRes.getId())) { + targetTags.set(i, tagToMerge); + replaced = true; + break; + } + } + if (!replaced) { + targetTags.add(tagToMerge); + } + } + } + } + + /** + * Jackan specific. Patches a dataset on the ckan server using a + * {@code package_update} call. Todo: this is a temporary solution until we + * implement new {@code patch} api of CKAN 2.3 + * + * @param dataset + * ckan dataset object. Fields set to {@code null} won't be + * updated on the server. Items present in lists such as + * {@code resources} or {@code extras} will be added to existing + * ones on the server. To support this behaviour provided + * {@code dataset} might be patched with latest metadata from the + * server prior sending it for update. + * + * @throws CkanException + * on error + * @since 0.4.1 + */ + public synchronized CkanDataset patchUpdateDataset(CkanDatasetBase dataset) { + checkNotNull(dataset, "Need a valid dataset!"); + + checkToken("Tried to patch update dataset" + dataset.getName()); + + CkanDataset origDataset = getDataset(dataset.idOrName()); + + // others + Map newOthers = new HashMap(); + + if (origDataset.getOthers() != null) { + newOthers.putAll(origDataset.getOthers()); + } + if (dataset.getOthers() != null) { + newOthers.putAll(dataset.getOthers()); + } + dataset.setOthers(newOthers); + + // extras + if (dataset.getExtras() == null) { + dataset.setExtras(origDataset.getExtras()); + } else { + Map newExtras = new HashMap(); + + if (origDataset.getExtras() != null) { + newExtras.putAll(origDataset.getExtrasAsHashMap()); + } + if (dataset.getExtras() != null) { + newExtras.putAll(dataset.getExtrasAsHashMap()); + } + dataset.setExtras(extrasMapToList(newExtras)); + } + + // resources + List newResources = new ArrayList(); + mergeResources(origDataset.getResources(), newResources); + mergeResources(dataset.getResources(), newResources); + dataset.setResources(newResources); + + // groups + List newGroups = new ArrayList(); + mergeGroups(origDataset.getGroups(), newGroups); + mergeGroups(dataset.getGroups(), newGroups); + dataset.setGroups(newGroups); + + // tags + List newTags = new ArrayList(); + mergeTags(origDataset.getTags(), newTags); + mergeTags(dataset.getTags(), newTags); + dataset.setTags(newTags); + + // relationships as subject + List newRelationshipsAsSubject = new ArrayList(); + mergeRelationships(origDataset.getRelationshipsAsSubject(), newRelationshipsAsSubject); + mergeRelationships(dataset.getRelationshipsAsSubject(), newRelationshipsAsSubject); + dataset.setRelationshipsAsSubject(newRelationshipsAsSubject); + + // relationships as object + List newRelationshipsAsObject = new ArrayList(); + mergeRelationships(origDataset.getRelationshipsAsObject(), newRelationshipsAsObject); + mergeRelationships(dataset.getRelationshipsAsObject(), newRelationshipsAsObject); + dataset.setRelationshipsAsObject(newRelationshipsAsObject); + + String json = null; + try { + json = getObjectMapperForPosting(CkanDatasetBase.class).writeValueAsString(dataset); + } catch (IOException ex) { + throw new JackanException(COULDNT_JSONIZE + dataset.getClass() + .getSimpleName(), + ex); + + } + + return postHttp(DatasetResponse.class, "/api/3/action/package_update", json, + ContentType.APPLICATION_JSON).result; + + } + + /** + * Marks a dataset as {@code 'deleted'}. + * + * Note this will just set dataset state to + * {@link eu.trentorise.opendata.jackan.model.CkanState#deleted} and make it + * inaccessible from the website and api. + * + * @param nameOrId + * either the dataset name (i.e. apple-production) or the the + * alphanumerical id (i.e. fe507a10-4c49-4b18-8bf6-6705198cfd42) + * + * @throws CkanException + * on error + * @throws CkanNotFoundException + * if dataset is not found + * + */ + // todo check if permissions change accessibility from api + public synchronized void deleteDataset(String nameOrId) { + checkNotNull(nameOrId, "Need a valid name or id!"); + + checkToken("Tried to delete dataset" + nameOrId); + + String json = "{\"id\":\"" + nameOrId + "\"}"; + postHttp(CkanResponse.class, "/api/3/action/package_delete", json, ContentType.APPLICATION_JSON); + } + + /** + * Creates CkanOrganization on the server. + * + * @param organization + * requires at least the name or id. Only non-null fields of + * {@link CkanGroupOrgBase} will be sent to server. + * @return a new object with the created organization. + * @throws CkanException + * on error. + * @since 0.4.1 + */ + public synchronized CkanOrganization createOrganization(CkanOrganization organization) { + checkNotNull(organization, "Need a valid " + organization + "!"); + + checkToken("Tried to create organization " + organization.getName()); + + String json = null; + try { + json = getObjectMapperForPosting(CkanOrganization.class).writeValueAsString(organization); + } catch (IOException e) { + throw new CkanException(COULDNT_JSONIZE + organization.getClass() + .getSimpleName(), + this, e); + + } + return postHttp(OrganizationResponse.class, "/api/3/action/organization_create", json, + ContentType.APPLICATION_JSON).result; + } + + /** + * Creates CkanGroup on the server. + * + * @param group + * requires at least the name or id. Only non-null fields of + * {@link CkanGroupOrgBase} will be sent to server. + * @return a new object with the created group. + * @throws CkanException + * on error. + * @since 0.4.1 + */ + public synchronized CkanGroup createGroup(CkanGroup group) { + checkNotNull(group, "Need a valid " + group + "!"); + + checkToken("Tried to create group " + group.idOrName()); + + String json = null; + try { + json = getObjectMapperForPosting(CkanGroup.class).writeValueAsString(group); + } catch (IOException e) { + throw new CkanException(COULDNT_JSONIZE + group.getClass() + .getSimpleName(), + this, e); + + } + return postHttp(GroupResponse.class, "/api/3/action/group_create", json, ContentType.APPLICATION_JSON).result; + } + + /** + * Returns the proxy used by the client, usually in a format with + * address and port like my.own-proxy.org:1234 + * + * @since 0.4.1 + */ + @Nullable + public String getProxy() { + return proxy; + } + + /** + * Convenience method to create a Builder with provided client to modify. + *

+ * WARNING: The passed client will be modified, so + * DO NOT pass an already built client. + *

+ *

+ * The builder is not threadsafe and you can use one builder instance to + * build only one client instance. + *

+ */ + protected static CkanClient.Builder newBuilder(CkanClient client) { + return new Builder(client); + } + +} + +class DatasetResponse extends CkanResponse { + + public CkanDataset result; +} + +class ResourceResponse extends CkanResponse { + + public CkanResource result; +} + +class DatasetListResponse extends CkanResponse { + + public List result; +} + +class UserListResponse extends CkanResponse { + + public List result; +} + +class UserResponse extends CkanResponse { + + public CkanUser result; +} + +class TagListResponse extends CkanResponse { + + public List result; +} + +class OrganizationResponse extends CkanResponse { + + public CkanOrganization result; +} + +class GroupResponse extends CkanResponse { + + public CkanGroup result; +} + +class OrganizationListResponse extends CkanResponse { + + public List result; +} + +class GroupListResponse extends CkanResponse { + + public List result; +} + +class GroupNamesResponse extends CkanResponse { + + public List result; +} + +class TagNamesResponse extends CkanResponse { + + public List result; +} + +class TagResponse extends CkanResponse { + + public CkanTag result; +} + +class VocabularyResponse extends CkanResponse { + + public CkanVocabulary result; +} + +class DatasetSearchResponse extends CkanResponse { + + public SearchResults result; +} + +class LicenseListResponse extends CkanResponse { + + public List result; +} + +class FormatListResponse extends CkanResponse { + + public Set result; +} + +class ApiVersionResponse { + + public int version; +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CkanQuery.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CkanQuery.java new file mode 100644 index 0000000..fcdcbba --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/CkanQuery.java @@ -0,0 +1,143 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.jackan; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.google.common.collect.Lists; + +/** + * Usage example: {@code + * CkanQuery.filter().byText("litigations").byGroupNames("justice") + * } + * + * @author David Leoni + */ +public final class CkanQuery { + + private String text; + private List groupNames; + private List organizationNames; + private List tagNames; + private List licenseIds; + + private CkanQuery() { + this.text = ""; + this.groupNames = new ArrayList(); + this.organizationNames = new ArrayList(); + this.tagNames = new ArrayList(); + this.licenseIds = new ArrayList(); + } + + /** + * Each filtered dataset must belong to all the given groups i.e. + * "british-academy", "home-office", "newcastle-city-council" + */ + public CkanQuery byGroupNames(Iterable groupNames) { + this.groupNames = Lists.newArrayList(groupNames); + return this; + } + + /** + * Each filtered dataset must belong to all the given groups i.e. + * "british-academy", "home-office", "newcastle-city-council" + */ + public CkanQuery byGroupNames(String... groupNames) { + this.groupNames = Arrays.asList(groupNames); + return this; + } + + /** + * @param text i.e. "health care London" + */ + public CkanQuery byText(String text) { + this.text = text; + return this; + } + + /** + * Each filtered dataset must belong to the given organization + * + * @param organizationName i.e. "audit-commission", + * "remploy-limited","royal-society" + */ + public CkanQuery byOrganizationName(String organizationName) { + this.organizationNames = Lists.newArrayList(organizationName); + return this; + } + + /** + * Each filtered dataset must have all the given tags + * + * @param tagNames i.e. "Community health partnership", "youth-justice", + * "trade-policy", + */ + public CkanQuery byTagNames(Iterable tagNames) { + this.tagNames = Lists.newArrayList(tagNames); + return this; + } + + /** + * Each filtered dataset must have all the given tags + * + * @param tagNames i.e. "Community health partnership", "youth-justice", + * "trade-policy", + */ + public CkanQuery byTagNames(String... tagNames) { + this.tagNames = Arrays.asList(tagNames); + return this; + } + + /** + * Each filtered dataset must have the given license + * + * @param licenseId i.e. "cc-by", "odc-by" + */ + public CkanQuery byLicenseId(String licenseId) { + this.licenseIds = Lists.newArrayList(licenseId); + return this; + } + + /** + * Factory method to start creating the query. + */ + public static CkanQuery filter() { + return new CkanQuery(); + } + + public String getText() { + return text; + } + + public List getGroupNames() { + return groupNames; + } + + public List getOrganizationNames() { + return organizationNames; + } + + public List getTagNames() { + return tagNames; + } + + public List getLicenseIds() { + return licenseIds; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/JackanModule.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/JackanModule.java new file mode 100644 index 0000000..bf1839d --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/JackanModule.java @@ -0,0 +1,135 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.jackan; + + +import static org.gcube.datacatalogue.ckanutillibrary.jackan.CkanClient.formatTimestamp; + +import java.io.IOException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +import org.gcube.com.fasterxml.jackson.core.JsonGenerator; +import org.gcube.com.fasterxml.jackson.core.JsonParser; +import org.gcube.com.fasterxml.jackson.core.JsonToken; +import org.gcube.com.fasterxml.jackson.core.type.TypeReference; +import org.gcube.com.fasterxml.jackson.databind.DeserializationContext; +import org.gcube.com.fasterxml.jackson.databind.JsonDeserializer; +import org.gcube.com.fasterxml.jackson.databind.PropertyNamingStrategy; +import org.gcube.com.fasterxml.jackson.databind.SerializerProvider; +import org.gcube.com.fasterxml.jackson.databind.module.SimpleModule; +import org.gcube.com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDataset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Custom Jackson module to serialize/deserialize as JSON Ckan objects with + * fields lower cased (i.e. 'author_email') and also Timestamp, which in Ckan + * has format like "2013-11-11T04:12:11.110868", see + * {@link CkanClient#CKAN_TIMESTAMP_PATTERN}. In case there are problems in + * parsing deserializes them to null. + * + * NOTE: We made a custom module because when reading dates, Jackson defaults to + * using GMT for all processing unless specifically told otherwise, see + * < href="http://wiki.fasterxml.com/JacksonFAQTimestampHandling" target="_blank">Jackson + * FAQ. When writing dates, Jackson would also add a Z for timezone and add + * +1 for GMT, which we don't want. + * + * @author David Leoni + * @since 0.4.1 + */ +public class JackanModule extends SimpleModule { + + private static final Logger LOG = LoggerFactory.getLogger(JackanModule.class.getName()); + + public JackanModule() { + + setNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); + + addSerializer(Timestamp.class, new StdSerializer(Timestamp.class) { + @Override + public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + try { + String str = formatTimestamp(value); + jgen.writeString(str); + } + catch (Exception ex) { + LOG.warn("Couldn't format timestamp " + value + ", writing 'null'", ex); + jgen.writeNull(); + } + } + + }); + + addDeserializer(Timestamp.class, new JsonDeserializer() { + + @Override + public Timestamp deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonToken t = jp.getCurrentToken(); + + if (t == JsonToken.VALUE_STRING) { + String str = jp.getText().trim(); + try { + return CkanClient.parseTimestamp(str); + } + catch (IllegalArgumentException ex) { + LOG.warn("Couldn't parse timestamp " + str + ", returning null", ex); + return null; + } + } + + if (t == JsonToken.VALUE_NULL) { + return null; + } + + LOG.warn("Unrecognized json token for timestamp {0}, returning null", t.asString()); + return null; + + } + + }); + } + + /** + * group org packages sometimes are arrays, sometimes numbers. If a number + * is found null is returned. + */ + public static class GroupOrgPackagesDeserializer extends JsonDeserializer> { + + private static final Logger LOG = LoggerFactory.getLogger(GroupOrgPackagesDeserializer.class.getName()); + + @Override + public List deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + + JsonToken t = jp.getCurrentToken(); + + if (t == JsonToken.VALUE_NUMBER_INT) { + return null; + } + + if (t == JsonToken.START_ARRAY) { + return jp.readValueAs(new TypeReference>() { + }); + } + + LOG.warn("Unrecognized token {0} for 'packages' field, returning an empty array.", t.asString()); + return new ArrayList(); + } + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/SearchResults.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/SearchResults.java new file mode 100644 index 0000000..9b3777f --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/jackan/SearchResults.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.jackan; + +import java.util.List; + +import javax.annotation.concurrent.Immutable; + +/** + * + * @author David Leoni + * @param the type of the results + */ +@Immutable +public class SearchResults { + + private int count; + private List results; + + /** + * @param count The number of matches on the server, which may be greater + * than the search results. + */ + public SearchResults(List results, int count) { + this.count = count; + this.results = results; + } + + private SearchResults() { + } + + /** + * Returns the number of matches on the server, which may be greater than + * the search results. + */ + public int getCount() { + return count; + } + + public List getResults() { + return results; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/DataCatalogue.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/DataCatalogue.java index f8bc8cb..52925ff 100644 --- a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/DataCatalogue.java +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/DataCatalogue.java @@ -6,11 +6,12 @@ import java.util.Map; import org.gcube.datacatalogue.ckanutillibrary.shared.LandingPages; import org.gcube.datacatalogue.ckanutillibrary.shared.ResourceBean; import org.gcube.datacatalogue.ckanutillibrary.shared.RolesCkanGroupOrOrg; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDataset; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroup; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanLicense; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanOrganization; + -import eu.trentorise.opendata.jackan.model.CkanDataset; -import eu.trentorise.opendata.jackan.model.CkanGroup; -import eu.trentorise.opendata.jackan.model.CkanLicense; -import eu.trentorise.opendata.jackan.model.CkanOrganization; /** * The Interface DataCatalogue. @@ -110,7 +111,7 @@ public interface DataCatalogue { * Retrieve a ckan dataset given its id. * * @param datasetId the dataset id - * @param apiKey the api key + * @param apiKey the api key. If null uses gCat to get the Dataset * @return the dataset */ CkanDataset getDataset(String datasetId, String apiKey); diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/DataCatalogueImpl.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/DataCatalogueImpl.java index 40dc0ce..39e830d 100644 --- a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/DataCatalogueImpl.java +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/DataCatalogueImpl.java @@ -10,7 +10,16 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.scope.api.ScopeProvider; import org.gcube.common.scope.impl.ScopeBean; import org.gcube.common.scope.impl.ScopeBean.Type; import org.gcube.datacatalogue.ckanutillibrary.ckan.ExtendCkanClient; @@ -21,30 +30,24 @@ import org.gcube.datacatalogue.ckanutillibrary.gcat.GCatCaller; import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueRunningCluster.ACCESS_LEVEL_TO_CATALOGUE_PORTLET; import org.gcube.datacatalogue.ckanutillibrary.server.utils.CKANConveter; import org.gcube.datacatalogue.ckanutillibrary.server.utils.CatalogueUtilMethods; +import org.gcube.datacatalogue.ckanutillibrary.server.utils.GCubeUtils; +import org.gcube.datacatalogue.ckanutillibrary.server.utils.GCubeUtils.GCUBE_SCOPE_LEVEL; import org.gcube.datacatalogue.ckanutillibrary.server.utils.url.EntityContext; import org.gcube.datacatalogue.ckanutillibrary.shared.LandingPages; import org.gcube.datacatalogue.ckanutillibrary.shared.ResourceBean; import org.gcube.datacatalogue.ckanutillibrary.shared.RolesCkanGroupOrOrg; import org.gcube.datacatalogue.ckanutillibrary.shared.State; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDataset; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroup; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanLicense; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanOrganization; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResource; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanUser; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.JackanException; import org.json.simple.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.trentorise.opendata.jackan.exceptions.JackanException; -import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpResponse; -import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpStatus; -import eu.trentorise.opendata.jackan.internal.org.apache.http.client.methods.HttpPost; -import eu.trentorise.opendata.jackan.internal.org.apache.http.entity.ContentType; -import eu.trentorise.opendata.jackan.internal.org.apache.http.entity.StringEntity; -import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.CloseableHttpClient; -import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.HttpClientBuilder; -import eu.trentorise.opendata.jackan.internal.org.apache.http.util.EntityUtils; -import eu.trentorise.opendata.jackan.model.CkanDataset; -import eu.trentorise.opendata.jackan.model.CkanGroup; -import eu.trentorise.opendata.jackan.model.CkanLicense; -import eu.trentorise.opendata.jackan.model.CkanOrganization; -import eu.trentorise.opendata.jackan.model.CkanResource; -import eu.trentorise.opendata.jackan.model.CkanUser; /** * This is the Ckan Utils implementation class. @@ -213,7 +216,7 @@ public class DataCatalogueImpl implements DataCatalogue { LOG.debug("Request for CKAN licenses"); // get the url and the api key of the user List result = new ArrayList(); - //retrieve the list of available licenses + // retrieve the list of available licenses List licenses = ckanCaller.getLicenseList(); for (CkanLicense ckanLicense : licenses) { @@ -229,10 +232,17 @@ public class DataCatalogueImpl implements DataCatalogue { @Override public List getLicenses() { LOG.debug("Request for CKAN licenses (original jackan objects are going to be retrieved)"); - //retrieve the list of available licenses + // retrieve the list of available licenses return ckanCaller.getLicenseList(); } - + + /* + * (non-Javadoc) + * + * @see + * org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue#getDataset(java. + * lang.String, java.lang.String) + */ @Override public CkanDataset getDataset(String datasetId, String apiKey) { LOG.info("Request ckan dataset with id " + datasetId); @@ -241,25 +251,25 @@ public class DataCatalogueImpl implements DataCatalogue { checkNotNull(datasetId); checkArgument(!datasetId.isEmpty()); - try{ - - if(apiKey!=null && !apiKey.isEmpty()) { - LOG.info("API-KEY found. Calling the "+ExtendCkanClient.class.getSimpleName()); + try { + + if (apiKey != null && !apiKey.isEmpty()) { + LOG.info("API-KEY found. Calling the " + ExtendCkanClient.class.getSimpleName()); ExtendCkanClient client = new ExtendCkanClient(CKAN_CATALOGUE_URL, apiKey); return client.getDataset(datasetId); } - + String authzToken = SecurityTokenProvider.instance.get(); - if(authzToken!=null && !authzToken.isEmpty()) { + if (authzToken != null && !authzToken.isEmpty()) { LOG.info("gcube-token found. Calling the gCat client"); String jsonDataset = gCatCaller.getDatasetForName(datasetId); return MarshUnmarshCkanObject.toCkanDataset(jsonDataset, METHOD.TO_READ); } - + LOG.info("No api-key or gcube-token found. Calling Ckan Client without API-KEY"); return ckanCaller.getDataset(datasetId); - - }catch(Exception e){ + + } catch (Exception e) { LOG.error("Unable to retrieve such dataset, returning null ...", e); } @@ -275,42 +285,42 @@ public class DataCatalogueImpl implements DataCatalogue { checkNotNull(datasetIdOrName); checkArgument(!datasetIdOrName.isEmpty()); String url = null; - - try{ + + try { // get the dataset from name ExtendCkanClient client = new ExtendCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); CkanDataset dataset = client.getDataset(datasetIdOrName); String name = dataset.getName(); - if(dataset != null){ + if (dataset != null) { - if(getUriResolverUrl() != null) + if (getUriResolverUrl() != null) url = getUrlForProduct(CONTEXT, EntityContext.DATASET, name); - if(url == null || url.isEmpty()) - url = getPortletUrl() + "?" + URLEncoder.encode("path=/dataset/" + name, "UTF-8"); + if (url == null || url.isEmpty()) + url = getPortletUrl() + "?" + URLEncoder.encode("path=/dataset/" + name, "UTF-8"); } - }catch(Exception e){ + } catch (Exception e) { LOG.error("Error while retrieving dataset with id/name=" + datasetIdOrName, e); - } //requestEntity.put("clear_url", Boolean.toString(unencrypted)); + } // requestEntity.put("clear_url", Boolean.toString(unencrypted)); return url; } @Override public Map> getUserRoleByGroup(String username) { - LOG.info("Get user role by group called. The username is: "+username); - + LOG.info("Get user role by group called. The username is: " + username); + checkNotNull(username); - Map> toReturn = new HashMap>(); + Map> toReturn = new HashMap>(); - try{ + try { String ckanUsername = CatalogueUtilMethods.fromUsernameToCKanUsername(username); Map partialResult = dbCaller.getGroupsByUserFromDB(ckanUsername); - + for (String groupID : partialResult.keySet()) { CkanGroup group = ckanCaller.getGroup(groupID); @@ -318,27 +328,26 @@ public class DataCatalogueImpl implements DataCatalogue { subMap.put(group, partialResult.get(groupID)); toReturn.put(groupID, subMap); } - + LOG.debug("Returning map " + toReturn); - LOG.info("Found " + toReturn.size() +" group/s with the user "+username); - }catch(Exception e){ - LOG.error("Failed to retrieve roles of user in his/her own groups",e); + LOG.info("Found " + toReturn.size() + " group/s with the user " + username); + } catch (Exception e) { + LOG.error("Failed to retrieve roles of user in his/her own groups", e); } return toReturn; - + } @Override - public Map> getUserRoleByOrganization( - String username) { - LOG.info("Get user role by organization called. The username is: "+username); - + public Map> getUserRoleByOrganization(String username) { + LOG.info("Get user role by organization called. The username is: " + username); + checkNotNull(username); - Map> toReturn = new HashMap>(); + Map> toReturn = new HashMap>(); - try{ + try { String ckanUsername = CatalogueUtilMethods.fromUsernameToCKanUsername(username); Map partialResult = dbCaller.getOrganizationsByUserFromDB(ckanUsername); @@ -349,13 +358,13 @@ public class DataCatalogueImpl implements DataCatalogue { HashMap subMap = new HashMap(); subMap.put(org, partialResult.get(orgID)); toReturn.put(orgID, subMap); - LOG.debug("For organisation: " + org.getName() + ", the user "+username+" has role: "+subMap); + LOG.debug("For organisation: " + org.getName() + ", the user " + username + " has role: " + subMap); } LOG.debug("Returning map " + toReturn); - LOG.info("Found " + toReturn.size() +" organization/s with the user "+username); - }catch(Exception e){ - LOG.error("Failed to retrieve roles of user in his/her own groups",e); + LOG.info("Found " + toReturn.size() + " organization/s with the user " + username); + } catch (Exception e) { + LOG.error("Failed to retrieve roles of user in his/her own groups", e); } return toReturn; @@ -364,11 +373,12 @@ public class DataCatalogueImpl implements DataCatalogue { /** * Retrieve an url for the tuple scope, entity, entity name + * * @param context * @param entityContext * @param entityName */ - private String getUrlForProduct(String context, EntityContext entityContext, String entityName){ + private String getUrlForProduct(String context, EntityContext entityContext, String entityName) { String toReturn = null; @@ -386,19 +396,19 @@ public class DataCatalogueImpl implements DataCatalogue { HttpResponse response = httpClient.execute(httpPostRequest); - if(response.getStatusLine().getStatusCode() != 200) + if (response.getStatusLine().getStatusCode() != 200) throw new Exception("There was an error while creating an url " + response.getStatusLine()); toReturn = EntityUtils.toString(response.getEntity()); LOG.debug("Result is " + toReturn); - }catch(Exception e){ + } catch (Exception e) { LOG.error("Failed to get an url for this product", e); } - return toReturn; + return toReturn; } - + @Override public LandingPages getLandingPages() throws Exception { @@ -409,8 +419,6 @@ public class DataCatalogueImpl implements DataCatalogue { landingPages.setUrlTypes(PORTLET_URL_FOR_SCOPE + "?path=/type/"); return landingPages; } - - @Override public String getUriResolverUrl() { @@ -438,43 +446,23 @@ public class DataCatalogueImpl implements DataCatalogue { // list to return List toReturn = new ArrayList(); - try{ - + try { Map partialResult = dbCaller.getOrganizationsByUserFromDB(ckanUsername); for (String orgID : partialResult.keySet()) { - CkanOrganization org = ckanCaller.getOrganization(orgID,false); + CkanOrganization org = ckanCaller.getOrganization(orgID, false); LOG.debug("User " + ckanUsername + " is into " + org.getName()); toReturn.add(org); } - - // get the list of all organizations - //List organizations = ckanCaller.getOrganizationList(); - // iterate over them - /*for (CkanOrganization ckanOrganization : organizations) { - // get the list of users in it (if you try ckanOrganization.getUsers() it returns null.. maybe a bug TODO) - List users = ckanCaller.getOrganization(ckanOrganization.getName()).getUsers(); - // check if the current user is among them - for (CkanUser ckanUser : users) { - - if(ckanUser.getName().equals(ckanUsername)){ - - LOG.debug("User " + ckanUsername + " is into " + ckanOrganization.getName()); - toReturn.add(ckanOrganization); - break; - - } - } - }*/ - }catch(Exception e){ + } catch (Exception e) { LOG.error("Unable to get user's organizations", e); } return toReturn; } - + @Override public List getGroupsByUser(String username) { LOG.debug("Requested groups for user " + username); @@ -486,46 +474,25 @@ public class DataCatalogueImpl implements DataCatalogue { // list to return List toReturn = new ArrayList(); - try{ + try { Map partialResult = dbCaller.getGroupsByUserFromDB(ckanUsername); for (String groupID : partialResult.keySet()) { - CkanGroup group = ckanCaller.getGroup(groupID,false); + CkanGroup group = ckanCaller.getGroup(groupID, false); LOG.debug("User " + ckanUsername + " is into " + group.getName()); toReturn.add(group); } - /* - // get the list of all organizations - List groups = ckanCaller.getGroupList(); - - // iterate over them - for (CkanGroup ckanGroup : groups) { - - List users = ckanCaller.getGroup(ckanGroup.getName()).getUsers(); - - // check if the current user is among them - for (CkanUser ckanUser : users) { - - if(ckanUser.getName().equals(ckanUsername)){ - - LOG.debug("User " + ckanUsername + " is into " + ckanGroup.getName()); - toReturn.add(ckanGroup); - break; - - } - } - }*/ - }catch(Exception e){ + } catch (Exception e) { LOG.error("Unable to get user's groups", e); } return toReturn; } - + @Override - public List getOrganizationsIds(){ + public List getOrganizationsIds() { List toReturn = new ArrayList(); List orgs = ckanCaller.getOrganizationList(); @@ -539,7 +506,7 @@ public class DataCatalogueImpl implements DataCatalogue { } @Override - public List getOrganizationsNames(){ + public List getOrganizationsNames() { List toReturn = new ArrayList(); List orgs = ckanCaller.getOrganizationList(); @@ -573,7 +540,7 @@ public class DataCatalogueImpl implements DataCatalogue { return orgsName; } - + @Override public String getRoleOfUserInOrganization(String username, String orgName) { @@ -582,19 +549,19 @@ public class DataCatalogueImpl implements DataCatalogue { String apiKey = getApiKeyFromUsername(username); String usernameCkan = CatalogueUtilMethods.fromUsernameToCKanUsername(username); - try{ + try { ExtendCkanClient client = new ExtendCkanClient(CKAN_CATALOGUE_URL, apiKey); List users = client.getOrganization(orgName).getUsers(); for (CkanUser ckanUser : users) { - if(ckanUser.getName().equals(usernameCkan)){ + if (ckanUser.getName().equals(usernameCkan)) { toReturn = ckanUser.getCapacity(); break; } } - }catch(Exception e){ - LOG.error("Unable to retrieve the role the user has into organization: "+orgName, e); + } catch (Exception e) { + LOG.error("Unable to retrieve the role the user has into organization: " + orgName, e); } return toReturn; @@ -609,10 +576,10 @@ public class DataCatalogueImpl implements DataCatalogue { */ @Override public boolean checkValidUser(String username) { - + String apiKey = getApiKeyFromUsername(username); return apiKey != null; - + } private String getApiKeyFromUsername(String username) { @@ -627,64 +594,144 @@ public class DataCatalogueImpl implements DataCatalogue { String ckanUsername = CatalogueUtilMethods.fromUsernameToCKanUsername(username); // check in the hashmap first - if(apiKeysMap.containsKey(ckanUsername)){ + if (apiKeysMap.containsKey(ckanUsername)) { CKANTokenBean bean = apiKeysMap.get(ckanUsername); - if(bean.timestamp + EXPIRE_KEY_TIME > System.currentTimeMillis()){ // it's still ok + if (bean.timestamp + EXPIRE_KEY_TIME > System.currentTimeMillis()) { // it's still ok return bean.apiKey; } } LOG.debug("Api key was not in cache or it expired"); - try{ - + try { + String apiToReturn = dbCaller.getApiKeyFromUsername(username, State.ACTIVE.name().toLowerCase()); // save into the hashmap - if(apiToReturn != null) + if (apiToReturn != null) apiKeysMap.put(ckanUsername, new CKANTokenBean(apiToReturn, System.currentTimeMillis())); return apiToReturn; - }catch(Exception e){ + } catch (Exception e) { LOG.error("Unable to retrieve key for user " + ckanUsername, e); } - + return null; } @Override public boolean existProductWithNameOrId(String nameOrId) { - + checkNotNull(nameOrId); checkArgument(!nameOrId.isEmpty()); - try{ + try { ExtendCkanClient client = new ExtendCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); CkanDataset product = client.getDataset(nameOrId); return product != null; - }catch(Exception e){ + } catch (Exception e) { LOG.debug("A dataset with name " + nameOrId + " doesn't exist"); return false; } } - + @Override public CkanOrganization getOrganizationByIdOrName(String idOrName) { checkNotNull(idOrName); String ckanName = idOrName.toLowerCase(); - try{ + try { return ckanCaller.getOrganization(ckanName); - }catch(Exception e){ - LOG.warn("Failed to retrieve the organization with name " +idOrName+ " on the ckan: "+ckanCaller.getCatalogUrl(), e); + } catch (Exception e) { + LOG.warn("Failed to retrieve the organization with name " + idOrName + " on the ckan: " + + ckanCaller.getCatalogUrl(), e); } return null; } + /** + * Check if the user has this role into the organization/group with + * groupOrOrganization name + * + * @param ckanUsername + * @param organizationName + * @param correspondentRoleToCheck + * @return true if he has the role, false otherwise + */ + protected boolean isRoleAlreadySet(String ckanUsername, String groupOrOrganization, + RolesCkanGroupOrOrg correspondentRoleToCheck, boolean group) throws Exception { + + // get the users (if you try ckanOrganization.getUsers() it returns null.. maybe + // a bug TODO) + List users; + + if (group) + users = ckanCaller.getGroup(groupOrOrganization).getUsers(); + else + users = ckanCaller.getOrganization(groupOrOrganization).getUsers(); + + for (CkanUser ckanUser : users) { + if (ckanUser.getName().equals(ckanUsername)) + if (ckanUser.getCapacity().equals(RolesCkanGroupOrOrg.convertToCkanCapacity(correspondentRoleToCheck))) + return true; + else + break; + } + + return false; + } + + + /** + * Just check if the group exists + * + * @param nameOrId + * @param client + * @return + */ + private CkanGroup groupExists(String nameOrId) { + + CkanGroup toReturn = null; + + try { + ExtendCkanClient client = new ExtendCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); + toReturn = client.getGroup(nameOrId); + + } catch (JackanException je) { + LOG.error("The group " + nameOrId + " doesn't exist"); + } + + return toReturn; + } + + + + /* + * + * + * + * + * + * + * + * + * + * WRITE OPERATIONS + * + * + * + * + * + * + * + * + * + * + */ @Override public boolean checkRoleIntoOrganization(String username, String organizationName, @@ -800,60 +847,8 @@ public class DataCatalogueImpl implements DataCatalogue { return false; } - /** - * Check if the user has this role into the organization/group with groupOrOrganization name - * @param ckanUsername - * @param organizationName - * @param correspondentRoleToCheck - * @return true if he has the role, false otherwise - */ - protected boolean isRoleAlreadySet(String ckanUsername, String groupOrOrganization, RolesCkanGroupOrOrg correspondentRoleToCheck, boolean group) throws Exception{ - - // get the users (if you try ckanOrganization.getUsers() it returns null.. maybe a bug TODO) - List users; - - if(group) - users = ckanCaller.getGroup(groupOrOrganization).getUsers(); - else - users = ckanCaller.getOrganization(groupOrOrganization).getUsers(); - - for (CkanUser ckanUser : users) { - if(ckanUser.getName().equals(ckanUsername)) - if(ckanUser.getCapacity().equals(RolesCkanGroupOrOrg.convertToCkanCapacity(correspondentRoleToCheck))) - return true; - else - break; - } - - return false; - } - - - /* - * - * - * - * - * - * - * - * - * - * WRITE OPERATIONS - * - * - * - * - * - * - * - * - * - * - */ - @Override - public String createCkanDatasetMultipleCustomFields(String username, String title, String name, String organizationNameOrId, + public String createCkanDatasetMultipleCustomFields(String username, String title, String name, String organizationName, String author, String authorMail, String maintainer, String maintainerMail, long version, String description, String licenseId, List tags, Map> customFieldsMultiple, List resources, boolean setPublic, boolean setSearchable, boolean socialPost) throws Exception { @@ -861,13 +856,36 @@ public class DataCatalogueImpl implements DataCatalogue { // checks (minimum) checkNotNull(username); - checkNotNull(organizationNameOrId); - checkArgument(!organizationNameOrId.isEmpty()); + //checkNotNull(organizationNameOrId); + //checkArgument(!organizationNameOrId.isEmpty()); checkArgument(!(title == null && name == null || title.isEmpty() && name.isEmpty()), "Name and Title cannot be empty/null at the same time!"); try { + + //Business logic to pass the organization name to gCat if and only if the scope is of ROOT or VO (not VRE) + String scope = ScopeProvider.instance.get(); + + if(scope==null) + throw new Exception("No scope detected. You must set it"); + + GCUBE_SCOPE_LEVEL gCubeScopeLevel = GCubeUtils.toGCubeLevel(scope); + String toPassOrganizationToGcat = null; + + if(gCubeScopeLevel!=null) { + switch (gCubeScopeLevel) { + case ROOT: + case VO: + toPassOrganizationToGcat = organizationName; + break; + case VRE: + toPassOrganizationToGcat = null; + default: + break; + } + } + // String ckanUsername = getUserFromApiKey(apiKey).getName(); - CkanDataset dataset = CKANConveter.toCkanDataset(ckanCaller, username, title, name, organizationNameOrId, author, authorMail, + CkanDataset dataset = CKANConveter.toCkanDataset(ckanCaller, username, title, name, toPassOrganizationToGcat, author, authorMail, maintainer, maintainerMail, version, description, licenseId, tags, null, customFieldsMultiple, resources, setPublic, setSearchable); @@ -910,50 +928,51 @@ public class DataCatalogueImpl implements DataCatalogue { } - @Override + @Override public boolean patchFieldsForDataset(String datasetId, Map mapFields) throws Exception { - LOG.info("Called patch the fields "+mapFields+" for dataset id: "+datasetId); - + LOG.info("Called patch the fields " + mapFields + " for dataset id: " + datasetId); + checkNotNull(datasetId); checkNotNull(mapFields); - + try { JSONObject jsonObj = new JSONObject(); for (String key : mapFields.keySet()) { jsonObj.put(key, mapFields.get(key)); } - + LOG.debug("Json Dataset is: " + jsonObj); gCatCaller.patchDataset(datasetId, jsonObj); - LOG.info("Patch operation for dataset "+datasetId+" terminates without errors"); + LOG.info("Patch operation for dataset " + datasetId + " terminates without errors"); return true; - }catch (Exception e) { - LOG.error("Error occurred trying to patch the dataset with id: "+datasetId, e.getMessage()); - throw new Exception("Unable to patch the dataset. Error: "+e.getMessage()); + } catch (Exception e) { + LOG.error("Error occurred trying to patch the dataset with id: " + datasetId, e.getMessage()); + throw new Exception("Unable to patch the dataset. Error: " + e.getMessage()); } - + } - + @Override public boolean setSearchableFieldForDataset(String datasetId, boolean searchable) throws Exception { - LOG.info("Set searchalbe field as "+searchable+" for dataset id: "+datasetId); - + LOG.info("Set searchalbe field as " + searchable + " for dataset id: " + datasetId); + checkNotNull(datasetId); - + try { String searchableAsString = searchable ? "True" : "False"; JSONObject jsonObj = new JSONObject(); - jsonObj.put("id", datasetId); //just to be sure + jsonObj.put("id", datasetId); // just to be sure jsonObj.put("searchable", searchableAsString); LOG.debug("Json Dataset is: " + jsonObj); gCatCaller.patchDataset(datasetId, jsonObj); - LOG.info("Set 'searchable' field for dataset "+datasetId+" terminates without errors"); + LOG.info("Set 'searchable' field for dataset " + datasetId + " terminates without errors"); return true; - }catch (Exception e) { - LOG.error("Error occured trying to set the 'searchable' field for the dataset with id: "+datasetId, e.getMessage()); - throw new Exception("Unable to set the 'searchable' field for this dataset. Error: "+e.getMessage()); + } catch (Exception e) { + LOG.error("Error occured trying to set the 'searchable' field for the dataset with id: " + datasetId, + e.getMessage()); + throw new Exception("Unable to set the 'searchable' field for this dataset. Error: " + e.getMessage()); } - + } @@ -964,23 +983,23 @@ public class DataCatalogueImpl implements DataCatalogue { // checks checkNotNull(resourceBean); - if(CatalogueUtilMethods.resourceExists(resourceBean.getUrl())){ + if (CatalogueUtilMethods.resourceExists(resourceBean.getUrl())) { CkanResource resource = CKANConveter.toCkanResource(CKAN_CATALOGUE_URL, resourceBean); - - String jsonValueResource = MarshUnmarshCkanObject.toJsonValueResource(resource); - LOG.trace("Serialized resource is: " + jsonValueResource); + + String jsonValueResource = MarshUnmarshCkanObject.toJsonValueResource(resource, METHOD.TO_CREATE); + LOG.debug("Serialized resource is: " + jsonValueResource); jsonValueResource = gCatCaller.addResourceToDataset(resourceBean.getDatasetId(), jsonValueResource); LOG.debug("Added resource to dataset is: " + jsonValueResource); CkanResource createdRes = MarshUnmarshCkanObject.toCkanResource(jsonValueResource); - if(createdRes != null){ - LOG.debug("Resource " + createdRes.getName() + " is now available"); + if (createdRes != null) { + LOG.debug("Resource " + createdRes.getName() + " added correclty"); return createdRes.getId(); - } - }else + + } else throw new Exception("It seems there is no is no resource at this url " + resourceBean.getUrl()); return null; @@ -1041,27 +1060,5 @@ public class DataCatalogueImpl implements DataCatalogue { return toCreate; } - - /** - * Just check if the group exists - * @param nameOrId - * @param client - * @return - */ - private CkanGroup groupExists(String nameOrId){ - - CkanGroup toReturn = null; - - try{ - ExtendCkanClient client = new ExtendCkanClient(CKAN_CATALOGUE_URL, CKAN_TOKEN_SYS); - toReturn = client.getGroup(nameOrId); - - }catch(JackanException je){ - LOG.error("The group "+nameOrId+" doesn't exist"); - } - - return toReturn; - } - } diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/CKANConveter.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/CKANConveter.java index c285789..d772a2c 100644 --- a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/CKANConveter.java +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/CKANConveter.java @@ -8,14 +8,14 @@ import java.util.Map.Entry; import org.gcube.datacatalogue.ckanutillibrary.ckan.ExtendCkanClient; import org.gcube.datacatalogue.ckanutillibrary.shared.ResourceBean; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDataset; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanOrganization; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanPair; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResource; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanTag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.trentorise.opendata.jackan.model.CkanDataset; -import eu.trentorise.opendata.jackan.model.CkanOrganization; -import eu.trentorise.opendata.jackan.model.CkanPair; -import eu.trentorise.opendata.jackan.model.CkanResource; -import eu.trentorise.opendata.jackan.model.CkanTag; import net.htmlparser.jericho.Renderer; import net.htmlparser.jericho.Segment; import net.htmlparser.jericho.Source; @@ -39,7 +39,7 @@ public class CKANConveter { * @param username the username * @param title the title * @param name the name - * @param organizationNameOrId the organization name or id + * @param organizationName the organization name or id * @param author the author * @param authorMail the author mail * @param maintainer the maintainer @@ -55,7 +55,7 @@ public class CKANConveter { * @param setSearchable the set searchable * @return the ckan dataset */ - public static CkanDataset toCkanDataset(ExtendCkanClient ckanCaller, String username, String title, String name, String organizationNameOrId, String author, + public static CkanDataset toCkanDataset(ExtendCkanClient ckanCaller, String username, String title, String name, String organizationName, String author, String authorMail, String maintainer, String maintainerMail, long version, String description, String licenseId, List tags, Map customFields, Map> customFieldsMultipleValues, List resources, boolean setPublic, boolean setSearchable) { @@ -81,8 +81,10 @@ public class CKANConveter { dataset.setTitle(title); //TODO SHOULD BE REVISITED by gCAT? - CkanOrganization orgOwner = ckanCaller.getOrganization(organizationNameOrId); - dataset.setOwnerOrg(orgOwner.getId()); + if(organizationName!=null) { + CkanOrganization orgOwner = ckanCaller.getOrganization(organizationName); + dataset.setOwnerOrg(orgOwner.getId()); + } dataset.setAuthor(author); dataset.setAuthorEmail(authorMail); @@ -112,6 +114,7 @@ public class CKANConveter { } dataset.setTags(ckanTags); dataset.setNumTags(ckanTags.size()); + } // set the custom fields, if any diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/GCubeUtils.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/GCubeUtils.java new file mode 100644 index 0000000..1b35c32 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/GCubeUtils.java @@ -0,0 +1,61 @@ +package org.gcube.datacatalogue.ckanutillibrary.server.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The Class GCubeUtils. + * + * @author Francesco Mangiacrapa at ISTI-CNR (francesco.mangiacrapa@isti.cnr.it) + * + * Feb 10, 2021 + */ +public class GCubeUtils { + + /** + * The Enum GCUBE_SCOPE_LEVEL. + * + * @author Francesco Mangiacrapa at ISTI-CNR (francesco.mangiacrapa@isti.cnr.it) + * + * Feb 10, 2021 + */ + public static enum GCUBE_SCOPE_LEVEL { + ROOT, VO, VRE + } + + private static final Logger LOG = LoggerFactory.getLogger(CatalogueUtilMethods.class); + + /** + * To G cube level. + * + * @param scope the scope + * @return the gcube scope level + * @throws IllegalArgumentException the illegal argument exception + */ + public static GCUBE_SCOPE_LEVEL toGCubeLevel(String scope) throws IllegalArgumentException { + LOG.debug("called toGCubeLevel on " + scope); + + if (scope == null || scope.isEmpty()) + throw new IllegalArgumentException("Scope is null or empty"); + + if (!scope.startsWith("/")) { + throw new IllegalArgumentException("Scope should start with '/' ->" + scope); + } + if (scope.endsWith("/")) { + throw new IllegalArgumentException("Scope should not end with '/' ->" + scope); + } + String[] splits = scope.split("/"); + if (splits.length > 4) + throw new IllegalArgumentException("Scope is invalid, too many '/' ->" + scope); + if (splits.length == 2) // is a root VO + return GCUBE_SCOPE_LEVEL.ROOT; + else if (splits.length == 3) {// is a VO + return GCUBE_SCOPE_LEVEL.VO; + } else if (splits.length == 4) {// is a VRE + return GCUBE_SCOPE_LEVEL.VRE; + } + return null; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/GenericUtils.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/GenericUtils.java new file mode 100644 index 0000000..cb9571e --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/server/utils/GenericUtils.java @@ -0,0 +1,405 @@ +package org.gcube.datacatalogue.ckanutillibrary.server.utils; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Locale; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; + + +/** + * The Class GenericUtils. + * + * @author Francesco Mangiacrapa at ISTI-CNR (francesco.mangiacrapa@isti.cnr.it) + * + * Feb 9, 2021 + */ +public final class GenericUtils { + + private static final Logger LOG = LoggerFactory.getLogger(GenericUtils.class.getName()); + + /** + * + * Checks if provided array is non null and non empty . + * + * @param errorMessageTemplate a template for the exception message should + * the check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code array} is empty or null + * @throws NullPointerException if the check fails and either + * {@code errorMessageTemplate} or {@code errorMessageArgs} is null (don't + * let this happen) + * + * @return a non-null non-empty array + */ + public static T[] checkNotEmpty(@Nullable T[] array, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { + String formattedMessage = format(errorMessageTemplate, errorMessageArgs); + + checkArgument(array != null, "%s -- Reason: Found null iterable.", formattedMessage); + if (array.length == 0) { + throw new IllegalArgumentException(formattedMessage + " -- Reason: Found empty array."); + } + return array; + } + + /** + * Tolerance for probabilities + */ + public static final double TOLERANCE = 0.001; + + /** + * Used for unparseable dates and other stuff. + */ + public static final String UNPARSEABLE = "unparseable:"; + + + + /** + * Tod Commons build properties path. + */ + public static final String BUILD_PROPERTIES_PATH = "tod.commons.build.properties"; + + + + /** + * Converts a language code to Java Locale. On null input returns + * {@link Locale#ROOT} + * + * Notice Java 7 introduced {@link Locale#forLanguageTag(String)}, but that + * method throws null pointer exception on null string, which unfortunately + * can happen quite often, so we use this method instead. + * + * @see Locale#forLanguageTag(String) + * @see #localeToLanguageTag(Locale) localeToLanguageTag(Locale) for the inverse operation + */ + public static Locale languageTagToLocale(@Nullable String languageTag) { + + if (languageTag == null) { + LOG.warn("Found null locale, returning Locale.ROOT"); + return Locale.ROOT; + } + return Locale.forLanguageTag(languageTag); + } + + /** + * Converts a Java locale to a String. On null input returns the empty + * string (which corresponds to Java {@link Locale#ROOT}) + * + * @see #languageTagToLocale(java.lang.String) #languageTagToLocale(java.lang.String) for the inverse operation + */ + public static String localeToLanguageTag(@Nullable Locale locale) { + if (locale == null) { + LOG.warn("Found null locale, returning empty string (which corresponds to Locale.ROOT)"); + return ""; + } + return locale.getLanguage(); + } + + /** + * Returns a new url with a slash added at the end of provided url. If + * provided url already ends with a slash it just returns it. + * + * @param url + */ + public static String addSlash(String url) { + checkNotNull(url, "invalid url!"); + String trimmedUrl = url.trim(); + if (trimmedUrl.endsWith("/")) { + return trimmedUrl; + } else { + return trimmedUrl + "/"; + } + } + + /** + * Returns the provided url with all trailing slash at the end removed. + */ + public static String removeTrailingSlash(String url) { + checkNotNull(url, "invalid url!"); + String tempUrl = url.trim(); + while (tempUrl.endsWith("/")) { + tempUrl = tempUrl.substring(0, tempUrl.length() - 1); + } + return tempUrl; + } + + /** + * Checks if provided URL is to be considered 'dirty'. Method may use some + * heuristics to detect oddities, like i.e. the string "null" inside the + * url. + * + * @deprecated Moved to + * {@link eu.trentorise.opendata.commons.validation.Preconditions#checkNotDirtyUrl(java.lang.String, java.lang.Object) } @ + * param url the URL to check + * @param prependedErrorMessage + * the exception message to use if the check fails; will be + * converted to a string using String.valueOf(Object) and + * prepended to more specific error messages. + * + * @throws IllegalArgumentException + * if provided URL fails validation. + * + * @return the non-dirty URL that was validated + * + */ + public static String checkNotDirtyUrl(@Nullable String url, @Nullable Object prependedErrorMessage) { + checkNotEmpty(url, prependedErrorMessage); + + if (url.equalsIgnoreCase("null")) { + throw new IllegalArgumentException(String.valueOf(prependedErrorMessage) + + " -- Reason: Found URL with string \"" + url + "\" as content!"); + } + + // todo delete this is a too radical checker... + if (url.toLowerCase() + .endsWith("/null")) { + throw new IllegalArgumentException( + String.valueOf(prependedErrorMessage) + " -- Reason: Found URL ending with /\"null\": " + url); + } + + return url; + } + + /** + * + * Checks if provided string is non null and non empty. + * + * @throws IllegalArgumentException + * if provided string fails validation + * + * @return the non-empty string that was validated + */ + public static String checkNotEmpty(String string, @Nullable Object prependedErrorMessage) { + checkArgument(string != null, "%s -- Reason: Found null string.", prependedErrorMessage); + if (string.length() == 0) { + throw new IllegalArgumentException( + String.valueOf(prependedErrorMessage) + " -- Reason: Found empty string."); + } + return string; + } + + /** + * Returns true if provided string is non null and non empty . + */ + public static boolean isNotEmpty(@Nullable String string) { + return string != null && string.length() != 0; + } + + /** + * Parses an URL having a numeric ID after the provided prefix, i.e. for + * prefix 'http://entitypedia.org/concepts/' and url + * http://entitypedia.org/concepts/14324 returns 14324 + * + * @deprecated this shouldn't be here..... + * + * @throws IllegalArgumentException + * on invalid URL + */ + public static long parseNumericalId(String prefix, String url) { + + checkNotNull(prefix, "prefix can't be null!"); + checkNotEmpty(url, "Invalid url!"); + + String s; + if (prefix.length() > 0) { + int pos = url.indexOf(prefix); + if (pos != 0) { + throw new IllegalArgumentException("Invalid URL for prefix " + prefix + ": " + url); + } + s = url.substring(prefix.length()); + } else { + s = url; + } + try { + return Long.parseLong(s); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("Invalid URL for prefix " + prefix + ": " + url, ex); + } + + } + + /** + * + * Substitutes each {@code %s} in {@code template} with an argument. These + * are matched by position: the first {@code %s} gets {@code args[0]}, etc. + * If there are more arguments than placeholders, the unmatched arguments + * will be appended to the end of the formatted message in square braces. + *
+ *
+ * (Copied from Guava's + * {@link com.google.common.base.Preconditions#format(java.lang.String, java.lang.Object...) } + * ) + * + * @param template + * a non-null string containing 0 or more {@code %s} + * placeholders. + * @param args + * the arguments to be substituted into the message template. + * Arguments are converted to strings using + * {@link String#valueOf(Object)}. Arguments can be null. + * + * @since 1.1 + */ + public static String format(String template, @Nullable Object... args) { + if (template == null) { + LOG.warn("Found null template while formatting, converting it to \"null\""); + } + template = String.valueOf(template); // null -> "null" + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + + return builder.toString(); + } + + /** + * Extracts parameters from given url. Works also with multiple params with + * same name. + * + * @return map of param name : [args] + * @throws IllegalArgumentException + * @since 1.1 + */ + public static Multimap parseUrlParams(String url) { + URL u; + try { + u = new URL(url); + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("Ill formed url!", ex); + } + Multimap queryPairs = LinkedListMultimap.create(); + final String[] pairs = u.getQuery() + .split("&"); + + try { + for (String pair : pairs) { + final int idx = pair.indexOf("="); + + final String key; + + key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; + + final String value = idx > 0 && pair.length() > idx + 1 + ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : ""; + queryPairs.put(key, value); + } + return queryPairs; + } catch (UnsupportedEncodingException ex) { + throw new IllegalArgumentException("Encoding not supported!", ex); + } + } + + /** + * Returns a copy of provided map with {@code newObject} set under the given + * key. If key already exists replaces its value in the returned object. Not + * efficient, but sometimes we need it. + * + * @param newObject + * Must be an immutable object. + * @since 1.1 + */ + public static ImmutableMap putKey(Map map, K key, V newObject) { + ImmutableMap.Builder mapb = ImmutableMap.builder(); + + for (K k : map.keySet()) { + if (!k.equals(key)) { + mapb.put(k, map.get(k)); + } + } + mapb.put(key, newObject); + return mapb.build(); + } + +// private static final FastDateFormat ISO_YEAR_FORMAT = FastDateFormat.getInstance("yyyy"); +// +// private static final FastDateFormat ISO_YEAR_MONTH_FORMAT = FastDateFormat.getInstance("yyyy-MM"); +// +// /** +// * @deprecated experimental, try to avoid using it for now +// * @since 1.1 +// * @throws TodParseException +// */ +// // todo this parser is horrible +// public static Date parseIso8061(String s) { +// +// try { +// return DateFormatUtils.ISO_DATE_TIME_ZONE_FORMAT.parse(s); +// } catch (ParseException ex) { +// } +// +// try { +// return DateFormatUtils.ISO_DATETIME_FORMAT.parse(s); +// } catch (ParseException ex) { +// } +// +// try { +// return DateFormatUtils.ISO_DATE_TIME_ZONE_FORMAT.parse(s); +// } catch (ParseException ex) { +// } +// +// try { +// return DateFormatUtils.ISO_DATE_FORMAT.parse(s); +// } catch (ParseException ex) { +// } +// +// try { +// return ISO_YEAR_MONTH_FORMAT.parse(s); +// } catch (ParseException ex) { +// } +// +// try { +// return ISO_YEAR_FORMAT.parse(s); +// } catch (ParseException ex) { +// } +// +// // todo week dates, ordinal dates +// +// throw new TodParseException("Couldn't parse date as ISO8061. Unparseable date was:" + s); +// } +// + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanActivity.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanActivity.java new file mode 100644 index 0000000..4529d07 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanActivity.java @@ -0,0 +1,133 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.sql.Timestamp; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * + * @author David Leoni + */ +public class CkanActivity { + + /** + * i.e. "ddb21e57-da76-4dc1-a815-4edd0e9e332e" + */ + private String id; + /** + * Ckan always refer to UTC timezone, in JSON looks like i.e. "2013-03-08T09:31:20.833590" + */ + private Timestamp timestamp; + /** + * i.e. "Impostazioni modificate." + */ + private String message; + /** + * i.e. "admin" + */ + private String author; + @Nullable + private Timestamp approvedTimestamp; + private List packages; + private List groups; + private CkanState state; + + public CkanActivity() { + + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Timestamp getTimestamp() { + return timestamp; + } + + /** + * internally date is stored with UTC timezone + */ + public void setTimestamp(Timestamp timestamp) { + this.timestamp = timestamp; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + @Nullable + public Timestamp getApprovedTimestamp() { + return approvedTimestamp; + } + + /** + * internally date is stored with UTC timezone + * + * @param approvedTimestamp + */ + public void setApprovedTimestamp(@Nullable Timestamp approvedTimestamp) { + this.approvedTimestamp = approvedTimestamp; + } + + public List getPackages() { + return packages; + } + + public void setPackages(List packages) { + this.packages = packages; + } + + /** + * Returns list of group names (i.e. region-trentino) + */ + public List getGroups() { + return groups; + } + + /** + * Returns list of group names (i.e. region-trentino) + */ + public void setGroups(List groups) { + this.groups = groups; + } + + public CkanState getState() { + return state; + } + + public void setState(CkanState state) { + this.state = state; + } +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanCapacity.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanCapacity.java new file mode 100644 index 0000000..34d0975 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanCapacity.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +/** + * Actually they are lower case in ckan, but 'public' and 'private' clash with + * Java keywords + * + * @author David Leoni + */ +public enum CkanCapacity { + + MEMBER, EDITOR, ADMIN, PUBLIC, PRIVATE; +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDataset.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDataset.java new file mode 100644 index 0000000..b7fc95e --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDataset.java @@ -0,0 +1,241 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.sql.Timestamp; + +import javax.annotation.Nullable; + +import org.gcube.com.fasterxml.jackson.annotation.JsonProperty; + + + +/** + * Extends {@link CkanDatasetBase} with fields found in search operations. + */ +public class CkanDataset extends CkanDatasetBase { + + private String creatorUserId; + private String licenseUrl; + private String licenseTitle; + private Timestamp metadataCreated; + private Timestamp metadataModified; + private int numResources; + private CkanTrackingSummary trackingSummary; + private int numTags; + private String notesRendered; + private Boolean open; + + private CkanOrganization organization; + + /** + * Actually it is named 'private' in api. Appears in searches. + */ + private Boolean priv; + private String revisionId; + private Timestamp revisionTimestamp; + + public CkanDataset() { + } + + /** + * @see CkanDatasetBase#CkanDatasetBase(String) + */ + public CkanDataset(String name) { + super(name); + } + + + + public String getCreatorUserId() { + return creatorUserId; + } + + public void setCreatorUserId(@Nullable String creatorUserId) { + this.creatorUserId = creatorUserId; + } + + /** + * + * Legacy api 1/2 docs says: boolean indication of whether dataset is + * open according to Open Knowledge Definition, based on other fields + */ + @JsonProperty("isopen") + public Boolean isOpen() { + return open; + } + + /** + * @see #isOpen() + */ + @JsonProperty("isopen") + public void setOpen(Boolean isOpen) { + this.open = isOpen; + } + + public String getLicenseTitle() { + return licenseTitle; + } + + public void setLicenseTitle(String licenseTitle) { + this.licenseTitle = licenseTitle; + } + + public String getLicenseUrl() { + return licenseUrl; + } + + public void setLicenseUrl(String licenseUrl) { + this.licenseUrl = licenseUrl; + } + + /** + * CKAN always refer to UTC timezone + */ + public Timestamp getMetadataCreated() { + return metadataCreated; + } + + /** + * CKAN always refer to UTC timezone + */ + public void setMetadataCreated(Timestamp metadataCreated) { + this.metadataCreated = metadataCreated; + } + + /** + * CKAN always refers to UTC timezone + */ + public Timestamp getMetadataModified() { + return metadataModified; + } + + /** + * CKAN always refers to UTC timezone + */ + public void setMetadataModified(Timestamp metadataModified) { + this.metadataModified = metadataModified; + } + + public String getNotesRendered() { + return notesRendered; + } + + public void setNotesRendered(String notesRendered) { + this.notesRendered = notesRendered; + } + + public int getNumTags() { + return numTags; + } + + public void setNumTags(int numTags) { + this.numTags = numTags; + } + + + + /** + * The organization that owns the dataset. + * + * Notice that if the dataset was obtained with a + * {@link eu.trentorise.opendata.jackan.CkanClient#getDataset(java.lang.String)} call, the returned + * organization won't have all the params you would get with a + * {@link eu.trentorise.opendata.jackan.CkanClient#getOrganization(java.lang.String)} call. + */ + public CkanOrganization getOrganization() { + return organization; + } + + /** + * Sets the organization that owns the dataset. + */ + public void setOrganization(CkanOrganization organization) { + this.organization = organization; + } + + /** + * Actually it is named "private" in the CKAN API. Appears in dataset + * searches. + */ + @JsonProperty("private") + public Boolean isPriv() { + return priv; + } + + /** + * Actually it is named "private" in the CKAN API. Appears in dataset + * searches. + */ + public void setPriv(Boolean priv) { + this.priv = priv; + } + + public int getNumResources() { + return numResources; + } + + public void setNumResources(int numResources) { + this.numResources = numResources; + } + + + + + /** + * Returns the alphanumerical id, like + * "39d94b20-ea72-4c5e-bd8f-967a77e03946" + */ + public String getRevisionId() { + return revisionId; + } + + /** + * Sets the alphanumerical id, like "39d94b20-ea72-4c5e-bd8f-967a77e03946" + */ + public void setRevisionId(String revisionId) { + this.revisionId = revisionId; + } + + /** + * Returns date in UTC timezone. Probably it is automatically calculated by + * CKAN. + */ + public Timestamp getRevisionTimestamp() { + return revisionTimestamp; + } + + /** + * CKAN always refer to UTC timezone. Probably it is automatically + * calculated by CKAN. + * + * @param revisionTimestamp + */ + public void setRevisionTimestamp(Timestamp revisionTimestamp) { + this.revisionTimestamp = revisionTimestamp; + } + + public CkanTrackingSummary getTrackingSummary() { + return trackingSummary; + } + + public void setTrackingSummary(CkanTrackingSummary trackingSummary) { + this.trackingSummary = trackingSummary; + } + + + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDatasetBase.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDatasetBase.java new file mode 100644 index 0000000..6791e29 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDatasetBase.java @@ -0,0 +1,470 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import static org.gcube.datacatalogue.ckanutillibrary.server.utils.GenericUtils.isNotEmpty; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.gcube.com.fasterxml.jackson.annotation.JsonAnyGetter; +import org.gcube.com.fasterxml.jackson.annotation.JsonAnySetter; +import org.gcube.com.fasterxml.jackson.annotation.JsonIgnore; + + + +/** + * A Ckan Dataset, which in turn holds Ckan Resources. + * + * In Ckan terminology it is also known as 'package'. + * + * {@link CkanDatasetBase} holds fields that can be sent when + * creating + * a dataset,, while {@link CkanDataset} holds more fields that can be + * returned with searches. + * + * This class initializes nothing to fully preserve all we get from ckan. In + * practice, all fields of retrieved resources can be null except maybe + * {@code name}. + * + * @author David Leoni + * @since 0.4.1 + */ +public class CkanDatasetBase { + + private String author; + private String authorEmail; + private List extras; + private List groups; + private String id; + private String licenseId; + private String maintainer; + private String maintainerEmail; + private String name; + private String notes; + private String ownerOrg; + private List relationshipsAsObject; + private List relationshipsAsSubject; + private List resources; + private CkanState state; + private List tags; + private String title; + private String type; + private String url; + private String version; + + /** + * Custom CKAN instances might sometimes gift us with properties that don't + * end up in extras. They will end up here. + */ + @Nullable + private Map others; + + public CkanDatasetBase() { + } + + /** + * Constructor with the minimal set of attributes required to successfully + * create a dataset on the server. + * + * @param name the dataset name (contains no spaces and has dashes as + * separators, i.e. "limestone-pavement-orders") + */ + public CkanDatasetBase(String name) { + this(); + this.name = name; + } + + /** + * CKAN instances might have + * + * custom data schemas that force presence of custom properties among + * 'regular' ones. In this case, they go to 'others' field. Note that to + * further complicate things there is also an {@link #getExtras() extras} + * field. + * + * @see #putOthers(java.lang.String, java.lang.Object) + */ + @JsonAnyGetter + @Nullable + public Map getOthers() { + return others; + } + + /** + * @see #getOthers() + * @see #putOthers(java.lang.String, java.lang.Object) + */ + public void setOthers(@Nullable Map others) { + this.others = others; + } + + /** + * See {@link #getOthers()} + * + * @see #setOthers(java.util.Map) + */ + @JsonAnySetter + public void putOthers(String name, Object value) { + if (others == null) { + others = new HashMap<>(); + } + others.put(name, value); + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthorEmail() { + return authorEmail; + } + + public void setAuthorEmail(String authorEmail) { + this.authorEmail = authorEmail; + } + + /** + * Notice that if the dataset was obtained with a + * {@link eu.trentorise.opendata.jackan.CkanClient#getDataset(java.lang.String)} call, the returned group + * won't have all the params you would get with a + * {@link eu.trentorise.opendata.jackan.CkanClient#getGroup(java.lang.String)} call. + */ + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + + /** + * Adds CkanGroups + * + * @param ckanGroups The CkanGroups elements + * + * @since 0.4.3 + */ + public void addGroups(CkanGroup... ckanGroups) { + if (this.groups == null) { + this.groups = new ArrayList<>(ckanGroups.length); + } + Collections.addAll(this.groups, ckanGroups); + } + + /** + * Regular place where to put custom metadata. See also + * {@link #getOthers()}. Note also extras can be in CkanDataset but not in + * CkanResource. + */ + public List getExtras() { + return extras; + } + + /** + * See {@link #getExtras()} + */ + public void setExtras(List extras) { + this.extras = extras; + } + + /** + * Always returns a non-null map (which might be empty) + */ + @JsonIgnore + public Map getExtrasAsHashMap() { + HashMap hm = new HashMap<>(); + if (extras != null) { + for (CkanPair cp : extras) { + hm.put(cp.getKey(), cp.getValue()); + } + } + return hm; + } + + /** + * Adds CkanExtras + * + * @param extras The CkanExtra elements + * + * @since 0.4.3 + */ + public void addExtras(CkanPair... extras) { + if (this.extras == null) { + this.extras = new ArrayList<>(extras.length); + } + Collections.addAll(this.extras, extras); + } + + /** + * Returns the alphanumerical id, i.e. + * "c4577b8f-5603-4098-917e-da03e8ddf461" + */ + public String getId() { + return id; + } + + /** + * Sets the alphanumerical id, i.e. "c4577b8f-5603-4098-917e-da03e8ddf461" + */ + public void setId(String id) { + this.id = id; + } + + /** + * The license id (i.e. 'cc-zero') + */ + public String getLicenseId() { + return licenseId; + } + + /** + * The license id (i.e. 'cc-zero') + */ + public void setLicenseId(String licenseId) { + this.licenseId = licenseId; + } + + public String getMaintainer() { + return maintainer; + } + + public void setMaintainer(String maintainer) { + this.maintainer = maintainer; + } + + public String getMaintainerEmail() { + return maintainerEmail; + } + + public void setMaintainerEmail(String maintainerEmail) { + this.maintainerEmail = maintainerEmail; + } + + /** + * The dataset name (contains no spaces and has dashes as separators, i.e. + * "limestone-pavement-orders") + */ + public String getName() { + return name; + } + + /** + * The dataset name. Name must not contain spaces and have dashes as + * separators, i.e. "limestone-pavement-orders" + * + */ + public void setName(String name) { + this.name = name; + } + + /** + * A description of the dataset. See also + * {@link CkanDataset#getNotesRendered()} Note CkanResource has instead a + * field called {@link CkanResourceBase#getDescription() description}. + */ + public String getNotes() { + return notes; + } + + /** + * A description of the dataset. See also + * {@link CkanDataset#getNotesRendered()} Note CkanResource has instead a + * field called {@link CkanResourceBase#getDescription() description}. + */ + public void setNotes(String notes) { + this.notes = notes; + } + + /** + * The owner organization alphanunmerical id, like + * "b112ed55-01b7-4ca4-8385-f66d6168efcc". + */ + public String getOwnerOrg() { + return ownerOrg; + } + + /** + * The owner organization alphanunmerical id, like + * "b112ed55-01b7-4ca4-8385-f66d6168efcc". + */ + public void setOwnerOrg(String ownerOrg) { + this.ownerOrg = ownerOrg; + } + + public List getRelationshipsAsObject() { + return relationshipsAsObject; + } + + public void setRelationshipsAsObject(List relationshipsAsObject) { + this.relationshipsAsObject = relationshipsAsObject; + } + + public List getRelationshipsAsSubject() { + return relationshipsAsSubject; + } + + public void setRelationshipsAsSubject(List relationshipsAsSubject) { + this.relationshipsAsSubject = relationshipsAsSubject; + } + + public List getResources() { + return this.resources; + } + + public void setResources(List resources) { + this.resources = resources; + } + + /** + * Adds CkanResources + * + * @param resources The CkanResources elements + * + * @since 0.4.3 + */ + public void addCkanResources(CkanResource... resources) { + if (this.resources == null) { + this.resources = new ArrayList<>(resources.length); + } + Collections.addAll(this.resources, resources); + } + + /** + * The current state of the dataset, e.g. 'active' or 'deleted', only active + * datasets show up in search results and other lists of datasets, this + * parameter will be ignored if you are not authorized to change the state + * of the dataset (optional, default: 'active') + */ + public CkanState getState() { + return state; + } + + /** + * The current state of the dataset, e.g. 'active' or 'deleted', only active + * datasets show up in search results and other lists of datasets, this + * parameter will be ignored if you are not authorized to change the state + * of the dataset (optional, default: 'active') + */ + public void setState(CkanState state) { + this.state = state; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + /** + * Adds CkanTag + * + * @param tags The CkanTags elements + * + * @since 0.4.3 + */ + public void addTags(CkanTag... tags) { + if (this.tags == null) { + this.tags = new ArrayList<>(tags.length); + } + Collections.addAll(this.tags, tags); + } + + /** + * The title, like "Hospitals of Trento" + */ + public String getTitle() { + return title; + } + + /** + * The title, like "Hospitals of Trento" + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * The type of the dataset (optional), IDatasetForm plugins associate + * themselves with different dataset types and provide custom dataset + * handling behaviour for these types + */ + public String getType() { + return type; + } + + /** + * The type of the dataset (optional), IDatasetForm plugins associate + * themselves with different dataset types and provide custom dataset + * handling behaviour for these types + */ + public void setType(String type) { + this.type = type; + } + + /** + * Should be the landing page on original data provider website describing + * the dataset. + */ + public String getUrl() { + return url; + } + + /** + * Should be the landing page on original data provider website describing + * the dataset. + */ + public void setUrl(String url) { + this.url = url; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + /** + * Returns the id if non-empty, the name otherwise + */ + @Nullable + public String idOrName() { + return isNotEmpty(getId()) ? getId() : getName(); + } + + /** + * Returns the name if non-empty, the id otherwise + */ + @Nullable + public String nameOrId() { + + return isNotEmpty(getName()) ? getName() : getId(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDatasetRelationship.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDatasetRelationship.java new file mode 100644 index 0000000..793e5e5 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanDatasetRelationship.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +/** + * + * @author David Leoni + */ +public class CkanDatasetRelationship { + + private String comment; + private String id; + private String object; + private String subject; + private String type; + + public CkanDatasetRelationship() { + } + + /** + * Constructor with the minal amount of fields required for creation + */ + public CkanDatasetRelationship(String subject, String object, String type) { + this.object = object; + this.subject = subject; + this.type = type; + } + + + /** + * A comment about the relationship + */ + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * The id or name of the dataset that is the object of the relationship + * + */ + public String getObject() { + return object; + } + + /** + * The id or name of the dataset that is the object of the relationship + * + */ + public void setObject(String object) { + this.object = object; + } + + /** + * The id or name of the dataset that is the subject of the relationship + */ + public String getSubject() { + return subject; + } + + /** + * The id or name of the dataset that is the subject of the relationship + */ + public void setSubject(String subject) { + this.subject = subject; + } + + /** + * The type of the relationship, one of 'depends_on', 'dependency_of', + * 'derives_from', 'has_derivation', 'links_to', 'linked_from', 'child_of' + * or 'parent_of' + * + */ + public String getType() { + return type; + } + + /** + * The type of the relationship, one of 'depends_on', 'dependency_of', + * 'derives_from', 'has_derivation', 'links_to', 'linked_from', 'child_of' + * or 'parent_of' + * + */ + public void setType(String type) { + this.type = type; + } +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanError.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanError.java new file mode 100644 index 0000000..8cd65d8 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanError.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.util.HashMap; +import java.util.Map; + +import org.gcube.com.fasterxml.jackson.annotation.JsonAnyGetter; +import org.gcube.com.fasterxml.jackson.annotation.JsonAnySetter; +import org.gcube.com.fasterxml.jackson.annotation.JsonProperty; + + + +/** + * For list of errors see controllers/api.py. + * For error descriptions see logic/__init__.py + * + * @author David Leoni + */ +public class CkanError { + + + public static final String AUTHORIZATION_ERROR = "Authorization Error"; + public static final String INTEGRITY_ERROR = "Integrity Error"; + public static final String NOT_FOUND_ERROR = "Not Found Error"; + public static final String SEARCH_QUERY_ERROR = "Search Query Error"; + public static final String SEARCH_ERROR = "Search Error"; + public static final String SEARCH_INDEX_ERROR = "Search Index Error"; + public static final String VALIDATION_ERROR = "Validation Error"; + + + private String message; + /** + * actually the original is __type + */ + private String type; + + /** + * Holds fields we can't foresee + */ + private Map others = new HashMap(); + + @Override + public String toString() { + return "Ckan error of type: " + getType() + " message:" + getMessage() + + " Other fields:" + others.toString(); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + /** + * For types, see {@link CkanError class description} + */ + @JsonProperty("__type") + public String getType() { + return type; + } + + /** + * todo what are possible types? + */ + public void setType(String type) { + this.type = type; + } + + /** + * Holds fields we can't foresee + */ + @JsonAnyGetter + public Map getOthers() { + return others; + } + + /** + * Holds fields we can't foresee + */ + @JsonAnySetter + public void setOthers(String name, Object value) { + others.put(name, value); + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroup.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroup.java new file mode 100644 index 0000000..6771c71 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroup.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +/** + * Class to explicitly model a Ckan group, which is not an organization, + * although it has the same attributes. + * + * {@link CkanGroupOrgBase} holds fields that can be sent when + * creating + * a group/organization, while {@link CkanGroupOrg} holds more fields that can be + * returned with searches. + * + * This class initializes nothing to fully preserve all we get from ckan. In + * practice, all fields of retrieved resources can be null except maybe + * {@code name}. + * + * @author David Leoni + */ +public class CkanGroup extends CkanGroupOrg { + + public CkanGroup() { + super(); + setOrganization(false); + } + + /** + * Constructor with minimal amount of parameters needed to successfully + * create an instance on the server. + * + * @param name Name in the url, lowercased and without spaces. i.e. + * management-of-territory + */ + public CkanGroup(String name) { + this(); + this.setName(name); + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroupOrg.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroupOrg.java new file mode 100644 index 0000000..4cfa377 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroupOrg.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.sql.Timestamp; + +/** + * Abstract class with additional fields found during searches of groups and + * organizations. + * + * @author David Leoni + * @since 0.4.1 + */ +public abstract class CkanGroupOrg extends CkanGroupOrgBase { + + private Timestamp created; + private String displayName; + private String imageDisplayUrl; + private int numFollowers; + private int packageCount; + private String revisionId; + + protected CkanGroupOrg(){ + super(); + } + + public Timestamp getCreated() { + return created; + } + + public void setCreated(Timestamp created) { + this.created = created; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getImageDisplayUrl() { + return imageDisplayUrl; + } + + public void setImageDisplayUrl(String imageDisplayUrl) { + this.imageDisplayUrl = imageDisplayUrl; + } + + public int getNumFollowers() { + return numFollowers; + } + + public void setNumFollowers(int numFollowers) { + this.numFollowers = numFollowers; + } + + public int getPackageCount() { + return packageCount; + } + + public void setPackageCount(int packageCount) { + this.packageCount = packageCount; + } + + public String getRevisionId() { + return revisionId; + } + + public void setRevisionId(String revisionId) { + this.revisionId = revisionId; + } +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroupOrgBase.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroupOrgBase.java new file mode 100644 index 0000000..dfcbade --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanGroupOrgBase.java @@ -0,0 +1,256 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + + +import static org.gcube.datacatalogue.ckanutillibrary.server.utils.GenericUtils.isNotEmpty; + +import java.util.List; + +import javax.annotation.Nullable; + +import org.gcube.com.fasterxml.jackson.annotation.JsonProperty; +import org.gcube.com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.gcube.datacatalogue.ckanutillibrary.jackan.JackanModule; + + + +/** + * Abstract class to model the same data structure that Ckan uses for creating + * both groups and organizations. Since they are different things and work with + * different APIs we made two different implementations, {@link CkanGroup} and + * {@link CkanOrganization}. The Ckan way to tell the difference is the {@link #isOrganization() + * } field). + * + * @author David Leoni + * @since 0.4.1 + */ +public abstract class CkanGroupOrgBase { + + private String approvalStatus; + private String description; + private List extras; + private List groups; + private String id; + + private String imageUrl; + private String name; + private boolean organization; + @JsonDeserialize(using = JackanModule.GroupOrgPackagesDeserializer.class) + private List packages; + + private CkanState state; + private String title; + private String type; + private List users; + + protected CkanGroupOrgBase() { + } + + /** + * Constructor with minimal amount of parameters needed to successfully + * create an instance on the server. + * + * @param name Name in the url, lowercased and without spaces. i.e. + * management-of-territory + */ + protected CkanGroupOrgBase(String name) { + this.name = name; + } + + /** + * can be "approved" or what? Bah + */ + public String getApprovalStatus() { + return approvalStatus; + } + + public void setApprovalStatus(String approvalStatus) { + this.approvalStatus = approvalStatus; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getExtras() { + return extras; + } + + public void setExtras(List extras) { + this.extras = extras; + } + + /** + * Have no idea what this could mean inside a group! + */ + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * The URL to an image to be displayed on the group/org’s page (optional) + */ + public String getImageUrl() { + return imageUrl; + } + + /** + * The URL to an image to be displayed on the group/org’s page (optional) + */ + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + /** + * Name in the url, lowercased and without spaces. i.e. + * management-of-territory + */ + public String getName() { + return name; + } + + /** + * @param name Name in the url, lowercased and without spaces. i.e. + * management-of-territory + */ + public void setName(String name) { + this.name = name; + } + + /** + * A ckan group can also be an organization. + */ + @JsonProperty("is_organization") + public boolean isOrganization() { + return organization; + } + + /** + * Protected, we use it only when deserializing + */ + @JsonProperty("is_organization") + protected void setOrganization(boolean organization) { + this.organization = organization; + } + + /** + * The datasets of the group. Some api return the *number* of packages, in + * this case we set the value to null. + */ + public List getPackages() { + return packages; + } + + /** + * The datasets of the group. Some api return the *number* of packages, in + * this case we set the value to null. + */ + public void setPackages(List packages) { + this.packages = packages; + } + + /** + * The current state of the group, e.g. 'active' or 'deleted', only active + * groups show up in search results and other lists of groups, this + * parameter will be ignored if you are not authorized to change the state + * of the group (optional, default: 'active') + */ + public CkanState getState() { + return state; + } + + /** + * The current state of the group/organization, e.g. 'active' or 'deleted', + * only active groups/organizations show up in search results and other + * lists of groups/organizations, this parameter will be ignored if you are + * not authorized to change the state (optional, default: 'active') + */ + public void setState(CkanState state) { + this.state = state; + } + + /** + * Human readable name, i.e. "Department of Justice" + * + * @see #getName() + */ + public String getTitle() { + return title; + } + + /** + * Human readable name, i.e. "Department of Justice" + * + * @see #setName(java.lang.String) + */ + public void setTitle(String title) { + this.title = title; + } + + public String getType() { + return type; + } + + /** + * Don't know possible ckan types + */ + public void setType(String type) { + this.type = type; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + /** + * Returns the id if non-empty, the name otherwise + */ + @Nullable + public String idOrName() { + return isNotEmpty(getId()) ? getId() : getName(); + } + + /** + * Returns the name if non-empty, the id otherwise + */ + @Nullable + public String nameOrId() { + + return isNotEmpty(getName()) ? getName() : getId(); + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanLicense.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanLicense.java new file mode 100644 index 0000000..a3234f0 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanLicense.java @@ -0,0 +1,233 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import org.gcube.com.fasterxml.jackson.annotation.JsonProperty; + +/** + * + * @author David Leoni + * @since 0.4.1 + */ +public class CkanLicense { + + private String status; + + private String maintainer; + + private String family; + + private String title; + + private boolean domainData; + + private boolean okdCompliant; + + private boolean domainContent; + + private String url; + + private boolean osiCompliant; + + private boolean domainSoftware; + + /** + * i.e. "cc-zero" + */ + private String id; + + public CkanLicense() { + } + + /** + * Returns true if it complies with the OpenDefinition: + * http://opendefinition.org/ + */ + @JsonProperty("is_okd_compliant") + public boolean isOkdCompliant() { + return okdCompliant; + } + + /** + * @param okdCompliant True if it complies with the OpenDefinition: + * http://opendefinition.org/ + * + * @see #setOsiCompliant(boolean) + */ + public void setOkdCompliant(boolean okdCompliant) { + this.okdCompliant = okdCompliant; + } + + /** + * Returns true if it complies with the Open Source Initiative? + * http://opensource.org/licenses + */ + @JsonProperty("is_osi_compliant") + public boolean isOsiCompliant() { + return osiCompliant; + } + + /** + * + * @param osiCompliant True if it complies with the Open Source Initiative? + * http://opensource.org/licenses + * + * @see #setOkdCompliant(boolean) + */ + public void setOsiCompliant(boolean osiCompliant) { + this.osiCompliant = osiCompliant; + } + + /** + * Returns true if the license applies to content domain. + * + * @see #isDomainSoftware() + * @see #isDomainData() + */ + @JsonProperty("domain_content") + public boolean isDomainContent() { + return domainContent; + } + + /** + * @param domainContent True if the license applies to content domain. + * + * @see #setDomainSoftware(boolean) + * @see #setDomainData(boolean) + */ + public void setDomainContent(boolean domainContent) { + this.domainContent = domainContent; + } + + /** + * True if the license applies to data domain. + * + * @see #isDomainContent() + * @see #isDomainSoftware() + */ + @JsonProperty("domain_data") + public boolean isDomainData() { + return domainData; + } + + /** + * @param domainData True if the license applies to data domain. + * + * @see #setDomainSoftware(boolean) + * @see #setDomainContent(boolean) + */ + public void setDomainData(boolean domainData) { + this.domainData = domainData; + } + + /** + * True if the license applies to software domain. + * + * @see #isDomainContent() + * @see #isDomainData() + */ + @JsonProperty("domain_software") + public boolean isDomainSoftware() { + return domainSoftware; + } + + /** + * @param domainSoftware True if the license applies to software domain. + * + * @see #setDomainData(boolean) + * @see #setDomainContent(boolean) + */ + public void setDomainSoftware(boolean domainSoftware) { + this.domainSoftware = domainSoftware; + } + + /** + * Gets the status, i.e. "active" todo check possaible status + */ + public String getStatus() { + return status; + } + + /** + * Sets the status, i.e. "active" todo check possaible status + */ + public void setStatus(String status) { + this.status = status; + } + + public String getMaintainer() { + return maintainer; + } + + public void setMaintainer(String maintainer) { + this.maintainer = maintainer; + } + + public String getFamily() { + return family; + } + + public void setFamily(String family) { + this.family = family; + } + + /** + * Gets the title, i.e. "Creative Commons CCZero" + */ + public String getTitle() { + return title; + } + + /** + * Sets the title, i.e. "Creative Commons CCZero" + * + * @param title + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Returns the url of a document describing the license i.e. + * "http://creativecommons.org/publicdomain/zero/1.0/deed.it", + */ + public String getUrl() { + return url; + } + + /** + * Sets the url of a document describing the license i.e. + * "http://creativecommons.org/publicdomain/zero/1.0/deed.it", + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * Returns the id of the license as used by ckan, i.e. "cc-zero" + */ + public String getId() { + return id; + } + + /** + * Sets the id of the license as used by ckan, i.e. "cc-zero" + */ + public void setId(String id) { + this.id = id; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanOrganization.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanOrganization.java new file mode 100644 index 0000000..fec6c67 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanOrganization.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + + +/** + * Class to explicitly model a Ckan organization, which is not a group, + * although it has the same attributes. + * + * {@link CkanGroupOrgBase} holds fields that can be sent when + * creating + * a group/organization, while {@link CkanGroupOrg} holds more fields that can be + * returned with searches. + * + * This class initializes nothing to fully preserve all we get from ckan. In + * practice, all fields of retrieved resources can be null except maybe + * {@code name}. + * + * @author David Leoni + */ +public class CkanOrganization extends CkanGroupOrg { + + public CkanOrganization() { + super(); + setOrganization(true); + } + + /** + * Constructor with minimal amount of parameters needed to successfully + * create an instance on the server. + * + * @param name Name in the url, lowercased and without spaces. i.e. + * management-of-territory + */ + public CkanOrganization(String name) { + this(); + this.setName(name); + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanPair.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanPair.java new file mode 100644 index 0000000..ed54f93 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanPair.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.util.Objects; + +import javax.annotation.Nullable; + +/** + * For key/value pairs present in extras field. Implements equals and hashCode. + * + * @author David Leoni + */ +public class CkanPair { + + private String key; + + @Nullable + private String value; + + public CkanPair() { + } + + public CkanPair(String key, @Nullable String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @Nullable + public String getValue() { + return value; + } + + public void setValue(@Nullable String value) { + this.value = value; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + Objects.hashCode(this.key); + hash = 97 * hash + Objects.hashCode(this.value); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CkanPair other = (CkanPair) obj; + if (!Objects.equals(this.key, other.key)) { + return false; + } + if (!Objects.equals(this.value, other.value)) { + return false; + } + return true; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResource.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResource.java new file mode 100644 index 0000000..2f3a580 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResource.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import javax.annotation.Nullable; + +/** + * Extends {@link CkanResourceBase} with fields found in search operations. + */ +public class CkanResource extends CkanResourceBase { + + private String resourceGroupId; + private String owner; + private int position; + private String revisionTimestamp; + private CkanTrackingSummary trackingSummary; + private CkanState state; + private String urlType; + + public CkanResource() { + } + + /** + * + * @see CkanResourceBase#CkanResourceBase(String, String) + */ + public CkanResource(String url, String packageId) { + super(url, packageId); + } + + + /** + * todo - What the hell is this? alphanumerical id, i.e. + * "fd6375cd-1d6a-41e8-8e10-460a11e2308e" + */ + public String getResourceGroupId() { + return resourceGroupId; + } + + /** + * todo - What the hell is this? alphanumerical id, i.e. + * "fd6375cd-1d6a-41e8-8e10-460a11e2308e" + */ + public void setResourceGroupId(String resourceGroupId) { + this.resourceGroupId = resourceGroupId; + } + + /** + * Username of the owner + */ + @Nullable + public String getOwner() { + return owner; + } + + /** + * @param owner Username of the owner + */ + public void setOwner(@Nullable String owner) { + this.owner = owner; + } + + /** + * Position inside the dataset? + */ + public int getPosition() { + return position; + } + + /** + * @param position Position inside the dataset? + */ + public void setPosition(int position) { + this.position = position; + } + + /** + * + */ + public String getRevisionTimestamp() { + return revisionTimestamp; + } + + /** + * + */ + public void setRevisionTimestamp(@Nullable String revisionTimestamp) { + this.revisionTimestamp = revisionTimestamp; + } + + public CkanState getState() { + return state; + } + + public void setState(CkanState state) { + this.state = state; + } + + public CkanTrackingSummary getTrackingSummary() { + return trackingSummary; + } + + public void setTrackingSummary(CkanTrackingSummary trackingSummary) { + this.trackingSummary = trackingSummary; + } + + + /** + * todo - Don't know what it is + */ + public String getUrlType() { + return urlType; + } + + /** + * todo - Don't know what it is + */ + public void setUrlType(String urlType) { + this.urlType = urlType; + } +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResourceBase.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResourceBase.java new file mode 100644 index 0000000..d703e63 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResourceBase.java @@ -0,0 +1,490 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.io.File; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.gcube.com.fasterxml.jackson.annotation.JsonAnyGetter; +import org.gcube.com.fasterxml.jackson.annotation.JsonAnySetter; +import org.gcube.com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions.JackanException; +import org.slf4j.Logger; + + + +/** + * + * A Ckan Resource describes with metadata a physical file, which may reside + * outside ckan. Resources are part of {@link CkanDataset}. In DCAT terminology, + * a Ckan Resource is a DCAT Distribution. + * + * {@link CkanResourceBase} holds fields that can be sent when + * creating + * a resource,, while {@link CkanResource} holds more fields that can be + * returned with searches. + * + * This class initializes nothing to fully preserve all we get from ckan. In + * practice, all fields of retrieved resources can be null except maybe + * {@code url}. + * + * @author David Leoni + * @since 0.4.1 + */ +@JsonIgnoreProperties({"upload"}) +public class CkanResourceBase { + + private static final Logger LOG = org.slf4j.LoggerFactory.getLogger(CkanResourceBase.class.getName()); + + private String cacheLastUpdated; + private String cacheUrl; + private Timestamp created; + private String description; + private String format; + private String hash; + private String id; + private String lastModified; + private String mimetype; + private String mimetypeInner; + private String name; + private String resourceType; + private String revisionId; + private String size; + private String url; + private File upload; + + private Timestamp webstoreLastUpdated; + + private String webstoreUrl; + + /** + * The dataset this resource belongs to. Not present when getting resources + * but needed when uploading them. + */ + private String packageId; + + /** + * See {@link #getOthers()} + */ + private Map others; + + /** + * The dataset this resource belongs to. Latest ckan give it back, but older ones + * may return {@code null}. Required when creating resources. + */ + @Nullable + public String getPackageId() { + return packageId; + } + + /** + * The dataset this resource belongs to. Latest ckan give it back, but older ones + * may return {@code null}. Required when creating resources. + * + * @param packageId the dataset this resource belongs to. + */ + public void setPackageId(@Nullable String packageId) { + this.packageId = packageId; + } + + public CkanResourceBase() { + } + + /** + * Constructor with the minimal list of required items to successfully + * create a resource on the server. + * + * @param url the Url to the pyhsical file i.e. + * http://dati.trentino.it/storage/f/2013-05-09T140831/TRENTO_Laghi_monitorati_UTM.csv + * (could also be a file outside ckan server) + * @param packageId id of the dataset that contains the resource + */ + public CkanResourceBase(String url, + String packageId) { + this(); + this.url = url; + this.packageId = packageId; + } + + /** + * CKAN instances might have + * + * custom data schemas that force presence of custom properties among + * 'regular' ones. In this case, they go to 'others' field. Notice that + * differently from dataset a resource down't have 'extras' field. + * + * @see #putOthers(java.lang.String, java.lang.Object) + */ + @JsonAnyGetter + @Nullable + public Map getOthers() { + return others; + } + + /** + * @param others + * @see #getOthers() + * @see #putOthers(java.lang.String, java.lang.Object) + */ + public void setOthers(@Nullable Map others) { + this.others = others; + } + + /** + * See {@link #getOthers()} + * + * @see #setOthers(java.util.Map) + */ + @JsonAnySetter + public void putOthers(String name, Object value) { + if (others == null) { + others = new HashMap(); + } + others.put(name, value); + } + + /** + * Should be a Timestamp + */ + public String getCacheLastUpdated() { + return cacheLastUpdated; + } + + /** + * Should be a Timestamp + */ + public void setCacheLastUpdated(@Nullable String cacheLastUpdated) { + this.cacheLastUpdated = cacheLastUpdated; + } + + /** + * God only knows what this is + */ + @Nullable + public String getCacheUrl() { + return cacheUrl; + } + + /** + * God only knows what this is + */ + public void setCacheUrl(String cacheUrl) { + this.cacheUrl = cacheUrl; + } + + /** + * In JSON is something like this: i.e. "2013-05-09T14:08:32.666477" . Ckan + * always refers to UTC timezone + */ + public Timestamp getCreated() { + return created; + } + + /** + * Ckan always refers to UTC timezone + */ + public void setCreated(Timestamp created) { + this.created = created; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * In Ckan 1.8 was lowercase, 2.2a seems capitalcase. + */ + public String getFormat() { + return format; + } + + /** + * In Ckan 1.8 was lowercase, 2.2a seems capitalcase. + */ + public void setFormat(String format) { + this.format = format; + } + + /** + * Sometimes for dati.trentino.it can be the empty string + */ + public String getHash() { + return hash; + } + + /** + * Sometimes for dati.trentino.it can be the empty string + */ + public void setHash(String hash) { + this.hash = hash; + } + + /** + * Returns the alphanumerical id, i.e. + * "c4577b8f-5603-4098-917e-da03e8ddf461" + */ + public String getId() { + return id; + } + + /** + * @param id alphanumerical id, i.e. "c4577b8f-5603-4098-917e-da03e8ddf461" + */ + public void setId(String id) { + this.id = id; + } + + /** + * Jackan note: this field should represent the timestamp of the last update + * of the resource *data*, not metadata. I think it's set by extensions and + * harvesters. Notice Ckan always refers to UTC timezone in + * {@link eu.trentorise.opendata.jackan.CkanClient#CKAN_TIMESTAMP_PATTERN} format, so this field probably + * should follow the same format. + */ + public String getLastModified() { + return lastModified; + } + + /** + * Jackan note: this field should represent the timestamp of the last update + * of the resource *data*, not metadata. I think it's set by extensions and + * harvesters. Notice Ckan always refers to UTC timezone in + * {@link eu.trentorise.opendata.jackan.CkanClient#CKAN_TIMESTAMP_PATTERN} format, so this field probably + * should follow the same format. + */ + public void setLastModified(String lastModified) { + this.lastModified = lastModified; + } + + /** + * i.e. text/csv + */ + public String getMimetype() { + return mimetype; + } + + /** + * i.e. text/csv + */ + public void setMimetype(String mimetype) { + this.mimetype = mimetype; + } + + /** + * Unknown meaning, as usual. Can be the empty string or null + */ + public String getMimetypeInner() { + return mimetypeInner; + } + + /** + * Unknown meaning, as usual. Can be the empty string or null + */ + public void setMimetypeInner(@Nullable String mimetypeInner) { + this.mimetypeInner = mimetypeInner; + } + + /** + * + * Human readable name, i.e. "Apple Production 2013 in CSV format". Not to + * be confused with {@link CkanDataset#name} which instead is lowercased and + * intended to be part of the url. + * + * + * Notice we found name null in data.gov.uk datasets... i.e. + * + * unclaimed-estates-list , taken + * from + * this dataset search (They use description field instead) + */ + @Nullable + public String getName() { + return name; + } + + /** + * Human readable name, i.e. "Apple Production 2013 in CSV format". Not to + * be confused with {@link CkanDataset#name} which instead is lowercased and + * intended to be part of the url. For Nullable explanation see + * {@link #getName()} + */ + public void setName(@Nullable String name) { + this.name = name; + } + + /** + * So far, found: "api", "file", "file.upload" + */ + public String getResourceType() { + return resourceType; + } + + /** + * So far, found: "api", "file", "file.upload" + */ + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + /** + * alphanumerical id, i.e. 0c949f17-d123-4379-8536-cfcf25b3b0e9 + */ + public String getRevisionId() { + return revisionId; + } + + /** + * alphanumerical id, i.e. 0c949f17-d123-4379-8536-cfcf25b3b0e9 + */ + public void setRevisionId(String revisionId) { + this.revisionId = revisionId; + } + + /** + * File size in bytes, if calculated by ckan for files in storage, like i.e. + * "242344". Otherwise it can be anything a human can insert. + */ + public String getSize() { + return size; + } + + /** + * File size in bytes, if calculated by ckan for files in storage, like i.e. + * "242344". Otherwise it can be anything a human can insert. + */ + public void setSize(@Nullable String size) { + this.size = size; + } + + /** + * The Url to the pyhsical file i.e. + * http://dati.trentino.it/storage/f/2013-05-09T140831/TRENTO_Laghi_monitorati_UTM.csv + * (could also be a file outside ckan server) + */ + public String getUrl() { + return url; + } + + /** + * The Url to the pyhsical file i.e. + * http://dati.trentino.it/storage/f/2013-05-09T140831/TRENTO_Laghi_monitorati_UTM.csv + * (could also be a file outside ckan server) + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * The file to be added to the resource. See {@link #setUpload(File, boolean)} for further info. + * + * @since 0.4.3 + */ + public File getUpload() { + return upload; + } + + /** + * Sets the file to upload. + * + * @param upload the File to upload. + * @deprecated Put here only to have a bean-style setter, + * if possible prefer calling {@link #setUpload(File, boolean)} + * + * @since 0.4.3 + */ + public void setUpload(@Nullable File upload){ + this.setUpload(upload, false); + } + + /** + * A file to be added to the resource. + * + * @param upload + * the file to upload. Its {@link #getSize() size} is automatically set. If passed file is {@code null}, + * reset upload and size fields. + * @param guessMimeTypeAndFormat + * whether automatic guessing of {@link #getMimetype() mime type} and {@link #getFormat() format} is done. + * + * @throws JackanException + * if asked for automatic guessing of mime type and format but those could not be guessed. + * + * @since 0.4.3 + */ + public void setUpload(@Nullable File upload, boolean guessMimeTypeAndFormat) { + LOG.error("Set UPLOAD IS NOT IMPLEMENTED!"); +// if (upload == null) { +// this.upload = null; +// this.size = null; +// } else { +// this.upload = upload; +// this.size = String.valueOf(upload.length()); +// if (guessMimeTypeAndFormat) { +// try (InputStream is = new FileInputStream(upload); +// BufferedInputStream bis = new BufferedInputStream(is);) { +// AutoDetectParser parser = new AutoDetectParser(); +// Metadata md = new Metadata(); +// md.add(Metadata.RESOURCE_NAME_KEY, upload.getName()); +// MediaType mediaType = parser.getDetector().detect(bis, md); +// this.mimetype = mediaType.getBaseType().toString(); +// this.format = mediaType.getSubtype().toUpperCase(); +// } catch (FileNotFoundException e) { +// LOG.log(Level.WARNING, "Unable to load file {0}", upload.getName()); +// throw new JackanException("Unable to load file " + upload.getName(), e); +// } catch (IOException e) { +// LOG.log(Level.WARNING, "Unable to detect mime type and format for file " + upload.getName(), e); +// throw new JackanException("Unable to detect mime type and format for file " + upload.getName(), e); +// } +// } +// } + + } + + /** + * Ckan always refers to UTC timezone + */ + public Timestamp getWebstoreLastUpdated() { + return webstoreLastUpdated; + } + + /** + * Ckan always refers to UTC timezone + */ + public void setWebstoreLastUpdated(Timestamp webstoreLastUpdated) { + this.webstoreLastUpdated = webstoreLastUpdated; + } + + /** + * Found "active" as value. Maybe it is a CkanState? + */ + public String getWebstoreUrl() { + return webstoreUrl; + } + + /** + * @param webstoreUrl Found "active" as value. Maybe it is a CkanState? + */ + public void setWebstoreUrl(String webstoreUrl) { + this.webstoreUrl = webstoreUrl; + } +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResponse.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResponse.java new file mode 100644 index 0000000..9a0e5db --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanResponse.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +/** + * + * @author David Leoni + */ +public class CkanResponse { + + private String help; + private boolean success; + private CkanError error; + + public CkanResponse() { + } + + public CkanResponse(String help, boolean success, CkanError error) { + this.help = help; + this.success = success; + this.error = error; + } + + public String getHelp() { + return help; + } + + public void setHelp(String help) { + this.help = help; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public CkanError getError() { + return error; + } + + public void setError(CkanError error) { + this.error = error; + } + + @Override + public String toString() { + return "CkanResponse{error=" + error+ ", success=" + success + ", help=" + help + '}'; + } + + + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanState.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanState.java new file mode 100644 index 0000000..2d9c6d0 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanState.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +/** + * Possible states of a Dataset or Resource - only active datasets show up in + * search results and other lists of datasets + * + * @author David Leoni + */ +public enum CkanState { + + /** + * Means element shows up in search results + */ + active, + deleted +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTag.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTag.java new file mode 100644 index 0000000..0482876 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTag.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.sql.Timestamp; + +import javax.annotation.Nullable; + +/** + * Extends {@link CkanTagBase} with fields found in search operations. + */ +public class CkanTag extends CkanTagBase { + + private String displayName; + private Timestamp revisionTimestamp; + private CkanState state; + + public CkanTag() { + super(); + } + + /** + * You can use this constructor when adding a free tag to a dataset. + * + * @param name the name for the new tag, a string between 2 and 100 + * characters long containing only alphanumeric characters and -, _ and ., + * e.g. 'Jazz' + */ + public CkanTag(String name) { + super(name); + } + + /** + * You can use this constructor when creating a tag associated to a + * controlled vocabulary + * + * @param name the name for the new tag, a string between 2 and 100 + * characters long containing only alphanumeric characters and -, _ and ., + * e.g. 'Jazz' + */ + public CkanTag(String name, String vocabularyId) { + super(name, vocabularyId); + } + + /** + * + * @return a human readable name, i.e. "Habitat Quality" + */ + public String getDisplayName() { + return displayName; + } + + /** + * + * @param displayName a human readable name, i.e. "Habitat Quality" + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @Nullable + public Timestamp getRevisionTimestamp() { + return revisionTimestamp; + } + + public void setRevisionTimestamp(@Nullable Timestamp revisionTimestamp) { + this.revisionTimestamp = revisionTimestamp; + + } + + public CkanState getState() { + return state; + } + + public void setState(CkanState state) { + this.state = state; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTagBase.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTagBase.java new file mode 100644 index 0000000..3a770a9 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTagBase.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +/** + * A Ckan Tag. Tags can be free or belong to a controlled vocabulary. + * + * {@link CkanTag} holds fields that can be sent when + * creating + * a tag, while {@link CkanTag} holds more fields that can be returned with + * searches. Notice free tags can be created by just adding them to a dataset to + * create. + * + * This class initializes nothing to fully preserve all we get from ckan. In + * practice, all fields of retrieved resources can be null except maybe + * {@code name}. + * + * @author David Leoni + * @since 0.4.1 + */ +public class CkanTagBase { + + private String id; + private String name; + private String vocabularyId; + + public CkanTagBase() { + } + + /** + * You can use this constructor when adding a free tag to a dataset. + * + * @param name the name for the new tag, a string between 2 and 100 + * characters long containing only alphanumeric characters and -, _ and ., + * e.g. 'Jazz' + */ + public CkanTagBase(String name) { + this(); + this.name = name; + } + + /** + * You can use this constructor when creating a tag associated to a + * controlled vocabulary + * + * @param name the name for the new tag, a string between 2 and 100 + * characters long containing only alphanumeric characters and -, _ and ., + * e.g. 'Jazz' + */ + public CkanTagBase(String name, String vocabularyId) { + this(name); + this.vocabularyId = vocabularyId; + } + + /** + * + * @return alphanumerical id, i.e. "7f0aa2fe-9733-4ce2-a351-d10278ba44ac" + */ + public String getId() { + return id; + } + + /** + * + * @param id alphanumerical id, i.e. "7f0aa2fe-9733-4ce2-a351-d10278ba44ac" + */ + public void setId(String id) { + this.id = id; + } + + /** + * + * @return a human readable name, i.e. "Habitat Quality" + */ + public String getName() { + return name; + } + + /** + * + * @param name a human readable name, i.e. "Habitat Quality" + */ + public void setName(String name) { + this.name = name; + } + + public String getVocabularyId() { + return vocabularyId; + } + + public void setVocabularyId(String vocabularyId) { + this.vocabularyId = vocabularyId; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTrackingSummary.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTrackingSummary.java new file mode 100644 index 0000000..c06a024 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanTrackingSummary.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +/** + * Just don't know what it is. + * + * @author David Leoni + */ +public class CkanTrackingSummary { + + private int recent; + private int total; + + public CkanTrackingSummary() { + } + + public int getRecent() { + return recent; + } + + public void setRecent(int recent) { + this.recent = recent; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanUser.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanUser.java new file mode 100644 index 0000000..56ca3ce --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanUser.java @@ -0,0 +1,169 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.sql.Timestamp; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * + * + * @author David Leoni + */ +public class CkanUser extends CkanUserBase { + + private List activity; + private boolean activityStreamsEmailNotifications; + private Timestamp created; + private String capacity; + private String displayName; + private String emailHash; + private int numberAdministeredPackages; + private int numFollowers; + private int numberOfEdits; + private CkanState state; + private boolean sysadmin; + + public CkanUser() { + super(); + } + + /** + * Constructor with the minimal amount of fields required for a successful + * creation. + * + * @param name the name of the new user, a string between 2 and 100 + * characters in length, containing only lowercase alphanumeric characters, + * - and _ + */ + public CkanUser(String name, String email, String password) { + super(name, email, password); + } + + public boolean isActivityStreamsEmailNotifications() { + return activityStreamsEmailNotifications; + } + + public void setActivityStreamsEmailNotifications(boolean activityStreamsEmailNotifications) { + this.activityStreamsEmailNotifications = activityStreamsEmailNotifications; + } + + /** + * Ckan uses UTC timezone + */ + public Timestamp getCreated() { + return created; + } + + /** + * Ckan uses UTC timezone + */ + public void setCreated(Timestamp created) { + this.created = created; + } + + /** + * i.e. David Leoni + */ + public String getDisplayName() { + return displayName; + } + + /** + * i.e. David Leoni + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getEmailHash() { + return emailHash; + } + + public void setEmailHash(String emailHash) { + this.emailHash = emailHash; + } + + public int getNumberAdministeredPackages() { + return numberAdministeredPackages; + } + + public void setNumberAdministeredPackages(int numberAdministeredPackages) { + this.numberAdministeredPackages = numberAdministeredPackages; + } + + public int getNumberOfEdits() { + return numberOfEdits; + } + + public void setNumberOfEdits(int numberOfEdits) { + this.numberOfEdits = numberOfEdits; + } + + public CkanState getState() { + return state; + } + + public void setState(CkanState state) { + this.state = state; + } + + public boolean isSysadmin() { + return sysadmin; + } + + public void setSysadmin(boolean sysadmin) { + this.sysadmin = sysadmin; + } + + /** + * You can obtain it with getUser(id) + */ + public List getActivity() { + return activity; + } + + /** + * You can obtain it with getUser(id) + */ + public void setActivity(List activity) { + this.activity = activity; + } + + /** + * i.e. "admin" + */ + public String getCapacity() { + return capacity; + } + + /** + * i.e. "admin" + */ + public void setCapacity(@Nullable String capacity) { + this.capacity = capacity; + } + + public Integer getNumFollowers() { + return numFollowers; + } + + public void setNumFollowers(@Nullable Integer numFollowers) { + this.numFollowers = numFollowers; + } +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanUserBase.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanUserBase.java new file mode 100644 index 0000000..0b80eed --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanUserBase.java @@ -0,0 +1,159 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import javax.annotation.Nullable; + +/* + * A Ckan User. Hopefully a happy one. + * + * {@link CkanUserBase} holds fields that can be sent when + * creating + * a user,, while {@link CkanUser} holds more fields that can be returned + * with searches. + * + * This class initializes nothing to fully preserve all we get from ckan. In + * practice, all fields of retrieved resources can be null except maybe + * {@code name}. + * @since 0.4.1 + */ +public class CkanUserBase { + + private String about; + private String fullname; + private String email; + private String id; + private String name; + private String openid; + private String password; + + public CkanUserBase() { + } + + /** + * Constructor with the minimal amount of fields required for a successful + * creation. + * + * @param name the name of the new user, a string between 2 and 100 + * characters in length, containing only lowercase alphanumeric characters, + * - and _ + */ + public CkanUserBase(String name, String email, String password) { + this(); + this.email = email; + this.name = name; + this.password = password; + } + + /** + * A description of the new user + */ + public String getAbout() { + return about; + } + + /** + * A description of the new user + */ + public void setAbout(@Nullable String about) { + this.about = about; + } + + /** + * Only used when creating the user. + */ + public String getEmail() { + return email; + } + + /** + * Only used when creating the user. + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Seems quite useless something like i.e. Mr David Leoni the Third ? + */ + public String getFullname() { + return fullname; + } + + /** + * Seems quite useless something like i.e. Mr David Leoni the Third ? + */ + public void setFullname(@Nullable String fullname) { + this.fullname = fullname; + } + + /** + * Alphanumerical id. i.e. "01ab5c4e-6d6b-46bc-8cn7-e37drs9aeb00" + */ + public String getId() { + return id; + } + + /** + * Alphanumerical id. i.e. "01ab5c4e-6d6b-46bc-8cn7-e37drs9aeb00" + */ + public void setId(String id) { + this.id = id; + } + + /** + * The name of the new user, a string between 2 and 100 characters in + * length, containing only lowercase alphanumeric characters, - and _ (i.e. + * david_leoni) + */ + public String getName() { + return name; + } + + /** + * The name of the new user, a string between 2 and 100 characters in + * length, containing only lowercase alphanumeric characters, - and _ (i.e. + * david_leoni) + */ + public void setName(String name) { + this.name = name; + } + + public String getOpenid() { + return openid; + } + + public void setOpenid(@Nullable String openid) { + this.openid = openid; + } + + /** + * The password of the new user, a string of at least 4 characters. Only + * available when creating the user. + */ + public String getPassword() { + return password; + } + + /** + * The password of the new user, a string of at least 4 characters. Only + * available when creating the user. + */ + public void setPassword(String password) { + this.password = password; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanVocabulary.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanVocabulary.java new file mode 100644 index 0000000..0314917 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanVocabulary.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.util.List; + +/** + * + * @author David Leoni + * @since 0.4.1 + */ +public class CkanVocabulary extends CkanVocabularyBase { + + + public CkanVocabulary() { + super(); + } + + /** + * Constructor with required fields for vocabulary creation. + * @param name the name of the new vocabulary, e.g. 'Genre' + * @param tags + */ + public CkanVocabulary(String name, List tags) { + super(name, tags); + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanVocabularyBase.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanVocabularyBase.java new file mode 100644 index 0000000..be9a958 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/CkanVocabularyBase.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model; + +import java.util.List; + +/** + * + * @author David Leoni + * @since 0.4.1 + */ +public class CkanVocabularyBase { + + private String id; + private String name; + private List tags; + + public CkanVocabularyBase() { + } + + /** + * Constructor with required fields for vocabulary creation. + * @param name the name of the new vocabulary, e.g. 'Genre' + * @param tags + */ + public CkanVocabularyBase(String name, List tags) { + this(); + this.name = name; + this.tags = tags; + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * The name of the new vocabulary, e.g. 'Genre' + */ + public String getName() { + return name; + } + + /** + * The name of the new vocabulary, e.g. 'Genre' + */ + public void setName(String name) { + this.name = name; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanAuthorizationException.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanAuthorizationException.java new file mode 100644 index 0000000..809600e --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanAuthorizationException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions; + +import org.gcube.datacatalogue.ckanutillibrary.jackan.CkanClient; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResponse; + +/** + * Exception raised when the user is not authorized to call the action. + * + * @author David Leoni + * @since 0.4.3 + */ +public class CkanAuthorizationException extends CkanException { + + public CkanAuthorizationException(String msg, CkanClient client, Throwable ex) { + super(msg, client, ex); + } + + public CkanAuthorizationException(String msg, CkanClient client) { + super(msg, client); + } + + public CkanAuthorizationException(String msg, CkanResponse ckanResponse, CkanClient client) { + super(msg, ckanResponse, client); + } + + public CkanAuthorizationException(String msg, CkanResponse ckanResponse, CkanClient client, Throwable ex) { + super(msg, ckanResponse, client, ex); + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanException.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanException.java new file mode 100644 index 0000000..f41497c --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanException.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions; + + +import javax.annotation.Nullable; + +import org.gcube.datacatalogue.ckanutillibrary.jackan.CkanClient; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResponse; + +/** + * Runtime exception for ckan-related problems. For possible cases description, see {@link eu.trentorise.opendata.jackan.model.CkanError CkanError} + * + * @author David Leoni + */ +public class CkanException extends JackanException { + + + @Nullable + private CkanResponse ckanResponse = null; + @Nullable + private CkanClient ckanClient = null; + + private static String makeMessage(String msg, @Nullable CkanResponse ckanResponse, @Nullable CkanClient client) { + return msg + " " + + (ckanResponse != null ? ckanResponse + " " : "") + + (client != null ? client : ""); + } + + + public CkanException(String msg, CkanClient client) { + super(makeMessage(msg, null, client)); + this.ckanClient = client; + } + + public CkanException(String msg, CkanResponse ckanResponse, CkanClient client) { + super(makeMessage(msg, ckanResponse, client)); + this.ckanResponse = ckanResponse; + this.ckanClient = client; + } + + public CkanException(String msg, CkanClient client, Throwable ex) { + this(msg, null, client, ex); + } + + public CkanException(String msg, CkanResponse ckanResponse, CkanClient client, Throwable ex) { + super(makeMessage(msg, ckanResponse, client), + ex); + this.ckanResponse = ckanResponse; + this.ckanClient = client; + } + + @Nullable + public CkanResponse getCkanResponse() { + return ckanResponse; + } + + @Nullable + public CkanClient getCkanClient() { + return ckanClient; + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanNotFoundException.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanNotFoundException.java new file mode 100644 index 0000000..d2b5e63 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanNotFoundException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions; + +import org.gcube.datacatalogue.ckanutillibrary.jackan.CkanClient; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResponse; + +/** + * Thrown when Ckan tells us something was not found. Notice this is not related + * to more generic {@link JackanNotFoundException} + * + * @author David Leoni + */ +public class CkanNotFoundException extends CkanException { + + public CkanNotFoundException(String msg, CkanResponse ckanResponse, CkanClient client) { + super(msg, ckanResponse, client); + } + + public CkanNotFoundException(String msg, CkanResponse ckanResponse, CkanClient client, Throwable ex) { + super(msg, ckanResponse, client, ex); + } + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanValidationException.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanValidationException.java new file mode 100644 index 0000000..7e37c2c --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/CkanValidationException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions; + +import org.gcube.datacatalogue.ckanutillibrary.jackan.CkanClient; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanResponse; + +/** + * Thrown when Ckan tells us something was not in the proper format. + * + * @author David Leoni + */ +public class CkanValidationException extends CkanException { + + + public CkanValidationException(String msg, CkanClient client, Throwable ex) { + super(msg, client, ex); + } + + public CkanValidationException(String msg, CkanClient client) { + super(msg, client); + } + + public CkanValidationException(String msg, CkanResponse ckanResponse, CkanClient client) { + super(msg, ckanResponse, client); + } + + public CkanValidationException(String msg, CkanResponse ckanResponse, CkanClient client, Throwable ex) { + super(msg, ckanResponse, client, ex); + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/JackanException.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/JackanException.java new file mode 100644 index 0000000..d66f1df --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/JackanException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 Trento Rise (trentorise.eu) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions; + + +/** + * Generic Jackan Runtime Exception. + * + * @author David Leoni + */ +public class JackanException extends RuntimeException { + + public JackanException(String msg) { + super(msg); + } + + public JackanException(String msg, Throwable ex) { + super(msg, ex); + } + + +} diff --git a/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/JackanNotFoundException.java b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/JackanNotFoundException.java new file mode 100644 index 0000000..9fde660 --- /dev/null +++ b/src/main/java/org/gcube/datacatalogue/ckanutillibrary/shared/jackan/model/exceptions/JackanNotFoundException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 Trento Rise. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.exceptions; + +/** + * A runtime exception to raise when something is not found. Note this is not + * related to ckan specific {@link CkanNotFoundException} + * + * @author David Leoni + */ +public class JackanNotFoundException extends JackanException { + + /** + * Creates the JackanNotFoundException using the provided message + */ + public JackanNotFoundException(String msg) { + super(msg); + } + + /** + * Creates the JackanNotFoundException using the provided message and throwable + */ + public JackanNotFoundException(String msg, Throwable tr) { + super(msg, tr); + } + +} diff --git a/src/test/java/org/gcube/datacatalogue/ckanutillibrary/test/TestDataCatalogueLib.java b/src/test/java/org/gcube/datacatalogue/ckanutillibrary/test/TestDataCatalogueLib.java index 4a3a201..706045c 100644 --- a/src/test/java/org/gcube/datacatalogue/ckanutillibrary/test/TestDataCatalogueLib.java +++ b/src/test/java/org/gcube/datacatalogue/ckanutillibrary/test/TestDataCatalogueLib.java @@ -14,13 +14,15 @@ import org.gcube.datacatalogue.ckanutillibrary.server.ApplicationProfileScopePer import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueFactory; import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueImpl; import org.gcube.datacatalogue.ckanutillibrary.shared.ResourceBean; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanDataset; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanGroup; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanOrganization; +import org.gcube.datacatalogue.ckanutillibrary.shared.jackan.model.CkanUser; import org.junit.Before; import org.junit.Test; import org.slf4j.LoggerFactory; -import eu.trentorise.opendata.jackan.model.CkanGroup; -import eu.trentorise.opendata.jackan.model.CkanOrganization; -import eu.trentorise.opendata.jackan.model.CkanUser; + /** * The Class TestDataCatalogueLib. @@ -87,7 +89,7 @@ public class TestDataCatalogueLib { public void getScopePerUrl(){ ScopeProvider.instance.set(scope); - String url = "https://dev4.d4science.org/group/devvre/ckan"; + String url = "https://dev2.d4science.org/group/devvre/ckan"; String scopeToUse = ApplicationProfileScopePerUrlReader.getScopePerUrl(url); LOG.debug("Retrieved scope is " + scopeToUse); @@ -222,8 +224,8 @@ public class TestDataCatalogueLib { ScopeProvider.instance.set(scope); SecurityTokenProvider.instance.set(authorizationToken); - DataCatalogueImpl instance = factory.getUtilsPerScope(scope); + DataCatalogueImpl instance = factory.getUtilsPerScope(scope); String licenseId = instance.getLicenses().get(0).getId(); Map> customFieldsMultiple = new HashMap>(); @@ -236,6 +238,8 @@ public class TestDataCatalogueLib { customFieldsMultiple.put("key-random-"+new Random().nextInt(10), values); } + customFieldsMultiple.put("empty-key", Arrays.asList("")); + customFieldsMultiple.put("system:type", Arrays.asList("EmptyProfile")); boolean setSearchable = true; @@ -268,7 +272,7 @@ public class TestDataCatalogueLib { setSearchable, true); - LOG.info(createdDataset); + LOG.info("Created the dataset with id: " +createdDataset); }catch (Exception e) { e.printStackTrace(); } @@ -277,8 +281,10 @@ public class TestDataCatalogueLib { //@Test public void createGroup() throws Exception{ + ScopeProvider.instance.set(scope); SecurityTokenProvider.instance.set(authorizationToken); + DataCatalogueImpl instance = factory.getUtilsPerScope(scope); int random = new Random().nextInt(); String groupTitle = "a grop created by catalogue-util-library "+random; @@ -286,6 +292,22 @@ public class TestDataCatalogueLib { CkanGroup ckanGroup = instance.createGroup(groupName, groupTitle, "description"); LOG.info("Created the group: "+ckanGroup); } + + @Test + public void testAddResource() throws Exception{ + + ScopeProvider.instance.set(scope); + SecurityTokenProvider.instance.set(authorizationToken); + + DataCatalogueImpl instance = factory.getUtilsPerScope(scope); + CkanDataset dataset = instance.getDataset("adatasetcreatedbycatalogue-util-library-873805063", null); + + for (int i = 0; i < 1; i++) { + ResourceBean resourceBean = new ResourceBean("https://google.com", "resouce "+i, "description "+i, null, testUser, dataset.getId(), null); + instance.addResourceToDataset(resourceBean); + } + } + @@ -436,7 +458,7 @@ public class TestDataCatalogueLib { // DataCatalogueImpl instance = factory.getUtilsPerScope(scope); // // String username = "francescomangiacrapa"; -// List organizations = instance.getOrganizationsByUser(username); +// List organizations = instance.getOrganizationsByUser(username); // // System.out.println("organizations for user " + username + " are: "); // @@ -576,27 +598,7 @@ public class TestDataCatalogueLib { // // } // -// //@Test -// public void testAddResource() throws Exception{ -// -// DataCatalogueImpl instance = factory.getUtilsPerScope(scope); -// String datasetId = "test_publish_folder_15_44"; -// //instance.assignDatasetToGroup(groupName, datasetId, instance.getApiKeyFromUsername("costantino_perciante")); -// -// String api = instance.getApiKeyFromUsername("costantino_perciante"); -// CheckedCkanClient client = new CheckedCkanClient(instance.getCatalogueUrl(), api); -// List randomName = Arrays.asList("FIRMS", "RAM", "FishSource"); -// for (int i = 0; i < 100; i++) { -// -// CkanResource resource = new CkanResource("https://goo.gl/FH5AQ5", datasetId); -// String name = randomName.get((int)Math.round(Math.ceil(Math.random() * 3))); -// -// resource.setName(name); -// client.createResource(resource); -// -// } -// } -// + // //@Test // public void checkGroupRole() throws Exception{ //