From 143a62d32106ae49271965578943c40b16c6bf9a Mon Sep 17 00:00:00 2001 From: Francois Prunayre Date: Fri, 20 Jan 2023 07:49:25 +0100 Subject: [PATCH] Harvester / URL / Add RDF DCAT harvester. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The simpleurl harvester can already point to JSON or XML feed. It can also point to a RDF DCAT feed which will be loaded using Jena. SPARQL queries are applied to extract necessary information from the RDF graph. This work was initially made by GIM team for Metadata vlaanderen in a DCAT-AP dedicated harvester (see https://github.com/metadata101/dcat-ap1.1/tree/master/src/main/java/org/fao/geonet/kernel/harvest/harvester/dcatap) but we considered that the simpleurl harvester can be a good candidate for simplification and provide DCAT feed support directly. The results can be converted using an XSL conversion. A conversion to ISO19115-3 is provided and custom plugins may provide other conversions. The provided ISO19115-3 conversion support only Dataset and cover most of the mapping done in OGC API record (see https://github.com/geonetwork/geonetwork-microservices/blob/main/modules/library/common-index-model/src/main/java/org/fao/geonet/index/converter/DcatConverter.java#L188) Tested with * http://mow-dataroom.s3-eu-west-1.amazonaws.com/dr_dcat.rdf * https://apps.titellus.net/geonetwork/api/collections/main/items?q=AlpenKonvention&f=dcat * https://apps.titellus.net/geonetwork/api/collections/main/items/7bb33d95-7950-499a-9bd8-6f31d58b0b35?f=dcat Other actions: - [ ] Add possibility to hash or not URI used for UUID (depends on https://github.com/geonetwork/core-geonetwork/pull/5736) - [ ] UI / Based on type of harvesting hide uneeded options eg. for a DCAT feed, only the URL is really necessary - [ ] Paging support for RDF feeds ? - [ ] Conversion / We could move them to schema to not to have to copy them in webapp/xsl/conversion folder. They would be grouped by schema which could also make the choice easier for end users Co-authored-by: Mathieu Chaussier Co-authored-by: Gustaaf Van de Boel Co-authored-by: Stijn Goedertier --- .../main/java/org/fao/geonet/utils/Xml.java | 20 + .../java/org/fao/geonet/utils/XmlTest.java | 13 +- .../java/org/fao/geonet/util/XslUtil.java | 16 + harvesters/pom.xml | 5 + .../{simpleUrl => simpleurl}/Aligner.java | 2 +- .../{simpleUrl => simpleurl}/Harvester.java | 265 ++-- .../harvest/harvester/simpleurl/RDFUtils.java | 228 +++ .../SimpleUrlHarvester.java | 14 +- .../SimpleUrlParams.java | 14 +- .../SimpleUrlResourceType.java | 2 +- .../resources/config-spring-geonetwork.xml | 2 +- .../simpleUrl/sparql/add-CatalogRecord.rq | 19 + .../simpleUrl/sparql/build-record.rq | 94 ++ .../simpleUrl/sparql/extract-records-ids.rq | 21 + .../sparql/extract-resources-no-records.rq | 41 + .../simpleUrl/sparql/fix-blank-node.rq | 46 + .../HarvesterTest.java | 2 +- .../harvester/simpleurl/RDFUtilsTest.java | 19 + .../javax.xml.transform.TransformerFactory | 1 + .../harvester/simpleurl/dcat-feed-mow.rdf | 1246 +++++++++++++++++ .../simpleurl/ogcapirecords-dcat-output.rdf | 116 ++ pom.xml | 6 + .../convert/DCAT/sparql-to-iso19115-3.xsl | 671 +++++++++ .../iso19115-3.2018/extract-date-modified.xsl | 6 +- .../admin/harvest/type/simpleurl.html | 5 +- web/src/main/webapp/WEB-INF/oasis-catalog.xml | 3 + .../import/SPARQL-DCAT-to-ISO19115-3-2018.xsl | 6 + .../webapp/xslt/common/functions-sparql.xsl | 47 + 28 files changed, 2795 insertions(+), 135 deletions(-) rename harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/{simpleUrl => simpleurl}/Aligner.java (99%) rename harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/{simpleUrl => simpleurl}/Harvester.java (59%) create mode 100644 harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/RDFUtils.java rename harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/{simpleUrl => simpleurl}/SimpleUrlHarvester.java (83%) rename harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/{simpleUrl => simpleurl}/SimpleUrlParams.java (89%) rename harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/{simpleUrl => simpleurl}/SimpleUrlResourceType.java (53%) create mode 100644 harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/add-CatalogRecord.rq create mode 100644 harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/build-record.rq create mode 100644 harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/extract-records-ids.rq create mode 100644 harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/extract-resources-no-records.rq create mode 100644 harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/fix-blank-node.rq rename harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/{simpleUrl => simpleurl}/HarvesterTest.java (97%) create mode 100644 harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/RDFUtilsTest.java create mode 100644 harvesters/src/test/resources/META-INF/services/javax.xml.transform.TransformerFactory create mode 100644 harvesters/src/test/resources/org/fao/geonet/kernel/harvest/harvester/simpleurl/dcat-feed-mow.rdf create mode 100644 harvesters/src/test/resources/org/fao/geonet/kernel/harvest/harvester/simpleurl/ogcapirecords-dcat-output.rdf create mode 100644 schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/DCAT/sparql-to-iso19115-3.xsl create mode 100644 web/src/main/webapp/xsl/conversion/import/SPARQL-DCAT-to-ISO19115-3-2018.xsl create mode 100644 web/src/main/webapp/xslt/common/functions-sparql.xsl diff --git a/common/src/main/java/org/fao/geonet/utils/Xml.java b/common/src/main/java/org/fao/geonet/utils/Xml.java index 59636fc9c9e4..24095054f9c1 100644 --- a/common/src/main/java/org/fao/geonet/utils/Xml.java +++ b/common/src/main/java/org/fao/geonet/utils/Xml.java @@ -135,6 +135,7 @@ public final class Xml { + "\uE000-\uFFFD" + "\ud800\udc00-\udbff\udfff" + "]"; + public static final String XML_VERSION_HEADER = "<\\?xml version='1.0' encoding='.*'\\?>\\s*"; public static SAXBuilder getSAXBuilder(boolean validate) { SAXBuilder builder = getSAXBuilderWithPathXMLResolver(validate, null); @@ -1128,6 +1129,8 @@ public static boolean isXMLLike(String inXMLStr) { Pattern pattern; Matcher matcher; + inXMLStr = inXMLStr.replaceFirst(XML_VERSION_HEADER, ""); + // Regular expression to see if it starts and ends with the same element or // it's a self-closing element. final String XML_PATTERN_STR = "<(\\S+?)(.*?)>(.*?)|<(\\S+?)(.*?)/>"; @@ -1146,6 +1149,23 @@ public static boolean isXMLLike(String inXMLStr) { return retBool; } + /** + * Check if is XML and the first tag local name + * is rdf or something like a DCAT feed. + */ + public static boolean isRDFLike(String inXMLStr) { + boolean retBool = false; + if (isXMLLike(inXMLStr)) { + String xml = inXMLStr.replaceFirst(XML_VERSION_HEADER, ""), + firstTag = xml + .substring(0, xml.indexOf(" ")) + .toLowerCase(); + retBool = firstTag.matches("<.*:(rdf|catalog|catalogrecord)"); + } + return retBool; + } + + private static class JeevesURIResolver implements URIResolver { /** diff --git a/common/src/test/java/org/fao/geonet/utils/XmlTest.java b/common/src/test/java/org/fao/geonet/utils/XmlTest.java index 9ca1667ef7aa..da5a73e96c45 100644 --- a/common/src/test/java/org/fao/geonet/utils/XmlTest.java +++ b/common/src/test/java/org/fao/geonet/utils/XmlTest.java @@ -192,5 +192,16 @@ public void testGetXPathExprAttribute() throws Exception { assertSame(attribute, actual.get(0)); } - + @Test + public void testIsXmlLike() { + assertEquals(true, + Xml.isXMLLike("")); + assertEquals(true, + Xml.isXMLLike("")); + assertEquals(true, + Xml.isXMLLike("\n")); + assertEquals(true, + Xml.isRDFLike("\n")); + } } diff --git a/core/src/main/java/org/fao/geonet/util/XslUtil.java b/core/src/main/java/org/fao/geonet/util/XslUtil.java index a3ececa61de2..3226ecabd46a 100644 --- a/core/src/main/java/org/fao/geonet/util/XslUtil.java +++ b/core/src/main/java/org/fao/geonet/util/XslUtil.java @@ -761,6 +761,22 @@ public static String wktGeomToBbox(Object WKT) throws Exception { return ret; } + public static String geoJsonGeomToBbox(Object WKT) throws Exception { + String ret = ""; + try { + Geometry geometry = new GeometryJSON().read(WKT); + if (geometry != null) { + final Envelope envelope = geometry.getEnvelopeInternal(); + return + String.format("%f|%f|%f|%f", + envelope.getMinX(), envelope.getMinY(), + envelope.getMaxX(), envelope.getMaxY()); + } + } catch (Throwable e) { + } + return ret; + } + /** * Get field value for metadata identified by uuid. * diff --git a/harvesters/pom.xml b/harvesters/pom.xml index 0004adcbc48c..e8c4bf752a07 100644 --- a/harvesters/pom.xml +++ b/harvesters/pom.xml @@ -49,6 +49,11 @@ com.github.lookfirst sardine + + org.apache.jena + apache-jena-libs + pom + org.jsoup jsoup diff --git a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/Aligner.java b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/Aligner.java similarity index 99% rename from harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/Aligner.java rename to harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/Aligner.java index 1197288343e5..a093756e6cc5 100644 --- a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/Aligner.java +++ b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/Aligner.java @@ -21,7 +21,7 @@ //=== Rome - Italy. email: geonetwork@osgeo.org //============================================================================== -package org.fao.geonet.kernel.harvest.harvester.simpleUrl; +package org.fao.geonet.kernel.harvest.harvester.simpleurl; import jeeves.server.context.ServiceContext; import org.fao.geonet.GeonetContext; diff --git a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/Harvester.java b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/Harvester.java similarity index 59% rename from harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/Harvester.java rename to harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/Harvester.java index 49fc11e0d3a6..95a4234e4d5c 100644 --- a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/Harvester.java +++ b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/Harvester.java @@ -21,9 +21,8 @@ //=== Rome - Italy. email: geonetwork@osgeo.org //============================================================================== -package org.fao.geonet.kernel.harvest.harvester.simpleUrl; +package org.fao.geonet.kernel.harvest.harvester.simpleurl; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; @@ -39,6 +38,7 @@ import org.fao.geonet.kernel.harvest.harvester.HarvestResult; import org.fao.geonet.kernel.harvest.harvester.IHarvester; import org.fao.geonet.lib.Lib; +import org.fao.geonet.util.Sha1Encoder; import org.fao.geonet.utils.GeonetHttpRequestFactory; import org.fao.geonet.utils.Log; import org.fao.geonet.utils.Xml; @@ -46,35 +46,31 @@ import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.Text; -import org.json.JSONException; import org.json.JSONObject; import org.json.XML; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.client.ClientHttpResponse; -import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; +import static org.fao.geonet.utils.Xml.isRDFLike; import static org.fao.geonet.utils.Xml.isXMLLike; /** - * Harvest metadata from a JSON source. + * Harvest metadata from a URL source. *

- * The JSON source can be a simple JSON file or + * The URL source can be a simple JSON, XML or RDF file or * an URL with indication on how to pass paging information. - * - * This harvester has been tested with CKAN search API. + *

+ * This harvester has been tested with CKAN, OpenDataSoft, + * OGC API Records, DCAT feeds. */ class Harvester implements IHarvester { public static final String LOGGER_NAME = "geonetwork.harvester.simpleurl"; @@ -90,7 +86,7 @@ class Harvester implements IHarvester { /** * Contains a list of accumulated errors during the executing of this harvest. */ - private List errors = new LinkedList(); + private List errors = new LinkedList<>(); public Harvester(AtomicBoolean cancelMonitor, Logger log, ServiceContext context, SimpleUrlParams params) { this.cancelMonitor = cancelMonitor; @@ -111,7 +107,7 @@ public HarvestResult harvest(Logger log) throws Exception { for (String url : urlList) { log.debug("Loading URL: " + url); - String content = retrieveUrl(url, log); + String content = retrieveUrl(url); if (cancelMonitor.get()) { return new HarvestResult(); } @@ -122,14 +118,20 @@ public HarvestResult harvest(Logger log) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonObj = null; Element xmlObj = null; - SimpleUrlResourceType type = content.startsWith(" allUuids = new HashMap(); + Map allUuids = new HashMap<>(); try { - Map uuids = new HashMap(); + Map uuids = new HashMap<>(); List listOfUrlForPages = buildListOfUrl(params, numberOfRecordsToHarvest); for (int i = 0; i < listOfUrlForPages.size(); i++) { if (i != 0) { - content = retrieveUrl(listOfUrlForPages.get(i), log); + content = retrieveUrl(listOfUrlForPages.get(i)); if (type == SimpleUrlResourceType.XML) { xmlObj = Xml.loadString(content, false); } else { jsonObj = objectMapper.readTree(content); } } - JsonNode nodes = null; - List xmlNodes = null; - if (StringUtils.isNotEmpty(params.loopElement)) { + if (StringUtils.isNotEmpty(params.loopElement) + || type == SimpleUrlResourceType.RDFXML) { try { if (type == SimpleUrlResourceType.XML) { - xmlNodes = Xml.selectNodes(xmlObj, params.loopElement, xmlObj.getAdditionalNamespaces()); - log.debug(String.format("%d records found in XML response.", xmlNodes.size())); - } else { - nodes = jsonObj.at(params.loopElement); - log.debug(String.format("%d records found in JSON response.", nodes.size())); - } - - - if (type == SimpleUrlResourceType.XML && xmlNodes != null) { - xmlNodes.forEach(record -> { - String uuid = - null; - try { - uuid = getXmlElementTextValue(Xml.selectSingle(record, params.recordIdPath, record.getAdditionalNamespaces())); - uuids.put(uuid, record); - } catch (JDOMException e) { - log.error(String.format("Failed to extract UUID for record. Error is %s.", - e.getMessage())); - aligner.getResult().badFormat ++; - aligner.getResult().totalMetadata ++; - } - }); - } else if (nodes != null) { - nodes.forEach(record -> { - String uuid = null; - try { - uuid = this.extractUuidFromIdentifier(record.at(params.recordIdPath).asText()); - } catch (Exception e) { - log.error(String.format("Failed to collect record UUID at path %s. Error is: %s", - params.recordIdPath, e.getMessage())); - } - String apiUrlPath = params.url.split("\\?")[0]; - URL apiUrl = null; - try { - apiUrl = new URL(apiUrlPath); - String nodeUrl = new StringBuilder(apiUrl.getProtocol()).append("://").append(apiUrl.getAuthority()).toString(); - Element xml = convertRecordToXml(record, uuid, apiUrlPath, nodeUrl); - uuids.put(uuid, xml); - } catch (MalformedURLException e) { - log.warning(String.format("Failed to parse JSON source URL. Error is: %s", e.getMessage())); - } - }); + collectRecordsFromXml(xmlObj, uuids, aligner); + } else if (type == SimpleUrlResourceType.RDFXML) { + collectRecordsFromRdf(xmlObj, uuids, aligner); + } else if (type == SimpleUrlResourceType.JSON) { + collectRecordsFromJson(jsonObj, uuids, aligner); } aligner.align(uuids, errors); allUuids.putAll(uuids); } catch (Exception e) { + errors.add(new HarvestError(this.context, e)); log.error(String.format("Failed to collect record in response at path %s. Error is: %s", params.loopElement, e.getMessage())); } @@ -238,6 +204,92 @@ public HarvestResult harvest(Logger log) throws Exception { return aligner.getResult(); } + private void collectRecordsFromJson(JsonNode jsonObj, + Map uuids, + Aligner aligner) { + JsonNode nodes = jsonObj.at(params.loopElement); + log.debug(String.format("%d records found in JSON response.", nodes.size())); + + nodes.forEach(jsonRecord -> { + String uuid = null; + try { + uuid = this.extractUuidFromIdentifier(jsonRecord.at(params.recordIdPath).asText()); + } catch (Exception e) { + log.error(String.format("Failed to collect record UUID at path %s. Error is: %s", + params.recordIdPath, e.getMessage())); + } + String apiUrlPath = params.url.split("\\?")[0]; + URL apiUrl = null; + try { + apiUrl = new URL(apiUrlPath); + String nodeUrl = new StringBuilder(apiUrl.getProtocol()).append("://").append(apiUrl.getAuthority()).toString(); + Element xml = convertJsonRecordToXml(jsonRecord, uuid, apiUrlPath, nodeUrl); + uuids.put(uuid, xml); + } catch (MalformedURLException e) { + errors.add(new HarvestError(this.context, e)); + log.warning(String.format("Failed to parse JSON source URL. Error is: %s", e.getMessage())); + } + }); + } + + private void collectRecordsFromRdf(Element xmlObj, + Map uuids, + Aligner aligner) { + Map rdfNodes = null; + try { + rdfNodes = RDFUtils.getAllUuids(xmlObj); + } catch (Exception e) { + errors.add(new HarvestError(this.context, e)); + log.error(String.format("Failed to find records in RDF graph. Error is: %s", + e.getMessage())); + } + if (rdfNodes != null) { + log.debug(String.format("%d records found in RDFXML response.", rdfNodes.size())); + + // TODO: Add param + boolean hashUuid = true; + rdfNodes.forEach((uuid, xml) -> { + if (hashUuid) { + uuid = Sha1Encoder.encodeString(uuid); + } + Element output = applyConversion(xml, uuid); + if (output != null) { + uuids.put(uuid, output); + } + }); + } + } + + private void collectRecordsFromXml(Element xmlObj, + Map uuids, + Aligner aligner) { + List xmlNodes = null; + try { + xmlNodes = Xml.selectNodes(xmlObj, params.loopElement, xmlObj.getAdditionalNamespaces()); + } catch (JDOMException e) { + log.error(String.format("Failed to query records using %s. Error is: %s", + params.loopElement, e.getMessage())); + } + + if (xmlNodes != null) { + log.debug(String.format("%d records found in XML response.", xmlNodes.size())); + + xmlNodes.forEach(element -> { + String uuid = + null; + try { + uuid = getXmlElementTextValue(Xml.selectSingle(element, params.recordIdPath, element.getAdditionalNamespaces())); + uuids.put(uuid, applyConversion(element, null)); + } catch (JDOMException e) { + log.error(String.format("Failed to extract UUID for record. Error is %s.", + e.getMessage())); + aligner.getResult().badFormat++; + aligner.getResult().totalMetadata++; + } + }); + } + } + private String getXmlElementTextValue(Object element) { String s = null; if (element instanceof Text) { @@ -250,7 +302,7 @@ private String getXmlElementTextValue(Object element) { return s; } - private String extractUuidFromIdentifier(final String identifier ) { + private String extractUuidFromIdentifier(final String identifier) { String uuid = identifier; if (Lib.net.isUrlValid(uuid)) { uuid = uuid.replaceFirst(".*/([^/?]+).*", "$1"); @@ -260,7 +312,7 @@ private String extractUuidFromIdentifier(final String identifier ) { @VisibleForTesting protected List buildListOfUrl(SimpleUrlParams params, int numberOfRecordsToHarvest) { - List urlList = new ArrayList(); + List urlList = new ArrayList<>(); if (StringUtils.isEmpty(params.pageSizeParam)) { urlList.add(params.url); return urlList; @@ -272,8 +324,8 @@ protected List buildListOfUrl(SimpleUrlParams params, int numberOfRecord numberOfRecordsPerPage = Integer.parseInt(pageSizeParamValue); } else { log.warning(String.format( - "Page size param '%s' not found or is not a numeric in URL '%s'. Can't build a list of pages.", - params.pageSizeParam, params.url)); + "Page size param '%s' not found or is not a numeric in URL '%s'. Can't build a list of pages.", + params.pageSizeParam, params.url)); urlList.add(params.url); return urlList; } @@ -284,67 +336,76 @@ protected List buildListOfUrl(SimpleUrlParams params, int numberOfRecord startAtZero = Integer.parseInt(pageFromParamValue) == 0; } else { log.warning(String.format( - "Page from param '%s' not found or is not a numeric in URL '%s'. Can't build a list of pages.", - params.pageFromParam, params.url)); + "Page from param '%s' not found or is not a numeric in URL '%s'. Can't build a list of pages.", + params.pageFromParam, params.url)); urlList.add(params.url); return urlList; } - int numberOfPages = (int) Math.abs((numberOfRecordsToHarvest + (startAtZero ? -1 : 0)) / numberOfRecordsPerPage) + 1; + int numberOfPages = Math.abs((numberOfRecordsToHarvest + (startAtZero ? -1 : 0)) / numberOfRecordsPerPage) + 1; for (int i = 0; i < numberOfPages; i++) { int from = i * numberOfRecordsPerPage + (startAtZero ? 0 : 1); int size = i == numberOfPages - 1 ? // Last page - numberOfRecordsToHarvest - from + (startAtZero ? 0 : 1) : - numberOfRecordsPerPage; + numberOfRecordsToHarvest - from + (startAtZero ? 0 : 1) : + numberOfRecordsPerPage; String url = params.url - .replaceAll(params.pageFromParam + "=[0-9]+", params.pageFromParam + "=" + from) - .replaceAll(params.pageSizeParam + "=[0-9]+", params.pageSizeParam + "=" + size); + .replaceAll(params.pageFromParam + "=[0-9]+", params.pageFromParam + "=" + from) + .replaceAll(params.pageSizeParam + "=[0-9]+", params.pageSizeParam + "=" + size); urlList.add(url); } return urlList; } - private Element convertRecordToXml(JsonNode record, String uuid, String apiUrl, String nodeUrl) { + private Element convertJsonRecordToXml(JsonNode jsonRecord, String uuid, String apiUrl, String nodeUrl) { ObjectMapper objectMapper = new ObjectMapper(); try { String recordAsXml = XML.toString( - new JSONObject( - objectMapper.writeValueAsString(record)), "record"); + new JSONObject( + objectMapper.writeValueAsString(jsonRecord)), "record"); recordAsXml = Xml.stripNonValidXMLCharacters(recordAsXml) - .replace("<@", "<") - .replace("]*<)", "_"); // this removes colon from property names + .replace("<@", "<") + .replace("]*<)", "_"); // this removes colon from property names Element recordAsElement = Xml.loadString(recordAsXml, false); recordAsElement.addContent(new Element("uuid").setText(uuid)); recordAsElement.addContent(new Element("apiUrl").setText(apiUrl)); recordAsElement.addContent(new Element("nodeUrl").setText(nodeUrl)); - Path importXsl = context.getAppPath().resolve(Geonet.Path.IMPORT_STYLESHEETS); - final Path xslPath = importXsl.resolve(params.toISOConversion + ".xsl"); - return Xml.transform(recordAsElement, xslPath); - } catch (JSONException e) { - e.printStackTrace(); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } catch (JDOMException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + return applyConversion(recordAsElement, null); } catch (Exception e) { - e.printStackTrace(); + log.error(String.format("Failed to convert JSON record %s to XML. Error is: %s", + uuid, e.getMessage())); } return null; } + private Element applyConversion(Element input, String uuid) { + if (StringUtils.isNotEmpty(params.toISOConversion)) { + Path importXsl = context.getAppPath().resolve(Geonet.Path.IMPORT_STYLESHEETS); + final Path xslPath = importXsl.resolve(params.toISOConversion + ".xsl"); + try { + HashMap xslParams = new HashMap<>(); + if (uuid != null) { + xslParams.put("uuid", uuid); + } + return Xml.transform(input, xslPath, xslParams); + } catch (Exception e) { + errors.add(new HarvestError(this.context, e)); + log.error(String.format("Failed to apply conversion %s to record %s. Error is: %s", + params.toISOConversion, uuid, e.getMessage())); + return null; + } + } else { + return input; + } + } + /** - * Does CSW GetCapabilities request and check that operations needed for harvesting (ie. - * GetRecords and GetRecordById) are available in remote node. - * - * @return + * Read the response of the URL. */ - private String retrieveUrl(String url, Logger log) throws Exception { + private String retrieveUrl(String url) throws Exception { if (!Lib.net.isUrlValid(url)) throw new BadParameterEx("Invalid URL", url); HttpGet httpMethod = null; @@ -365,11 +426,9 @@ private String retrieveUrl(String url, Logger log) throws Exception { } private URI createUrl(String jsonUrl) throws URISyntaxException { - // TODO: Add paging and loop return new URI(jsonUrl); } - public List getErrors() { return errors; } diff --git a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/RDFUtils.java b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/RDFUtils.java new file mode 100644 index 000000000000..b20011678d24 --- /dev/null +++ b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/RDFUtils.java @@ -0,0 +1,228 @@ +package org.fao.geonet.kernel.harvest.harvester.simpleurl; + +import org.apache.commons.io.IOUtils; +import org.apache.jena.query.*; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFDataMgr; +import org.fao.geonet.Constants; +import org.fao.geonet.domain.ISODate; +import org.fao.geonet.domain.Pair; +import org.fao.geonet.utils.Log; +import org.fao.geonet.utils.Xml; +import org.jdom.Element; +import org.jdom.JDOMException; + +import java.io.*; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * RDF utilities to query and process DCAT feeds. + */ +public class RDFUtils { + public static final String RESOURCE_FOLDER = "harvester-resources/simpleUrl/"; + + public static final String LOGGER_NAME = "geonetwork.harvester.simpleurl"; + + /** + * Retrieve all UUIDs from the RDF feed using a SPARQL query + * + * @return a map of UUID and the corresponding DCAT record. + */ + public static HashMap getAllUuids(String feedUrl) throws Exception { + // Create an empty in-memory model and populate it from the graph + Model model = ModelFactory.createMemModelMaker().createDefaultModel(); + RDFDataMgr.read(model, feedUrl); + + Element rdfModel = Xml.loadStream(IOUtils.toInputStream(toRdfString(model), StandardCharsets.UTF_8.displayName())); + + return getAllUuids(rdfModel); + } + + /** + * Retrieve all UUIDs from the RDF feed using a SPARQL query. + *

+ * The SPARQL results structure is: + *

+     * 
+     *   
+     *     
+     *     
+     *     
+     *     
+     *   
+     *   
+     *     
+     *       
+     *         https://apps.titellus.net/geonetwork/api/collections/main/items/8698bf0b-fceb-4f0f-989b-111e7c4af0a4
+     *       
+     *       
+     *         http://www.w3.org/1999/02/22-rdf-syntax-ns#type
+     *       
+     *       
+     *         http://www.w3.org/ns/dcat#CatalogRecord
+     *       
+     *       
+     *         rdf:type
+     *       
+     *     
+     *     ...
+     * 
+ * and can be processed using XSLT to build a metadata supported by the catalogue. + * + * @return a map of UUID and the corresponding DCAT record. + */ + public static HashMap getAllUuids(Element feed) throws Exception { + Element rdfDocument = checkForMissingRdfAbout(feed); + + Model model = ModelFactory.createMemModelMaker().createDefaultModel(); + RDFDataMgr.read(model, + IOUtils.toInputStream(Xml.getString(rdfDocument), StandardCharsets.UTF_8), + Lang.RDFXML); + + model = checkAndCreateMissingCatalogRecords(model); + + Query queryRecordIds = QueryFactory.create(getQueryString("extract-records-ids.rq")); + QueryExecution qe = QueryExecutionFactory.create(queryRecordIds, model); + ResultSet resultIds = qe.execSelect(); + + HashMap records = new HashMap<>(); + while (resultIds.hasNext()) { + Pair recordInfo = getRecordInfo(resultIds.nextSolution(), model); + if (recordInfo != null) { + records.put(recordInfo.one(), recordInfo.two()); + } + } + + qe.close(); + model.close(); + return records; + } + + + private static Element checkForMissingRdfAbout(Element rdfModel) throws Exception { + List ns = rdfModel.getAdditionalNamespaces(); + List nodeWithNoRdfAbout = + Xml.selectNodes(rdfModel, + ".//*[local-name() = 'Dataset' or local-name() = 'DataService'][not(@rdf:about)]", ns + ); + nodeWithNoRdfAbout.forEach(n -> { + try { + Object httpIdentifier = Xml.selectSingle(n, "dct:identifier[matches(., '^http(s)://.*$')]/text()", ns); + if (httpIdentifier != null) { + n.setAttribute("rdf:about", (String) httpIdentifier); + } + } catch (JDOMException e) { + } + }); + return rdfModel; + } + + private static Model checkAndCreateMissingCatalogRecords(Model model) throws IOException, URISyntaxException { + Query queryExtractNoRec = QueryFactory.create(getQueryString("extract-resources-no-records.rq")); + QueryExecution qe = QueryExecutionFactory.create(queryExtractNoRec, model); + + Model newModel = model; + ResultSet resultIds = qe.execSelect(); + while (resultIds.hasNext()) { + QuerySolution solution = resultIds.nextSolution(); + newModel = createCatalogRecord( + newModel, + solution.get("resourceId").toString(), + solution.get("catalogId").toString() + ); + } + return newModel; + } + + + private static InputStream getResourceAsStream(String resourcePath) { + return RDFUtils.class.getClassLoader().getResourceAsStream(resourcePath); + } + + /** + * Convert a model back to RDF XML + * Only used for debugging + */ + private static String toRdfString(Model model) { + StringWriter out = new StringWriter(); + RDFDataMgr.write(out, model, Lang.RDFXML); + return out.toString(); + } + + + private static String getQueryString(String queryFile) throws IOException, URISyntaxException { + return IOUtils.toString(getResourceAsStream(RESOURCE_FOLDER + "sparql/" + queryFile), Constants.CHARSET); + } + + private static Model createCatalogRecord(Model model, String resourceId, String catalogId) throws IOException, URISyntaxException { + String recordUUID = resourceId; + Date now = new Date(); + String localQuery = getQueryString("add-CatalogRecord.rq") + .replace("%recordID%", recordUUID) + .replace("%recordUUID%", recordUUID) + .replace("%resourceId%", resourceId) + // TODO: Should we set modified of catalog record to the date of publication? + // If not, they will be popup on top of search by date + .replace("%modifiedDate%", new ISODate(now.getTime(), false).toString()) + .replace("%catalogId%", catalogId); + + Query queryFixBlankNodes = QueryFactory.create(localQuery); + QueryExecution qe = QueryExecutionFactory.create(queryFixBlankNodes, model); + Model newModel = qe.execConstruct(); + qe.close(); + return newModel; + } + + + private static Pair getRecordInfo(QuerySolution solution, Model model) { + try { + String recordId = solution.get("recordId").toString(); + String resourceId = solution.get("resourceId").toString(); + String baseRecordUUID = solution.get("baseRecordUUID").toString(); + + String localQueryBuildRecord = getQueryString("build-record.rq") + .replace("%recordId%", recordId) + .replace("%resourceId%", resourceId); + + Query queryRecord = QueryFactory.create(localQueryBuildRecord); + QueryExecution qe = QueryExecutionFactory.create(queryRecord, model); + ResultSet results = qe.execSelect(); + + if (results.hasNext()) { + ByteArrayOutputStream outxml = new ByteArrayOutputStream(); + ResultSetFormatter.outputAsXML(outxml, results); + Element sparqlResults = Xml.loadStream( + new ByteArrayInputStream(outxml.toByteArray())); + qe.close(); + + Map params = new HashMap<>(); + params.put("recordUUID", baseRecordUUID); + + // TODO: Update record only if modified is more recent than local +// Literal modifiedLiteral = solution.getLiteral("modified"); +// String modified; +// if (modifiedLiteral != null) { +// modified = DateUtil.convertToISOZuluDateTime(modifiedLiteral.getString()); +// } + + return Pair.read(baseRecordUUID, sparqlResults); + } else { + qe.close(); + } + + } catch (JDOMException | IOException | URISyntaxException e) { + Log.error(LOGGER_NAME, String.format( + "Error extracting record info using SPARQL. Error is: %s", e.getMessage())); + } + + // we get here if we couldn't get the UUID or date modified + return null; + } +} diff --git a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlHarvester.java b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlHarvester.java similarity index 83% rename from harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlHarvester.java rename to harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlHarvester.java index bd8dcaa8ecdb..f1c458a5a295 100644 --- a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlHarvester.java +++ b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlHarvester.java @@ -21,25 +21,13 @@ //=== Rome - Italy. email: geonetwork@osgeo.org //============================================================================== -package org.fao.geonet.kernel.harvest.harvester.simpleUrl; +package org.fao.geonet.kernel.harvest.harvester.simpleurl; -import jeeves.server.context.ServiceContext; import org.fao.geonet.Logger; -import org.fao.geonet.domain.Source; -import org.fao.geonet.domain.SourceType; -import org.fao.geonet.exceptions.BadInputEx; import org.fao.geonet.kernel.harvest.harvester.AbstractHarvester; -import org.fao.geonet.kernel.harvest.harvester.AbstractParams; import org.fao.geonet.kernel.harvest.harvester.HarvestResult; -import org.fao.geonet.kernel.harvest.harvester.csw.CswParams; -import org.fao.geonet.repository.SourceRepository; -import org.fao.geonet.resources.Resources; -import org.jdom.Element; -import org.springframework.beans.factory.annotation.Autowired; -import java.io.File; import java.sql.SQLException; -import java.util.UUID; /** * Harvest metadata from a JSON source. diff --git a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlParams.java b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlParams.java similarity index 89% rename from harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlParams.java rename to harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlParams.java index 769c692e6536..d613c3ce8a7f 100644 --- a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlParams.java +++ b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlParams.java @@ -21,7 +21,7 @@ //=== Rome - Italy. email: geonetwork@osgeo.org //============================================================================== -package org.fao.geonet.kernel.harvest.harvester.simpleUrl; +package org.fao.geonet.kernel.harvest.harvester.simpleurl; import org.fao.geonet.Util; import org.fao.geonet.exceptions.BadInputEx; @@ -44,7 +44,7 @@ public SimpleUrlParams(DataManager dm) { } /** - * called when a new entry must be added. Reads values from the provided entry, providing + * called when a new entry must be added. Read values from the provided entry, providing * default values. */ public void create(Element node) throws BadInputEx { @@ -52,14 +52,14 @@ public void create(Element node) throws BadInputEx { Element site = node.getChild("site"); - url = Util.getParam(site, "url", "http://dados.gov.br/api/3/action/package_search?q="); - loopElement = Util.getParam(site, "loopElement", "/result/results"); - numberOfRecordPath = Util.getParam(site, "numberOfRecordPath", "/result/count"); + url = Util.getParam(site, "url", ""); + loopElement = Util.getParam(site, "loopElement", ""); + numberOfRecordPath = Util.getParam(site, "numberOfRecordPath", ""); recordIdPath = Util.getParam(site, "recordIdPath", "id"); pageSizeParam = Util.getParam(site, "pageSizeParam", "rows"); pageFromParam = Util.getParam(site, "pageFromParam", "start"); - toISOConversion = Util.getParam(site, "toISOConversion", "CKAN-to-ISO19115-3-2018"); - icon = Util.getParam(site, "icon", "default.gif"); + toISOConversion = Util.getParam(site, "toISOConversion", ""); + icon = Util.getParam(site, "icon", ""); } /** diff --git a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlResourceType.java b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlResourceType.java similarity index 53% rename from harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlResourceType.java rename to harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlResourceType.java index 3efce3ba873b..28cd4895305f 100644 --- a/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/SimpleUrlResourceType.java +++ b/harvesters/src/main/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/SimpleUrlResourceType.java @@ -1,4 +1,4 @@ -package org.fao.geonet.kernel.harvest.harvester.simpleUrl; +package org.fao.geonet.kernel.harvest.harvester.simpleurl; public enum SimpleUrlResourceType { JSON, diff --git a/harvesters/src/main/resources/config-spring-geonetwork.xml b/harvesters/src/main/resources/config-spring-geonetwork.xml index 75469da05f15..4b362a2f6e43 100644 --- a/harvesters/src/main/resources/config-spring-geonetwork.xml +++ b/harvesters/src/main/resources/config-spring-geonetwork.xml @@ -61,6 +61,6 @@ diff --git a/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/add-CatalogRecord.rq b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/add-CatalogRecord.rq new file mode 100644 index 000000000000..698f4e40ac32 --- /dev/null +++ b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/add-CatalogRecord.rq @@ -0,0 +1,19 @@ +PREFIX dcat: +PREFIX dct: +PREFIX foaf: + +# Create a catalog record for the given resource +CONSTRUCT { + ?recordId a dcat:CatalogRecord. + ?recordId dct:identifier "%recordUUID%". + ?recordId foaf:primaryTopic <%resourceId%>. + ?recordId dct:modified "%modifiedDate%". + <%catalogId%> dcat:record ?recordId. + ?s ?p ?o. +} WHERE { + { + BIND (<%recordID%> as ?recordId) + } UNION { + ?s ?p ?o. + } +} diff --git a/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/build-record.rq b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/build-record.rq new file mode 100644 index 000000000000..77d0017895f3 --- /dev/null +++ b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/build-record.rq @@ -0,0 +1,94 @@ +PREFIX dcat: +PREFIX dct: +PREFIX vcard: +PREFIX apf: +PREFIX afn: +PREFIX skos: + +# Retrieve all triples about a specific record URI. +# Replace the string "%recordId%" with the record ID and +# the string "%resourceId%" with the resource ID before executing +SELECT DISTINCT ?subject ?predicate ?object ?pAsQName +WHERE { + { + # Triples on a dct:Catalog instance + ?subject ?predicate ?object. + ?subject a dcat:Catalog. + ?subject dcat:dataset|dcat:service <%resourceId%>. + FILTER (?predicate != dcat:dataset && ?predicate != dcat:service && ?predicate != dcat:record) + + } UNION { + # Triples on a dct:Catalog instance's child resources (publisher, distribution, ed.) + ?subject ?predicate ?object. + ?s a dcat:Catalog. + ?s dcat:dataset|dcat:service <%resourceId%>. + ?s ?p ?subject. + FILTER (?p != dcat:dataset && ?p != dcat:service && ?p != dcat:record). + FILTER (?predicate != dcat:dataset && ?predicate != dcat:service && ?predicate != dcat:record). + + } UNION { + # Triples on a specific catalog record + ?subject ?predicate ?object. + FILTER (?subject = <%recordId%> || ?object = <%recordId%>) + + } UNION { + # Triples on specific catalog record's child resources + ?subject ?predicate ?object. + <%recordId%> ?p ?subject + + } UNION { + # Triples on specific catalog record's child of child + ?subject ?predicate ?object. + <%recordId%> ?p1 ?obj. + ?obj ?p2 ?subject. + + } UNION { + # Triples on a specific resource + ?subject ?predicate ?object. + FILTER (?subject = <%resourceId%> || ?object = <%resourceId%>) + + } UNION { + #Triples on a specific resource's child resources (publisher, distribution, ed.) + ?subject ?predicate ?object. + <%resourceId%> ?p ?subject. + + } UNION { + # Triple on a specific distribution's child dct:license + ?subject ?predicate ?object. + <%resourceId%> dcat:distribution ?distribution. + ?distribution ?p ?subject. + + } UNION { + # Triple on a specific contactPoint's child Address + ?subject ?predicate ?object. + <%resourceId%> dcat:contactPoint ?address. + ?address ?p ?subject. + + } UNION { + # Triples on a skos:Concept instance + ?subject ?predicate ?object. + ?subject a skos:Concept. + } + + BIND (afn:namespace(?predicate) as ?pns) + + BIND (COALESCE( + IF(?pns = 'http://www.w3.org/ns/dcat#', 'dcat:', 1/0), + IF(?pns = 'http://purl.org/dc/terms/', 'dct:', 1/0), + IF(?pns = 'http://spdx.org/rdf/terms#', 'spdx:', 1/0), + IF(?pns = 'http://www.w3.org/2004/02/skos/core#', 'skos:', 1/0), + IF(?pns = 'http://www.w3.org/ns/adms#', 'adms:', 1/0), + IF(?pns = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf:', 1/0), + IF(?pns = 'http://www.w3.org/2006/vcard/ns#', 'vcard:', 1/0), + IF(?pns = 'http://xmlns.com/foaf/0.1/', 'foaf:', 1/0), + IF(?pns = 'http://www.w3.org/2002/07/owl#', 'owl:', 1/0), + IF(?pns = 'http://schema.org/', 'schema:', 1/0), + IF(?pns = 'http://www.w3.org/2000/01/rdf-schema#', 'rdfs:', 1/0), + IF(?pns = 'http://www.w3.org/ns/locn#', 'locn:', 1/0), + IF(?pns = 'http://purl.org/dc/elements/1.1/', 'dc:', 1/0), + IF(?pns = 'http://data.vlaanderen.be/ns/metadata-dcat#', 'mdcat:', 1/0), + 'unkown:' + ) AS ?pprefix) + + BIND (CONCAT(?pprefix, afn:localname(?predicate)) AS ?pAsQName) +} diff --git a/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/extract-records-ids.rq b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/extract-records-ids.rq new file mode 100644 index 000000000000..f16a1162816b --- /dev/null +++ b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/extract-records-ids.rq @@ -0,0 +1,21 @@ +PREFIX dcat: +PREFIX dct: +PREFIX foaf: + +# Extract all CatalogRecord ids, modified date and primary topic +SELECT ?catalogId ?recordId ?modified ?resourceId ?baseRecordUUID ?baseResourceUUID +WHERE { + VALUES ?type { dcat:Dataset dcat:DataService } + ?recordId a dcat:CatalogRecord . + ?recordId foaf:primaryTopic ?resourceId . + ?resourceId a ?type + OPTIONAL { + ?catalogId a dcat:Catalog . + ?catalogId dcat:record ?recordId . + ?recordId dct:modified ?modified . + ?recordId dct:identifier ?recordDctIdentifier . + ?resourceId dct:identifier ?resourceDctIdentifier . + } + BIND (if (bound(?recordDctIdentifier), ?recordDctIdentifier, ?recordId) as ?baseRecordUUID) + BIND (if (bound(?resourceDctIdentifier), ?resourceDctIdentifier, ?resourceId) as ?baseResourceUUID) +} diff --git a/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/extract-resources-no-records.rq b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/extract-resources-no-records.rq new file mode 100644 index 000000000000..957f97f66bb6 --- /dev/null +++ b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/extract-resources-no-records.rq @@ -0,0 +1,41 @@ +PREFIX dcat: +PREFIX dct: +PREFIX foaf: + +# Extract list of resources ID that doesn't have a dcat:CatalogRecord. +# It first check for the first resource dct:identifier. +# If none, check on the resource about. +# If none, ignore the resource. +SELECT ?catalogId ?resourceId +WHERE { + { + ?resourceId a dcat:Dataset . + ?catalogId dcat:dataset ?resourceId . + OPTIONAL { + ?catalogId a dcat:Catalog . + ?resourceId dct:identifier ?identifier + } + FILTER (NOT EXISTS { + ?recordId a dcat:CatalogRecord . + ?recordId foaf:primaryTopic ?resourceId . + } + ) + FILTER (bound(?identifier) || !isBlank(?resourceId)) + + } UNION { + ?catalogId a dcat:Catalog . + ?resourceId a dcat:DataService . + ?catalogId dcat:service ?resourceId . + OPTIONAL { + ?resourceId dct:identifier ?identifier . + } + BIND (if (bound(?identifier), ?identifier, ?resourceId) as ?brId) + FILTER (NOT EXISTS { + ?recordId a dcat:CatalogRecord . + ?recordId foaf:primaryTopic ?resourceId . + } + ) + FILTER (bound(?identifier) || !isBlank(?resourceId)) + } +} +GROUP BY ?resourceId ?catalogId diff --git a/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/fix-blank-node.rq b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/fix-blank-node.rq new file mode 100644 index 000000000000..88e948e33079 --- /dev/null +++ b/harvesters/src/main/resources/harvester-resources/simpleUrl/sparql/fix-blank-node.rq @@ -0,0 +1,46 @@ +PREFIX dcat: +PREFIX dct: + +# Set URIs of blank-node dcat:Dataset and dcat:DataService instances to a URN composed of the resource's first title. +CONSTRUCT { + ?newResourceURI ?p1 ?o1 . + ?s2 ?p2 ?newResourceURI . + ?s3 ?p3 ?o3 . +} +WHERE { + { + ?oldResourceURI a dcat:Dataset . + ?oldResourceURI ?p1 ?o1 . + ?s2 ?p2 ?oldResourceURI . + FILTER (isBlank(?oldResourceURI)) . + { + SELECT ?oldResourceURI (UUID() AS ?uuid) (MIN(?t) AS ?title) + WHERE { + ?oldResourceURI dct:title ?t . + } + GROUP BY ?oldResourceURI + } + BIND (?uuid AS ?newResourceURI) . + } UNION { + ?s3 ?p3 ?o3 . + FILTER (NOT EXISTS {?s3 a dcat:Dataset . FILTER (isBlank(?s3)) .}) . + FILTER (NOT EXISTS {?o3 a dcat:Dataset . FILTER (isBlank(?o3)) .}) . + } UNION { + ?oldResourceURI a dcat:DataService . + ?oldResourceURI ?p1 ?o1 . + ?s2 ?p2 ?oldResourceURI . + FILTER (isBlank(?oldResourceURI)) . + { + SELECT ?oldResourceURI (UUID() AS ?uuid) (MIN(?t) AS ?title) + WHERE { + ?oldResourceURI dct:title ?t . + } + GROUP BY ?oldResourceURI + } + BIND (?uuid AS ?newResourceURI) . + } UNION { + ?s3 ?p3 ?o3 . + FILTER (NOT EXISTS {?s3 a dcat:DataService . FILTER (isBlank(?s3)) .}) . + FILTER (NOT EXISTS {?o3 a dcat:DataService . FILTER (isBlank(?o3)) .}) . + } +} diff --git a/harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/HarvesterTest.java b/harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/HarvesterTest.java similarity index 97% rename from harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/HarvesterTest.java rename to harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/HarvesterTest.java index c405bcba4388..06e2998bfd45 100644 --- a/harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleUrl/HarvesterTest.java +++ b/harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/HarvesterTest.java @@ -1,4 +1,4 @@ -package org.fao.geonet.kernel.harvest.harvester.simpleUrl; +package org.fao.geonet.kernel.harvest.harvester.simpleurl; import org.fao.geonet.utils.Log; import org.junit.Test; diff --git a/harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/RDFUtilsTest.java b/harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/RDFUtilsTest.java new file mode 100644 index 000000000000..465929697cb1 --- /dev/null +++ b/harvesters/src/test/java/org/fao/geonet/kernel/harvest/harvester/simpleurl/RDFUtilsTest.java @@ -0,0 +1,19 @@ +package org.fao.geonet.kernel.harvest.harvester.simpleurl; + +import org.jdom.Element; +import org.junit.Test; + +import java.util.Map; + +import static junit.framework.Assert.assertEquals; + +public class RDFUtilsTest { + @Test + public void test_getAllUuidsFromFeed() throws Exception { + Map records = RDFUtils.getAllUuids(this.getClass().getResource("dcat-feed-mow.rdf").toString()); + assertEquals(22, records.size()); + + records = RDFUtils.getAllUuids(this.getClass().getResource("ogcapirecords-dcat-output.rdf").toString()); + assertEquals(1, records.size()); + } +} diff --git a/harvesters/src/test/resources/META-INF/services/javax.xml.transform.TransformerFactory b/harvesters/src/test/resources/META-INF/services/javax.xml.transform.TransformerFactory new file mode 100644 index 000000000000..b53ca855ffb5 --- /dev/null +++ b/harvesters/src/test/resources/META-INF/services/javax.xml.transform.TransformerFactory @@ -0,0 +1 @@ +net.sf.saxon.TransformerFactoryImpl \ No newline at end of file diff --git a/harvesters/src/test/resources/org/fao/geonet/kernel/harvest/harvester/simpleurl/dcat-feed-mow.rdf b/harvesters/src/test/resources/org/fao/geonet/kernel/harvest/harvester/simpleurl/dcat-feed-mow.rdf new file mode 100644 index 000000000000..e5048f67dc9a --- /dev/null +++ b/harvesters/src/test/resources/org/fao/geonet/kernel/harvest/harvester/simpleurl/dcat-feed-mow.rdf @@ -0,0 +1,1246 @@ + + + + Dataroom Beleidsdomein Mobiliteit en Openbare Werken + Het beleidsdomein verzamelt indicatoren, die relevant zijn voor de beleidsvoorbereiding, -opvolging +en -evaluatie, in deze databank voor intern gebruik en voor verdere verspreiding, onder meer als open data. +De kwaliteit en betrouwbaarheid van de data is afhankelijk van de gebruikte meettechnieken +en berekeningswijzen, die als metadata bij de indicatoren worden aangegeven. + 2016-02-03 + 2023-01-18 + + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2022-04-28 + 2022-04-28 + aantal ton gelost op de waterwegen + hoeveelheid goederen gelost langs waterwegen beheerd door De Vlaamse Waterweg uitgedrukt in ton + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + aantal ton gelost op de waterwegen - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal ton gelost op de waterwegen - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal ton gelost op de waterwegen - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + aantal ton gelost op de waterwegen - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2021-01-29 + 2021-01-29 + verdeling van woon-werkverplaatsingen volgens hoofdvervoerswijze (beroepsactieven) + percentage van de woon-werkverplaatsingen door beroepsactieven waarvoor gebruik wordt gemaakt van de betreffende vervoerswijze als hoofdvervoerswijze (d.w.z. voor het grootste deel van de af te leggen afstand) + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + verdeling van woon-werkverplaatsingen volgens hoofdvervoerswijze (beroepsactieven) - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + verdeling van woon-werkverplaatsingen volgens hoofdvervoerswijze (beroepsactieven) - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + verdeling van woon-werkverplaatsingen volgens hoofdvervoerswijze (beroepsactieven) - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + verdeling van woon-werkverplaatsingen volgens hoofdvervoerswijze (beroepsactieven) - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + aantal tonkilometer Albertkanaal + hoeveelheid goederen vervoerd op het Albertkanaal uitgedrukt in tonkilometer + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + aantal tonkilometer Albertkanaal - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal tonkilometer Albertkanaal - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal tonkilometer Albertkanaal - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2022-04-28 + 2022-04-28 + aantal vervoerde containers op de waterwegen + hoeveelheid vervoerde containers op waterwegen beheerd door De Vlaamse Waterweg uitgedrukt in twenty-foot equivalent units (TEU) + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + aantal vervoerde containers op de waterwegen - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal vervoerde containers op de waterwegen - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal vervoerde containers op de waterwegen - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + aantal vervoerde containers op de waterwegen - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + verdeling van personen (beroepsactieven) volgens hoofdvervoerswijze woon-werkverkeer + percentage van de beroepsactieven met een vast werkadres dat voor het woon-werkverkeer gebruikt maakt van de betreffende vervoerswijze als hoofdvervoerswijze (d.w.z. voor het grootste deel van de af te leggen afstand) + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + verdeling van personen (beroepsactieven) volgens hoofdvervoerswijze woon-werkverkeer - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + verdeling van personen (beroepsactieven) volgens hoofdvervoerswijze woon-werkverkeer - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + verdeling van personen (beroepsactieven) volgens hoofdvervoerswijze woon-werkverkeer - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + verdeling van personen (beroepsactieven) volgens hoofdvervoerswijze woon-werkverkeer - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2021-02-10 + 2021-02-10 + verdeling van personen (scholieren en studenten) volgens hoofdvervoerswijze woon-schoolverkeer + percentage van de scholieren en studenten dat voor het woon-schoolverkeer gebruikt maakt van de betreffende vervoerswijze als hoofdvervoerswijze (d.w.z. voor het grootste deel van de af te leggen afstand) + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + verdeling van personen (scholieren en studenten) volgens hoofdvervoerswijze woon-schoolverkeer - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + verdeling van personen (scholieren en studenten) volgens hoofdvervoerswijze woon-schoolverkeer - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + verdeling van personen (scholieren en studenten) volgens hoofdvervoerswijze woon-schoolverkeer - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + verdeling van personen (scholieren en studenten) volgens hoofdvervoerswijze woon-schoolverkeer - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + aantal ton zwerfvuil langs de gewestwegen + som van het aantal ton opgehaald zwerfvuil langs de autosnelwegen en gewestwegen + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + aantal ton zwerfvuil langs de gewestwegen - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal ton zwerfvuil langs de gewestwegen - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal ton zwerfvuil langs de gewestwegen - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2022-04-28 + 2022-04-28 + aantal ton vervoerd op de waterwegen + hoeveelheid goederen vervoerd op waterwegen beheerd door De Vlaamse Waterweg uitgedrukt in ton + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + aantal ton vervoerd op de waterwegen - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal ton vervoerd op de waterwegen - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal ton vervoerd op de waterwegen - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + aantal ton vervoerd op de waterwegen - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + aantal ton vervoerd Albertkanaal + hoeveelheid goederen vervoerd op het Albertkanaal uitgedrukt in ton + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + aantal ton vervoerd Albertkanaal - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal ton vervoerd Albertkanaal - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal ton vervoerd Albertkanaal - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + verdeling van verplaatsingen volgens verplaatsingsmotief + percentage van het gemiddeld aantal verplaatsingen per persoon, per dag dat wordt gemaakt voor het betreffende verplaatsingsmotief + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + verdeling van verplaatsingen volgens verplaatsingsmotief - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + verdeling van verplaatsingen volgens verplaatsingsmotief - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + verdeling van verplaatsingen volgens verplaatsingsmotief - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + verdeling van verplaatsingen volgens verplaatsingsmotief - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + aantal fietsers op vaste telposten + aantal fietsers geregistreerd door vaste fietstelposten + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + aantal fietsers op vaste telposten - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal fietsers op vaste telposten - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal fietsers op vaste telposten - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2022-04-28 + 2022-04-28 + aantal ton geladen op de waterwegen + hoeveelheid goederen geladen langs waterwegen beheerd door De Vlaamse Waterweg uitgedrukt in ton + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + aantal ton geladen op de waterwegen - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal ton geladen op de waterwegen - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal ton geladen op de waterwegen - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + aantal ton geladen op de waterwegen - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + filezwaarte op het hoofdwegennet - 2007 tot 2014 - oude berekeningswijze + product van filelengte en fileduur, cumulatief over de beschouwde periode, cumulatief over wegvakken van het hoofdwegennet + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + filezwaarte op het hoofdwegennet - 2007 tot 2014 - oude berekeningswijze - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + filezwaarte op het hoofdwegennet - 2007 tot 2014 - oude berekeningswijze - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + filezwaarte op het hoofdwegennet - 2007 tot 2014 - oude berekeningswijze - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + verdeling van verplaatsingen volgens hoofdvervoerswijze en afstandsklasse + percentage van het gemiddeld aantal verplaatsingen per persoon, per dag dat valt binnen de betreffende afstandsklasse en waarvoor gebruik gemaakt wordt van de betreffende vervoerswijze als hoofdvervoerswijze (d.w.z. voor het grootste deel van de af te leggen afstand) + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + verdeling van verplaatsingen volgens hoofdvervoerswijze en afstandsklasse - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + verdeling van verplaatsingen volgens hoofdvervoerswijze en afstandsklasse - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + verdeling van verplaatsingen volgens hoofdvervoerswijze en afstandsklasse - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + verdeling van verplaatsingen volgens hoofdvervoerswijze en afstandsklasse - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2021-05-06 + 2021-05-06 + verdeling van woon-schoolverplaatsingen volgens hoofdvervoerswijze (scholieren en studenten) + percentage van de woon-schoolverplaatsingen door scholieren en studenten waarvoor gebruik wordt gemaakt van de betreffende vervoerswijze als hoofdvervoerswijze (d.w.z. voor het grootste deel van de af te leggen afstand) + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + verdeling van woon-schoolverplaatsingen volgens hoofdvervoerswijze (scholieren en studenten) - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + verdeling van woon-schoolverplaatsingen volgens hoofdvervoerswijze (scholieren en studenten) - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + verdeling van woon-schoolverplaatsingen volgens hoofdvervoerswijze (scholieren en studenten) - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + voertuigenpark (De Lijn) + wordt bijgehouden in de loop van het jaar door voertuigen uit dienst te verwijderen en nieuwe voertuigen in dienst te zetten + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + voertuigenpark (De Lijn) - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + voertuigenpark (De Lijn) - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + voertuigenpark (De Lijn) - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + voertuigenpark (De Lijn) - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + aantal reizigers per type vervoersbewijs (De Lijn) + aantal reizigers per type vervoersbewijs + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + aantal reizigers per type vervoersbewijs (De Lijn) - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal reizigers per type vervoersbewijs (De Lijn) - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal reizigers per type vervoersbewijs (De Lijn) - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + aantal reizigers per type vervoersbewijs (De Lijn) - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2021-12-23 + 2021-12-23 + kilometers per persoon per dag voor verplaatsingen met de fiets als hoofdvervoerswijze + gemiddeld aantal afgelegde kilometers per persoon per dag voor verplaatsingen waarvoor gebruik wordt gemaakt van de fiets of elektrische fiets als hoofdvervoerswijze + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + kilometers per persoon per dag voor verplaatsingen met de fiets als hoofdvervoerswijze - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + kilometers per persoon per dag voor verplaatsingen met de fiets als hoofdvervoerswijze - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + kilometers per persoon per dag voor verplaatsingen met de fiets als hoofdvervoerswijze - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2022-01-13 + 2022-01-13 + fietsbezit per gezin + Percentage gezinnen die x aantal fietsen bezitten, afzonderlijk voor gewone en voor elektrische fietsen. Waarbij x varieert van 0 tot 9. + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + fietsbezit per gezin - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + fietsbezit per gezin - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + fietsbezit per gezin - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2021-03-26 + 2021-03-26 + aantal voertuigkilometers per voertuigtype en wegennetwerk + jaarlijks aantal voertuigkilometers op snelwegen, andere gewestwegen en gemeentewegen per voertuigtype + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + aantal voertuigkilometers per voertuigtype en wegennetwerk - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal voertuigkilometers per voertuigtype en wegennetwerk - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal voertuigkilometers per voertuigtype en wegennetwerk - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2022-09-22 + 2022-09-22 + aantal tonkilometer op de waterwegen + hoeveelheid goederen vervoerd op waterwegen beheerd door De Vlaamse Waterweg uitgedrukt in tonkilometer + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + + aantal tonkilometer op de waterwegen - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + aantal tonkilometer op de waterwegen - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + aantal tonkilometer op de waterwegen - Commentaar + + + XML + Opmerkingen horend bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie). + + + aantal tonkilometer op de waterwegen - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + + 2019-08-26 + 2019-08-26 + filezwaarte - nieuwe berekeningswijze + product van filelengte en fileduur, cumulatief over de beschouwde periode, cumulatief over wegvakken van het hoofdwegennet + + + Beleidsdomein Mobiliteit en Openbare Werken + organization + + + + + https://www.mow-contact.be/ + + + + + + + + + + filezwaarte - nieuwe berekeningswijze - Cijfers (XML) + + + XML + Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) +zijn terug te vinden in de commentaar file. + + + filezwaarte - nieuwe berekeningswijze - Cijfers (Tabel) + + + HTML + Kies de URL hierboven om de cijfergegevens in een tabel te zien. + +De cijfergegevens in de tabel kunnen in MS Excel opgeladen worden via de 'Data' - 'From Web' functie. Gebruik de +hierboven vermelde URL in het adres veld. + +Eventuele opmerkingen bij de cijferrecords voor een bepaalde periode (cfr. meetfrequentie) zijn terug te vinden +in de commentaar file. + + + filezwaarte - nieuwe berekeningswijze - Rapport (PowerBI) + + + Rapport + Rapport met de cijfergegevens weergeven. + + diff --git a/harvesters/src/test/resources/org/fao/geonet/kernel/harvest/harvester/simpleurl/ogcapirecords-dcat-output.rdf b/harvesters/src/test/resources/org/fao/geonet/kernel/harvest/harvester/simpleurl/ogcapirecords-dcat-output.rdf new file mode 100644 index 000000000000..d65f90c1499b --- /dev/null +++ b/harvesters/src/test/resources/org/fao/geonet/kernel/harvest/harvester/simpleurl/ogcapirecords-dcat-output.rdf @@ -0,0 +1,116 @@ + + 8698bf0b-fceb-4f0f-989b-111e7c4af0a4 + 2022-11-19T08:23:19.058Z + 2022-11-19T08:23:19.058Z + + + ger + + + + + + Alpenkonvention + Perimeter der Alpenkonvention in der Schweiz. Die Alpenkonvention ist ein völkerrechtlicher Vertrag zwischen den acht Alpenländern Deutschland, Frankreich, Italien, Liechtenstein, Monaco, Österreich, Schweiz, Slowenien sowie der Europäischen Union. Das Ziel des Übereinkommens ist der Schutz der Alpen durch eine sektorübergreifende, ganzheitliche und nachhaltige Politik. + ch.are.alpenkonvention + 1999-01-01T00:00:00Z + 2009-01-01T00:00:00Z + + + ger + + + + + + fre + + + + + + ita + + + + + + Bundesamt für Raumentwicklung + pointOfContact + rolf.giezendanner@are.admin.ch + + + + + Bundesamt für Raumentwicklung + owner + info@are.admin.ch + + + + + Umweltüberwachung + + + + + Nachhaltige Entwicklung + + + + + Aufbewahrungs- und Archivierungsplanung AAP - Bund + + + + + Alpenkonvention + + + + + AK + + + + + Dataset + + + + + Alpenkonvention + + + 25000 + + + {"coordinates":[[[6.755991,45.788744],[10.541824,45.788744],[10.541824,47.517566],[6.755991,47.517566],[6.755991,45.788744]]],"type":"Polygon"} + + + + + + Permalink opendata.swiss + Permalink opendata.swiss + + + OPENDATA:SWISS + + + + + + + asNeeded + + + + + + Digitalisiert nach den administrativen Einheiten der Schweiz, die im Anhang des Übereinkommens erscheinen. + + + + + diff --git a/pom.xml b/pom.xml index 10bb11a4ec6d..3b31d9c38ec6 100644 --- a/pom.xml +++ b/pom.xml @@ -426,6 +426,12 @@ rio 1.0.9
+ + org.apache.jena + apache-jena-libs + pom + 3.17.0 + diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/DCAT/sparql-to-iso19115-3.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/DCAT/sparql-to-iso19115-3.xsl new file mode 100644 index 000000000000..79fafcbfc96a --- /dev/null +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/convert/DCAT/sparql-to-iso19115-3.xsl @@ -0,0 +1,671 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pointOfContact + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ISO 19115-3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dataset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/extract-date-modified.xsl b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/extract-date-modified.xsl index ae4c56a43cc8..a6546241b0ab 100644 --- a/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/extract-date-modified.xsl +++ b/schemas/iso19115-3.2018/src/main/plugin/iso19115-3.2018/extract-date-modified.xsl @@ -7,10 +7,10 @@ - + /cit:date/*)[1]"/> diff --git a/web-ui/src/main/resources/catalog/templates/admin/harvest/type/simpleurl.html b/web-ui/src/main/resources/catalog/templates/admin/harvest/type/simpleurl.html index 8b8dc9a33a85..bcd44bb8a826 100644 --- a/web-ui/src/main/resources/catalog/templates/admin/harvest/type/simpleurl.html +++ b/web-ui/src/main/resources/catalog/templates/admin/harvest/type/simpleurl.html @@ -44,10 +44,7 @@ data-gn-harvester-account="harvesterSelected" /> -
+