diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index af5385c878..4fef9f628f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,70 +3,59 @@ name: CI
on: push
jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Set up JDK 11
- uses: actions/setup-java@v3
- with:
- java-version: '11'
- distribution: 'temurin'
- cache: maven
- - name: Cache SonarCloud packages
- uses: actions/cache@v3
- with:
- path: ~/.sonar/cache
- key: ${{ runner.os }}-sonar
- restore-keys: ${{ runner.os }}-sonar
- - name: Build - Clean Jacoco Verify
- run: >
- mvn --batch-mode --update-snapshots
- clean
- org.jacoco:jacoco-maven-plugin:prepare-agent
- verify
- org.jacoco:jacoco-maven-plugin:report
- - name: Sonar Analyze
- run: >
- mvn --batch-mode
- -Dsonar.host.url=https://sonarcloud.io
- -Dsonar.organization=europeana
- -Dsonar.projectKey=europeana_metis-framework
- sonar:sonar
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- deploy_libraries:
- needs: build
- if: ${{ github.ref == 'refs/heads/develop' }}
- runs-on: ubuntu-latest
+ ci:
+ uses: europeana/metis-actions/.github/workflows/ci.yml@main
+ with:
+ sonar_organization: europeana
+ sonar_project_key: europeana_metis-framework
+ secrets:
+ SONAR_TOKEN: ${{ secrets.METIS_SONAR_TOKEN }}
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Set up JDK 11
- uses: actions/setup-java@v3
- with:
- java-version: '11'
- distribution: 'temurin'
- cache: maven
- - name: Prepare Settings Deploy
- run: >
- mkdir -p ~/.m2 &&
- echo "${REPOSITORY_ID}${REPOSITORY_USERNAME}${REPOSITORY_PASSWORD}" > ~/.m2/settings.xml
- env:
- REPOSITORY_ID: artifactory.eanadev.org
- REPOSITORY_USERNAME: ${{ secrets.REPOSITORY_USERNAME }}
- REPOSITORY_PASSWORD: ${{ secrets.REPOSITORY_PASSWORD }}
- - name: Deploy
- run: >
- mvn --batch-mode
- -Dmaven.test.skip=true
- -Dmaven.install.skip=true
- deploy
+ deploy-artifacts:
+ needs: ci
+ uses: europeana/metis-actions/.github/workflows/deploy-artifacts.yml@main
+ secrets:
+ ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
+ ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
+
+ docker-build_metis-authentication:
+ needs: ci
+ uses: europeana/metis-actions/.github/workflows/docker-build-push.yml@main
+ with:
+ docker-organization: europeana
+ docker-image-name: metis-authentication
+ project-path: metis-authentication/metis-authentication-rest/
+ secrets:
+ DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
+ DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
+ docker-build_metis-core:
+ needs: ci
+ uses: europeana/metis-actions/.github/workflows/docker-build-push.yml@main
+ with:
+ docker-organization: europeana
+ docker-image-name: metis-core
+ project-path: metis-core/metis-core-rest/
+ secrets:
+ DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
+ DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
+ docker-build_metis-dereference:
+ needs: ci
+ uses: europeana/metis-actions/.github/workflows/docker-build-push.yml@main
+ with:
+ docker-organization: europeana
+ docker-image-name: metis-dereference
+ project-path: metis-dereference/metis-dereference-rest/
+ secrets:
+ DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
+ DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
+ docker-build_metis-repository:
+ needs: ci
+ uses: europeana/metis-actions/.github/workflows/docker-build-push.yml@main
+ with:
+ docker-organization: europeana
+ docker-image-name: metis-repository
+ project-path: metis-repository/metis-repository-rest/
+ secrets:
+ DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
+ DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000..df5709a597
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,26 @@
+name: RELEASE
+
+on:
+ workflow_dispatch:
+ inputs:
+ release-version:
+ description: 'The version of the intended release, should be just a number'
+ required: true
+ new-snapshot-version:
+ description: 'The version of the intended new snapshot'
+ required: true
+ commit-hash-branch:
+ description: 'The commit hash or branch to use as a base for the merge and release(default: develop)'
+ required: false
+ default: develop
+
+jobs:
+ ci-release:
+ uses: europeana/metis-actions/.github/workflows/release.yml@main
+ with:
+ release-version: ${{ github.event.inputs.release-version }}
+ commit-hash-branch: ${{ github.event.inputs.commit-hash-branch }}
+ new-snapshot-version: ${{ github.event.inputs.new-snapshot-version }}
+ target-merge-branch: master
+ secrets:
+ METIS_PERSONAL_ACCESS_TOKEN: ${{ secrets.METIS_PERSONAL_ACCESS_TOKEN }}
diff --git a/.run/metis-authentication(.properties).run.xml b/.run/metis-authentication(.properties).run.xml
index 0acd17f9f5..c086d4b414 100644
--- a/.run/metis-authentication(.properties).run.xml
+++ b/.run/metis-authentication(.properties).run.xml
@@ -6,6 +6,7 @@
+
diff --git a/.run/metis-core (.properties).run.xml b/.run/metis-core (.properties).run.xml
index d368520e91..bc96545a9e 100644
--- a/.run/metis-core (.properties).run.xml
+++ b/.run/metis-core (.properties).run.xml
@@ -8,6 +8,7 @@
+
diff --git a/.run/metis-dereference(.properties).run.xml b/.run/metis-dereference(.properties).run.xml
index 95beeaa833..4ec05005ed 100644
--- a/.run/metis-dereference(.properties).run.xml
+++ b/.run/metis-dereference(.properties).run.xml
@@ -6,6 +6,7 @@
+
diff --git a/.run/metis-repository (.properties).run.xml b/.run/metis-repository (.properties).run.xml
new file mode 100644
index 0000000000..5a71d54331
--- /dev/null
+++ b/.run/metis-repository (.properties).run.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/metis-authentication/metis-authentication-common/pom.xml b/metis-authentication/metis-authentication-common/pom.xml
index e31aa05b0b..f0c7832a5f 100644
--- a/metis-authentication/metis-authentication-common/pom.xml
+++ b/metis-authentication/metis-authentication-common/pom.xml
@@ -4,7 +4,7 @@
metis-authenticationeu.europeana.metis
- 9
+ 10metis-authentication-common
diff --git a/metis-authentication/metis-authentication-rest-client/pom.xml b/metis-authentication/metis-authentication-rest-client/pom.xml
index bcbeae7996..31a7a52b80 100644
--- a/metis-authentication/metis-authentication-rest-client/pom.xml
+++ b/metis-authentication/metis-authentication-rest-client/pom.xml
@@ -4,7 +4,7 @@
metis-authenticationeu.europeana.metis
- 9
+ 10metis-authentication-rest-client
diff --git a/metis-authentication/metis-authentication-rest/pom.xml b/metis-authentication/metis-authentication-rest/pom.xml
index 3dd426f3fb..f8d1761485 100644
--- a/metis-authentication/metis-authentication-rest/pom.xml
+++ b/metis-authentication/metis-authentication-rest/pom.xml
@@ -4,7 +4,7 @@
metis-authenticationeu.europeana.metis
- 9
+ 10metis-authentication-rest
diff --git a/metis-authentication/metis-authentication-service/pom.xml b/metis-authentication/metis-authentication-service/pom.xml
index 38fe7a1343..0d2525425f 100644
--- a/metis-authentication/metis-authentication-service/pom.xml
+++ b/metis-authentication/metis-authentication-service/pom.xml
@@ -4,7 +4,7 @@
metis-authenticationeu.europeana.metis
- 9
+ 10metis-authentication-service
diff --git a/metis-authentication/pom.xml b/metis-authentication/pom.xml
index 5d391a94ab..6e9669f9a6 100644
--- a/metis-authentication/pom.xml
+++ b/metis-authentication/pom.xml
@@ -4,7 +4,7 @@
metis-frameworkeu.europeana.metis
- 9
+ 10metis-authenticationpom
diff --git a/metis-common/metis-common-base/pom.xml b/metis-common/metis-common-base/pom.xml
new file mode 100644
index 0000000000..d00f4b9fd0
--- /dev/null
+++ b/metis-common/metis-common-base/pom.xml
@@ -0,0 +1,12 @@
+
+ 4.0.0
+
+ eu.europeana.metis
+ metis-common
+ 10
+
+ metis-common-base
+
+ metis-common-base
+
diff --git a/metis-common/metis-common-base/src/main/java/eu/europeana/metis/common/DatabaseProperties.java b/metis-common/metis-common-base/src/main/java/eu/europeana/metis/common/DatabaseProperties.java
new file mode 100644
index 0000000000..b2fafbe37f
--- /dev/null
+++ b/metis-common/metis-common-base/src/main/java/eu/europeana/metis/common/DatabaseProperties.java
@@ -0,0 +1,5 @@
+package eu.europeana.metis.common;
+
+public interface DatabaseProperties {
+
+}
diff --git a/metis-common/metis-common-base/src/main/java/eu/europeana/metis/common/SettingsHolder.java b/metis-common/metis-common-base/src/main/java/eu/europeana/metis/common/SettingsHolder.java
new file mode 100644
index 0000000000..c38403db4a
--- /dev/null
+++ b/metis-common/metis-common-base/src/main/java/eu/europeana/metis/common/SettingsHolder.java
@@ -0,0 +1,5 @@
+package eu.europeana.metis.common;
+
+public interface SettingsHolder {
+ DatabaseProperties getDatabaseProperties();
+}
diff --git a/metis-common/metis-common-mongo/pom.xml b/metis-common/metis-common-mongo/pom.xml
index 6c3cb78e5e..8ed299cf25 100644
--- a/metis-common/metis-common-mongo/pom.xml
+++ b/metis-common/metis-common-mongo/pom.xml
@@ -4,11 +4,16 @@
metis-commoneu.europeana.metis
- 9
+ 10metis-common-mongo
+
+ eu.europeana.metis
+ metis-common-base
+ ${project.version}
+ eu.europeana.metismetis-common-utils
diff --git a/metis-common/metis-common-mongo/src/main/java/eu/europeana/metis/mongo/connection/MongoProperties.java b/metis-common/metis-common-mongo/src/main/java/eu/europeana/metis/mongo/connection/MongoProperties.java
index e71ee360d4..63e63042ca 100644
--- a/metis-common/metis-common-mongo/src/main/java/eu/europeana/metis/mongo/connection/MongoProperties.java
+++ b/metis-common/metis-common-mongo/src/main/java/eu/europeana/metis/mongo/connection/MongoProperties.java
@@ -3,6 +3,7 @@
import com.mongodb.MongoCredential;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
+import eu.europeana.metis.common.DatabaseProperties;
import eu.europeana.metis.network.InetAddressUtil;
import java.net.InetSocketAddress;
import java.util.ArrayList;
@@ -18,7 +19,7 @@
*
* @param The type of exception thrown when the properties are not valid.
*/
-public class MongoProperties {
+public class MongoProperties implements DatabaseProperties {
// Exception creator
private final Function exceptionCreator;
diff --git a/metis-common/metis-common-network/pom.xml b/metis-common/metis-common-network/pom.xml
index 22a0ea6da6..db63871a0e 100644
--- a/metis-common/metis-common-network/pom.xml
+++ b/metis-common/metis-common-network/pom.xml
@@ -4,7 +4,7 @@
metis-commoneu.europeana.metis
- 9
+ 10metis-common-network
diff --git a/metis-common/metis-common-solr/pom.xml b/metis-common/metis-common-solr/pom.xml
index 84aedaefb5..72a6a753d9 100644
--- a/metis-common/metis-common-solr/pom.xml
+++ b/metis-common/metis-common-solr/pom.xml
@@ -4,11 +4,16 @@
metis-commoneu.europeana.metis
- 9
+ 10metis-common-solr
+
+ eu.europeana.metis
+ metis-common-base
+ ${project.version}
+ eu.europeana.metismetis-common-network
diff --git a/metis-common/metis-common-solr/src/main/java/eu/europeana/metis/solr/connection/SolrProperties.java b/metis-common/metis-common-solr/src/main/java/eu/europeana/metis/solr/connection/SolrProperties.java
index a6f49a6800..bdd51b0bf5 100644
--- a/metis-common/metis-common-solr/src/main/java/eu/europeana/metis/solr/connection/SolrProperties.java
+++ b/metis-common/metis-common-solr/src/main/java/eu/europeana/metis/solr/connection/SolrProperties.java
@@ -1,5 +1,6 @@
package eu.europeana.metis.solr.connection;
+import eu.europeana.metis.common.DatabaseProperties;
import eu.europeana.metis.network.InetAddressUtil;
import java.net.InetSocketAddress;
import java.net.URI;
@@ -14,7 +15,7 @@
*
* @param The type of exception thrown when the properties are not valid.
*/
-public class SolrProperties {
+public class SolrProperties implements DatabaseProperties {
// Default settings
private static final int DEFAULT_ZOOKEEPER_TIMEOUT_IN_SECONDS = 30;
diff --git a/metis-common/metis-common-utils/pom.xml b/metis-common/metis-common-utils/pom.xml
index 32c96b9e9a..463f1c2ca5 100644
--- a/metis-common/metis-common-utils/pom.xml
+++ b/metis-common/metis-common-utils/pom.xml
@@ -4,7 +4,7 @@
metis-commoneu.europeana.metis
- 9
+ 10metis-common-utils
diff --git a/metis-common/metis-common-zoho/pom.xml b/metis-common/metis-common-zoho/pom.xml
index e25445e206..41affd18c3 100644
--- a/metis-common/metis-common-zoho/pom.xml
+++ b/metis-common/metis-common-zoho/pom.xml
@@ -4,7 +4,7 @@
metis-commoneu.europeana.metis
- 9
+ 10metis-common-zoho
diff --git a/metis-common/pom.xml b/metis-common/pom.xml
index bb4eaa3cb6..a9b6a28048 100644
--- a/metis-common/pom.xml
+++ b/metis-common/pom.xml
@@ -4,12 +4,13 @@
metis-frameworkeu.europeana.metis
- 9
+ 10metis-commonpom
+ metis-common-basemetis-common-utilsmetis-common-mongometis-common-solr
diff --git a/metis-core/metis-core-common/pom.xml b/metis-core/metis-core-common/pom.xml
index e068a31f64..084264d9ad 100644
--- a/metis-core/metis-core-common/pom.xml
+++ b/metis-core/metis-core-common/pom.xml
@@ -4,7 +4,7 @@
metis-coreeu.europeana.metis
- 9
+ 10metis-core-common
diff --git a/metis-core/metis-core-rest/pom.xml b/metis-core/metis-core-rest/pom.xml
index 6dfac25eca..5e726932f1 100644
--- a/metis-core/metis-core-rest/pom.xml
+++ b/metis-core/metis-core-rest/pom.xml
@@ -4,7 +4,7 @@
metis-coreeu.europeana.metis
- 9
+ 10metis-core-rest
diff --git a/metis-core/metis-core-rest/src/main/java/eu/europeana/metis/core/rest/config/ECloudConfig.java b/metis-core/metis-core-rest/src/main/java/eu/europeana/metis/core/rest/config/ECloudConfig.java
index 62d45fa76b..970f239b13 100644
--- a/metis-core/metis-core-rest/src/main/java/eu/europeana/metis/core/rest/config/ECloudConfig.java
+++ b/metis-core/metis-core-rest/src/main/java/eu/europeana/metis/core/rest/config/ECloudConfig.java
@@ -41,7 +41,7 @@ public ECloudConfig(ConfigurationPropertiesHolder propertiesHolder) {
@Bean
DataSetServiceClient dataSetServiceClient() {
- dataSetServiceClient = new DataSetServiceClient(propertiesHolder.getEcloudBaseUrl(), null,
+ dataSetServiceClient = new DataSetServiceClient(propertiesHolder.getEcloudBaseUrl(),
propertiesHolder.getEcloudUsername(), propertiesHolder.getEcloudPassword(),
propertiesHolder.getDpsConnectTimeoutInMillisecs(),
propertiesHolder.getDpsReadTimeoutInMillisecs());
@@ -50,7 +50,7 @@ DataSetServiceClient dataSetServiceClient() {
@Bean
RecordServiceClient recordServiceClient() {
- recordServiceClient = new RecordServiceClient(propertiesHolder.getEcloudBaseUrl(), null,
+ recordServiceClient = new RecordServiceClient(propertiesHolder.getEcloudBaseUrl(),
propertiesHolder.getEcloudUsername(), propertiesHolder.getEcloudPassword(),
propertiesHolder.getDpsConnectTimeoutInMillisecs(),
propertiesHolder.getDpsReadTimeoutInMillisecs());
@@ -59,7 +59,7 @@ RecordServiceClient recordServiceClient() {
@Bean
FileServiceClient fileServiceClient() {
- fileServiceClient = new FileServiceClient(propertiesHolder.getEcloudBaseUrl(), null,
+ fileServiceClient = new FileServiceClient(propertiesHolder.getEcloudBaseUrl(),
propertiesHolder.getEcloudUsername(), propertiesHolder.getEcloudPassword(),
propertiesHolder.getDpsConnectTimeoutInMillisecs(),
propertiesHolder.getDpsReadTimeoutInMillisecs());
diff --git a/metis-core/metis-core-service/pom.xml b/metis-core/metis-core-service/pom.xml
index 1fd2c1e5bf..92e1e87af1 100644
--- a/metis-core/metis-core-service/pom.xml
+++ b/metis-core/metis-core-service/pom.xml
@@ -4,7 +4,7 @@
metis-coreeu.europeana.metis
- 9
+ 10metis-core-service
diff --git a/metis-core/pom.xml b/metis-core/pom.xml
index d1fae52415..76129fe3f4 100644
--- a/metis-core/pom.xml
+++ b/metis-core/pom.xml
@@ -4,7 +4,7 @@
metis-frameworkeu.europeana.metis
- 9
+ 10metis-corepom
diff --git a/metis-dereference/metis-dereference-common/pom.xml b/metis-dereference/metis-dereference-common/pom.xml
index b03ae65b3d..28523fe221 100644
--- a/metis-dereference/metis-dereference-common/pom.xml
+++ b/metis-dereference/metis-dereference-common/pom.xml
@@ -4,7 +4,7 @@
metis-dereferenceeu.europeana.metis
- 9
+ 10metis-dereference-common
diff --git a/metis-dereference/metis-dereference-import/pom.xml b/metis-dereference/metis-dereference-import/pom.xml
index d8bcc4c701..a5cbf00079 100644
--- a/metis-dereference/metis-dereference-import/pom.xml
+++ b/metis-dereference/metis-dereference-import/pom.xml
@@ -1,9 +1,10 @@
-
+metis-dereferenceeu.europeana.metis
- 9
+ 104.0.0metis-dereference-import
diff --git a/metis-dereference/metis-dereference-rest/pom.xml b/metis-dereference/metis-dereference-rest/pom.xml
index 687e95c22f..3c0ce3bc8b 100644
--- a/metis-dereference/metis-dereference-rest/pom.xml
+++ b/metis-dereference/metis-dereference-rest/pom.xml
@@ -4,7 +4,7 @@
metis-dereferenceeu.europeana.metis
- 9
+ 10metis-dereference-rest
diff --git a/metis-dereference/metis-dereference-service/pom.xml b/metis-dereference/metis-dereference-service/pom.xml
index a97094e57d..afdb8ab720 100644
--- a/metis-dereference/metis-dereference-service/pom.xml
+++ b/metis-dereference/metis-dereference-service/pom.xml
@@ -4,7 +4,7 @@
metis-dereferenceeu.europeana.metis
- 9
+ 10metis-dereference-service
diff --git a/metis-dereference/pom.xml b/metis-dereference/pom.xml
index f9f963200b..0af1f8402b 100644
--- a/metis-dereference/pom.xml
+++ b/metis-dereference/pom.xml
@@ -1,9 +1,10 @@
-
+4.0.0eu.europeana.metis
- 9
+ 10metis-framework
diff --git a/metis-enrichment/metis-enrichment-client/pom.xml b/metis-enrichment/metis-enrichment-client/pom.xml
index b56a432acf..fc17b5735d 100644
--- a/metis-enrichment/metis-enrichment-client/pom.xml
+++ b/metis-enrichment/metis-enrichment-client/pom.xml
@@ -4,7 +4,7 @@
metis-enrichmenteu.europeana.metis
- 9
+ 10metis-enrichment-clientjar
diff --git a/metis-enrichment/metis-enrichment-client/src/main/java/eu/europeana/enrichment/rest/client/EnrichmentWorkerImpl.java b/metis-enrichment/metis-enrichment-client/src/main/java/eu/europeana/enrichment/rest/client/EnrichmentWorkerImpl.java
index bf8843a0f2..c78e7eddf6 100644
--- a/metis-enrichment/metis-enrichment-client/src/main/java/eu/europeana/enrichment/rest/client/EnrichmentWorkerImpl.java
+++ b/metis-enrichment/metis-enrichment-client/src/main/java/eu/europeana/enrichment/rest/client/EnrichmentWorkerImpl.java
@@ -76,6 +76,7 @@ public ProcessedResult process(final InputStream inputStream, Set
} catch (SerializationException e) {
reports.add(Report
.buildEnrichmentError()
+ .withMessage("Error serializing rdf input")
.withException(e)
.build());
result = new ProcessedResult<>(null, reports);
@@ -108,6 +109,7 @@ public ProcessedResult process(final String inputString, Set modes
} catch (SerializationException e) {
reports.add(Report
.buildEnrichmentError()
+ .withMessage("Error serializing rdf input")
.withValue(inputString)
.withException(e)
.build());
diff --git a/metis-enrichment/metis-enrichment-client/src/main/java/eu/europeana/enrichment/rest/client/enrichment/EnricherImpl.java b/metis-enrichment/metis-enrichment-client/src/main/java/eu/europeana/enrichment/rest/client/enrichment/EnricherImpl.java
index 8aaac09e60..d01aa44ed0 100644
--- a/metis-enrichment/metis-enrichment-client/src/main/java/eu/europeana/enrichment/rest/client/enrichment/EnricherImpl.java
+++ b/metis-enrichment/metis-enrichment-client/src/main/java/eu/europeana/enrichment/rest/client/enrichment/EnricherImpl.java
@@ -64,7 +64,7 @@ public EnricherImpl(RecordParser recordParser, EntityResolver entityResolver, En
private static HttpStatus containsWarningStatus(String message) {
for (HttpStatus status : WARNING_STATUSES) {
- if (message.contains(status.toString())) {
+ if (message != null && message.contains(status.toString())) {
return status;
}
}
@@ -132,14 +132,15 @@ public Pair
\ No newline at end of file
+
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/FullBeanPublisher.java b/metis-indexing/src/main/java/eu/europeana/indexing/FullBeanPublisher.java
index 2c6045ae50..fa51fa80f5 100644
--- a/metis-indexing/src/main/java/eu/europeana/indexing/FullBeanPublisher.java
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/FullBeanPublisher.java
@@ -11,45 +11,51 @@
import com.mongodb.MongoSocketException;
import eu.europeana.corelib.definitions.edm.beans.FullBean;
import eu.europeana.corelib.definitions.edm.beans.IdBean;
-import eu.europeana.metis.mongo.dao.RecordDao;
import eu.europeana.corelib.solr.bean.impl.FullBeanImpl;
import eu.europeana.indexing.exception.IndexerRelatedIndexingException;
import eu.europeana.indexing.exception.IndexingException;
+import eu.europeana.indexing.exception.PublishToSolrIndexingException;
import eu.europeana.indexing.exception.RecordRelatedIndexingException;
import eu.europeana.indexing.exception.SetupRelatedIndexingException;
import eu.europeana.indexing.fullbean.RdfToFullBeanConverter;
import eu.europeana.indexing.mongo.FullBeanUpdater;
+import eu.europeana.indexing.solr.EdmLabel;
import eu.europeana.indexing.solr.SolrDocumentPopulator;
import eu.europeana.indexing.utils.RdfWrapper;
import eu.europeana.indexing.utils.TriConsumer;
+import eu.europeana.metis.mongo.dao.RecordDao;
import eu.europeana.metis.mongo.dao.RecordRedirectDao;
import java.io.IOException;
+import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.MapSolrParams;
/**
- * Publisher for Full Beans (instances of {@link FullBeanImpl}) that makes them accessible and
- * searchable for external agents.
+ * Publisher for Full Beans (instances of {@link FullBeanImpl}) that makes them accessible and searchable for external agents.
*
* @author jochen
*/
-class FullBeanPublisher {
+public class FullBeanPublisher {
private static final String REDIRECT_PUBLISH_ERROR = "Could not publish the redirection changes.";
private static final String MONGO_SERVER_PUBLISH_ERROR = "Could not publish to Mongo server.";
private static final String SOLR_SERVER_PUBLISH_ERROR = "Could not publish to Solr server.";
+ private static final String SOLR_SERVER_PUBLISH_RETRY_ERROR = "Could not publish to Solr server after retry.";
private static final String SOLR_SERVER_SEARCH_ERROR = "Could not search Solr server.";
private static final TriConsumer> EMPTY_PREPROCESSOR = (created, updated, recordDateAndCreationDate) -> {
@@ -68,8 +74,8 @@ class FullBeanPublisher {
* @param edmMongoClient The Mongo persistence.
* @param recordRedirectDao The record redirect dao
* @param solrServer The searchable persistence.
- * @param preserveUpdateAndCreateTimesFromRdf This determines whether this publisher will use the
- * updated and created times from the incoming RDFs, or whether it computes its own.
+ * @param preserveUpdateAndCreateTimesFromRdf This determines whether this publisher will use the updated and created times from
+ * the incoming RDFs, or whether it computes its own.
*/
FullBeanPublisher(RecordDao edmMongoClient, RecordRedirectDao recordRedirectDao,
SolrClient solrServer, boolean preserveUpdateAndCreateTimesFromRdf) {
@@ -83,10 +89,10 @@ class FullBeanPublisher {
* @param edmMongoClient The Mongo persistence.
* @param recordRedirectDao The record redirect dao
* @param solrServer The searchable persistence.
- * @param preserveUpdateAndCreateTimesFromRdf This determines whether this publisher will use the
- * updated and created times from the incoming RDFs, or whether it computes its own.
- * @param fullBeanConverterSupplier Supplies an instance of {@link RdfToFullBeanConverter} used to
- * parse strings to instances of {@link FullBeanImpl}. Will be called once during every publish.
+ * @param preserveUpdateAndCreateTimesFromRdf This determines whether this publisher will use the updated and created times from
+ * the incoming RDFs, or whether it computes its own.
+ * @param fullBeanConverterSupplier Supplies an instance of {@link RdfToFullBeanConverter} used to parse strings to instances of
+ * {@link FullBeanImpl}. Will be called once during every publish.
*/
FullBeanPublisher(RecordDao edmMongoClient, RecordRedirectDao recordRedirectDao,
SolrClient solrServer, boolean preserveUpdateAndCreateTimesFromRdf,
@@ -168,31 +174,120 @@ public void publish(RdfWrapper rdf, Date recordDate, List datasetIdsToRe
private void publish(RdfWrapper rdf, Date recordDate, List datasetIdsToRedirectFrom,
boolean performRedirects) throws IndexingException {
- // Convert RDF to Full Bean.
- final RdfToFullBeanConverter fullBeanConverter = fullBeanConverterSupplier.get();
- final FullBeanImpl fullBean = fullBeanConverter.convertRdfToFullBean(rdf);
+ final FullBeanImpl fullBean = convertRDFToFullBean(rdf);
- // Provide the preprocessor: this will set the created and updated timestamps as needed.
- final TriConsumer> fullBeanPreprocessor =
- preserveUpdateAndCreateTimesFromRdf ? EMPTY_PREPROCESSOR
- : (FullBeanPublisher::setUpdateAndCreateTime);
+ final TriConsumer> fullBeanPreprocessor = providePreprocessor();
- // Perform redirection
- final List> recordsForRedirection;
+ final List> recordsForRedirection = performRedirection(rdf,
+ recordDate, datasetIdsToRedirectFrom, performRedirects);
+
+ final FullBeanImpl savedFullBean = publishToMongo(recordDate, fullBean, fullBeanPreprocessor,
+ recordsForRedirection);
+
+ publishToSolrFinal(rdf, savedFullBean);
+ }
+
+ /**
+ * Publishes an RDF only to mongo server
+ *
+ * @param rdf RDF to publish.
+ * @param recordDate The date that would represent the created/updated date of a record
+ * @throws IndexingException which can be one of:
+ *
+ *
{@link IndexerRelatedIndexingException} In case an error occurred during publication.
+ *
{@link SetupRelatedIndexingException} in case an error occurred during indexing setup
+ *
{@link RecordRelatedIndexingException} in case an error occurred related to record
+ * contents
+ *
+ */
+ public void publishMongo(RdfWrapper rdf, Date recordDate) throws IndexingException {
+ final FullBeanImpl fullBean = convertRDFToFullBean(rdf);
+
+ final TriConsumer> fullBeanPreprocessor = providePreprocessor();
+
+ publishToMongo(recordDate, fullBean, fullBeanPreprocessor, Collections.emptyList());
+ }
+
+ /**
+ * Publishes an RDF to solr server
+ *
+ * @param rdf RDF to publish.
+ * @param recordDate The date that would represent the created/updated date of a record
+ * @throws IndexingException which can be one of:
+ *
+ *
{@link IndexerRelatedIndexingException} In case an error occurred during publication.
+ *
{@link SetupRelatedIndexingException} in case an error occurred during indexing setup
+ *
{@link RecordRelatedIndexingException} in case an error occurred related to record
+ * contents
+ *
+ */
+ public void publishSolr(RdfWrapper rdf, Date recordDate) throws IndexingException {
+ final FullBeanImpl fullBean = convertRDFToFullBean(rdf);
+ if (!preserveUpdateAndCreateTimesFromRdf) {
+ Date createdDate;
+ if (rdf.getAbout() == null) {
+ createdDate = recordDate;
+ } else {
+ final String solrQuery = String.format("%s:\"%s\"", EdmLabel.EUROPEANA_ID, ClientUtils.escapeQueryChars(rdf.getAbout()));
+ final Map queryParamMap = new HashMap<>();
+ queryParamMap.put("q", solrQuery);
+ queryParamMap.put("fl", EdmLabel.TIMESTAMP_CREATED + "," + EdmLabel.EUROPEANA_ID);
+ SolrDocumentList solrDocuments = getExistingDocuments(queryParamMap);
+ createdDate = (Date) solrDocuments.stream()
+ .map(document -> document.getFieldValue(EdmLabel.TIMESTAMP_CREATED.toString()))
+ .collect(Collectors.toList())
+ .stream().findFirst().orElse(recordDate);
+ }
+ setUpdateAndCreateTime(null, fullBean, Pair.of(recordDate, createdDate));
+ }
+ publishToSolrFinal(rdf, fullBean);
+ }
+
+ private SolrDocumentList getExistingDocuments(Map queryParamMap)
+ throws IndexerRelatedIndexingException, RecordRelatedIndexingException {
+ SolrDocumentList solrDocuments;
try {
- recordsForRedirection = RecordRedirectsUtil
- .checkAndApplyRedirects(recordRedirectDao, rdf, recordDate, datasetIdsToRedirectFrom,
- performRedirects, this::getSolrDocuments);
+ // Found
+ solrDocuments = getSolrDocuments(queryParamMap);
} catch (RuntimeException e) {
- throw new RecordRelatedIndexingException(REDIRECT_PUBLISH_ERROR, e);
+ //Not found or an error use empty list of documents
+ solrDocuments = new SolrDocumentList();
+ }
+ return solrDocuments;
+ }
+
+ private TriConsumer> providePreprocessor() {
+ // Provide the preprocessor: this will set the created and updated timestamps as needed.
+ return preserveUpdateAndCreateTimesFromRdf ? EMPTY_PREPROCESSOR
+ : (FullBeanPublisher::setUpdateAndCreateTime);
+ }
+
+ private void publishToSolrFinal(RdfWrapper rdf, FullBeanImpl savedFullBean) throws RecordRelatedIndexingException {
+ // Publish to Solr
+ try {
+ retryableExternalRequestForNetworkExceptions(() -> {
+ try {
+ publishToSolr(rdf, savedFullBean);
+ } catch (IndexingException e) {
+ throw new PublishToSolrIndexingException(SOLR_SERVER_PUBLISH_ERROR, e);
+ }
+ return null;
+ });
+ } catch (Exception e) {
+ throw new RecordRelatedIndexingException(SOLR_SERVER_PUBLISH_RETRY_ERROR, e);
}
+ }
+ private FullBeanImpl publishToMongo(Date recordDate, FullBeanImpl fullBean,
+ TriConsumer> fullBeanPreprocessor,
+ List> recordsForRedirection)
+ throws SetupRelatedIndexingException, IndexerRelatedIndexingException, RecordRelatedIndexingException {
// Publish to Mongo
final FullBeanImpl savedFullBean;
try {
savedFullBean = new FullBeanUpdater(fullBeanPreprocessor).update(fullBean, recordDate,
recordsForRedirection.stream().map(Pair::getValue).min(Comparator.naturalOrder())
- .orElse(null), edmMongoClient);
+ .orElse(null), edmMongoClient);
} catch (MongoIncompatibleDriverException | MongoConfigurationException | MongoSecurityException e) {
throw new SetupRelatedIndexingException(MONGO_SERVER_PUBLISH_ERROR, e);
} catch (MongoSocketException | MongoClientException | MongoInternalException | MongoInterruptedException e) {
@@ -200,20 +295,27 @@ private void publish(RdfWrapper rdf, Date recordDate, List datasetIdsToR
} catch (RuntimeException e) {
throw new RecordRelatedIndexingException(MONGO_SERVER_PUBLISH_ERROR, e);
}
+ return savedFullBean;
+ }
- // Publish to Solr
+ private List> performRedirection(RdfWrapper rdf, Date recordDate, List datasetIdsToRedirectFrom,
+ boolean performRedirects) throws IndexingException {
+ // Perform redirection
+ final List> recordsForRedirection;
try {
- retryableExternalRequestForNetworkExceptions(() -> {
- try {
- publishToSolr(rdf, savedFullBean);
- } catch (IndexingException e) {
- throw new RuntimeException(e);
- }
- return null;
- });
- } catch (Exception e) {
- throw new RecordRelatedIndexingException(SOLR_SERVER_PUBLISH_ERROR, e);
+ recordsForRedirection = RecordRedirectsUtil
+ .checkAndApplyRedirects(recordRedirectDao, rdf, recordDate, datasetIdsToRedirectFrom,
+ performRedirects, this::getSolrDocuments);
+ } catch (RuntimeException e) {
+ throw new RecordRelatedIndexingException(REDIRECT_PUBLISH_ERROR, e);
}
+ return recordsForRedirection;
+ }
+
+ private FullBeanImpl convertRDFToFullBean(RdfWrapper rdf) {
+ // Convert RDF to Full Bean.
+ final RdfToFullBeanConverter fullBeanConverter = fullBeanConverterSupplier.get();
+ return fullBeanConverter.convertRdfToFullBean(rdf);
}
private void publishToSolr(RdfWrapper rdf, FullBeanImpl fullBean) throws IndexingException {
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/IndexerImpl.java b/metis-indexing/src/main/java/eu/europeana/indexing/IndexerImpl.java
index cbfbe2f96b..80566f5f46 100644
--- a/metis-indexing/src/main/java/eu/europeana/indexing/IndexerImpl.java
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/IndexerImpl.java
@@ -32,7 +32,7 @@
*
* @author jochen
*/
-class IndexerImpl implements Indexer {
+public class IndexerImpl implements Indexer {
private static final Logger LOGGER = LoggerFactory.getLogger(IndexerImpl.class);
@@ -207,7 +207,7 @@ public long countRecords(String datasetId) {
* @author jochen
*/
@FunctionalInterface
- interface IndexingSupplier {
+ public interface IndexingSupplier {
/**
* Gets a result.
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/IndexingSettings.java b/metis-indexing/src/main/java/eu/europeana/indexing/IndexingSettings.java
index d70f957b87..3eb4a4cb9c 100644
--- a/metis-indexing/src/main/java/eu/europeana/indexing/IndexingSettings.java
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/IndexingSettings.java
@@ -1,5 +1,8 @@
package eu.europeana.indexing;
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullFieldName;
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullMessage;
+
import com.mongodb.ServerAddress;
import eu.europeana.indexing.exception.SetupRelatedIndexingException;
import eu.europeana.metis.mongo.connection.MongoProperties;
@@ -44,12 +47,12 @@ public void addMongoHost(InetSocketAddress host) throws SetupRelatedIndexingExce
* @throws SetupRelatedIndexingException In case the provided value is null.
*/
public void setMongoDatabaseName(String mongoDatabaseName) throws SetupRelatedIndexingException {
- this.mongoDatabaseName = nonNull(mongoDatabaseName, "mongoDatabaseName");
+ this.mongoDatabaseName = nonNullFieldName(mongoDatabaseName, "mongoDatabaseName");
}
public void setRecordRedirectDatabaseName(String recordRedirectDatabaseName)
throws SetupRelatedIndexingException {
- this.recordRedirectDatabaseName = nonNull(recordRedirectDatabaseName,
+ this.recordRedirectDatabaseName = nonNullFieldName(recordRedirectDatabaseName,
"recordRedirectDatabaseName");
}
@@ -186,10 +189,7 @@ public List getMongoHosts() throws SetupRelatedIndexingException
* @throws SetupRelatedIndexingException In case no Mongo database name was set.
*/
public String getMongoDatabaseName() throws SetupRelatedIndexingException {
- if (mongoDatabaseName == null) {
- throw new SetupRelatedIndexingException("Please provide a Mongo database name.");
- }
- return mongoDatabaseName;
+ return nonNullMessage(mongoDatabaseName, "Please provide a Mongo database name.");
}
/**
@@ -219,12 +219,4 @@ public List getZookeeperHosts() {
public SolrProperties getSolrProperties() {
return this.solrProperties;
}
-
- private static T nonNull(T value, String fieldName) throws SetupRelatedIndexingException {
- if (value == null) {
- throw new SetupRelatedIndexingException(
- String.format("Value '%s' cannot be null.", fieldName));
- }
- return value;
- }
}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/SettingsConnectionProvider.java b/metis-indexing/src/main/java/eu/europeana/indexing/SettingsConnectionProvider.java
index 4a685fd6c1..22e36be1ae 100644
--- a/metis-indexing/src/main/java/eu/europeana/indexing/SettingsConnectionProvider.java
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/SettingsConnectionProvider.java
@@ -1,5 +1,7 @@
package eu.europeana.indexing;
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullMessage;
+
import com.mongodb.MongoConfigurationException;
import com.mongodb.MongoIncompatibleDriverException;
import com.mongodb.MongoSecurityException;
@@ -7,8 +9,8 @@
import com.mongodb.client.MongoClient;
import eu.europeana.indexing.exception.IndexerRelatedIndexingException;
import eu.europeana.indexing.exception.SetupRelatedIndexingException;
-import eu.europeana.metis.mongo.dao.RecordDao;
import eu.europeana.metis.mongo.connection.MongoClientProvider;
+import eu.europeana.metis.mongo.dao.RecordDao;
import eu.europeana.metis.mongo.dao.RecordRedirectDao;
import eu.europeana.metis.solr.client.CompoundSolrClient;
import eu.europeana.metis.solr.connection.SolrClientProvider;
@@ -44,13 +46,9 @@ public final class SettingsConnectionProvider implements AbstractConnectionProvi
* @throws SetupRelatedIndexingException In case the connections could not be set up.
* @throws IndexerRelatedIndexingException In case the connection could not be established.
*/
- public SettingsConnectionProvider(IndexingSettings settings)
- throws SetupRelatedIndexingException, IndexerRelatedIndexingException {
-
+ public SettingsConnectionProvider(IndexingSettings settings) throws SetupRelatedIndexingException, IndexerRelatedIndexingException {
// Sanity check
- if (settings == null) {
- throw new SetupRelatedIndexingException("The provided settings object is null.");
- }
+ settings = nonNullMessage(settings,"The provided settings object is null.");
// Create Solr and Zookeeper connections.
this.solrClient = new SolrClientProvider<>(settings.getSolrProperties()).createSolrClient();
@@ -67,9 +65,7 @@ public SettingsConnectionProvider(IndexingSettings settings)
}
}
- private static MongoClient createMongoClient(IndexingSettings settings)
- throws SetupRelatedIndexingException {
-
+ private static MongoClient createMongoClient(IndexingSettings settings) throws SetupRelatedIndexingException {
// Perform logging unecessary
if (LOGGER.isInfoEnabled()) {
LOGGER.info(
@@ -85,8 +81,7 @@ private static MongoClient createMongoClient(IndexingSettings settings)
return new MongoClientProvider<>(settings.getMongoProperties()).createMongoClient();
}
- private static RecordDao setUpEdmMongoConnection(IndexingSettings settings,
- MongoClient client)
+ private static RecordDao setUpEdmMongoConnection(IndexingSettings settings, MongoClient client)
throws SetupRelatedIndexingException {
try {
return new RecordDao(client, settings.getMongoDatabaseName());
@@ -96,8 +91,7 @@ private static RecordDao setUpEdmMongoConnection(IndexingSettings settings,
}
private static RecordRedirectDao setUpRecordRedirectDaoConnection(IndexingSettings settings,
- MongoClient client)
- throws SetupRelatedIndexingException {
+ MongoClient client) throws SetupRelatedIndexingException {
try {
RecordRedirectDao recordRedirectDao = null;
if (StringUtils.isNotBlank(settings.getRecordRedirectDatabaseName())) {
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/SimpleIndexer.java b/metis-indexing/src/main/java/eu/europeana/indexing/SimpleIndexer.java
new file mode 100644
index 0000000000..4bacdcb788
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/SimpleIndexer.java
@@ -0,0 +1,26 @@
+package eu.europeana.indexing;
+
+import eu.europeana.indexing.exception.IndexingException;
+import eu.europeana.metis.schema.jibx.RDF;
+
+/**
+ * The interface Simple indexer.
+ */
+public interface SimpleIndexer {
+
+ /**
+ * Index record.
+ *
+ * @param record the record
+ * @throws IndexingException the indexing exception
+ */
+ void indexRecord(RDF record) throws IndexingException;
+
+ /**
+ * Index record.
+ *
+ * @param record the record
+ * @throws IndexingException the indexing exception
+ */
+ void indexRecord(String record) throws IndexingException;
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/SimpleIndexerFactory.java b/metis-indexing/src/main/java/eu/europeana/indexing/SimpleIndexerFactory.java
new file mode 100644
index 0000000000..4e73a71f98
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/SimpleIndexerFactory.java
@@ -0,0 +1,30 @@
+package eu.europeana.indexing;
+
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.indexing.mongo.MongoIndexer;
+import eu.europeana.indexing.mongo.MongoIndexingSettings;
+import eu.europeana.indexing.solr.SolrIndexer;
+import eu.europeana.indexing.solr.SolrIndexingSettings;
+import eu.europeana.metis.common.SettingsHolder;
+
+/**
+ * The type Simple indexer factory.
+ */
+public class SimpleIndexerFactory {
+
+ /**
+ * Gets an indexer.
+ * @param settings the settings can be either a SolrProperties or MongoProperties object.
+ * @return the indexer {@link SimpleIndexer} pointing to mongo or solr.
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public SimpleIndexer getIndexer(SettingsHolder settings) throws SetupRelatedIndexingException {
+ if (settings instanceof SolrIndexingSettings) {
+ return new SolrIndexer((SolrIndexingSettings) settings);
+ } else if (settings instanceof MongoIndexingSettings) {
+ return new MongoIndexer((MongoIndexingSettings) settings);
+ } else {
+ throw new IllegalArgumentException("Invalid property configuration");
+ }
+ }
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/exception/PublishToSolrIndexingException.java b/metis-indexing/src/main/java/eu/europeana/indexing/exception/PublishToSolrIndexingException.java
new file mode 100644
index 0000000000..6583134685
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/exception/PublishToSolrIndexingException.java
@@ -0,0 +1,8 @@
+package eu.europeana.indexing.exception;
+
+public class PublishToSolrIndexingException extends RuntimeException {
+
+ private static final long serialVersionUID = -276105897439704602L;
+
+ public PublishToSolrIndexingException(String message, Exception cause) { super(message, cause);}
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoConnectionProvider.java b/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoConnectionProvider.java
new file mode 100644
index 0000000000..2d67920013
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoConnectionProvider.java
@@ -0,0 +1,91 @@
+package eu.europeana.indexing.mongo;
+
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullFieldName;
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullMessage;
+
+import com.mongodb.MongoConfigurationException;
+import com.mongodb.MongoIncompatibleDriverException;
+import com.mongodb.MongoSecurityException;
+import com.mongodb.ServerAddress;
+import com.mongodb.client.MongoClient;
+import eu.europeana.indexing.AbstractConnectionProvider;
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.metis.mongo.connection.MongoClientProvider;
+import eu.europeana.metis.mongo.connection.MongoProperties;
+import eu.europeana.metis.mongo.dao.RecordDao;
+import eu.europeana.metis.mongo.dao.RecordRedirectDao;
+import java.lang.invoke.MethodHandles;
+import java.util.stream.Collectors;
+import org.apache.solr.client.solrj.SolrClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The type Mongo connection provider.
+ */
+public final class MongoConnectionProvider implements AbstractConnectionProvider {
+
+ private static final String MONGO_SERVER_SETUP_ERROR = "Could not set up connection to Mongo server.";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private final MongoClient mongoClient;
+ private final RecordDao recordDao;
+ private final RecordRedirectDao recordRedirectDao;
+
+ /**
+ * Instantiates a new Mongo connection provider.
+ *
+ * @param settings the mongo settings
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public MongoConnectionProvider(MongoIndexingSettings settings) throws SetupRelatedIndexingException {
+
+ // Sanity check
+ settings = nonNullMessage(settings, "The provided mongo settings object is null.");
+
+ try {
+ this.mongoClient = createMongoClient(settings);
+ this.recordDao = new RecordDao(this.mongoClient, nonNullFieldName(settings.getMongoDatabaseName(), "mongoDatabaseName"));
+ this.recordRedirectDao = new RecordRedirectDao(this.mongoClient,
+ nonNullFieldName(settings.getRecordRedirectDatabaseName(), "recordRedirectDatabaseName"));
+ } catch (MongoIncompatibleDriverException | MongoConfigurationException | MongoSecurityException e) {
+ throw new SetupRelatedIndexingException(MONGO_SERVER_SETUP_ERROR, e);
+ }
+ }
+
+ private static MongoClient createMongoClient(MongoIndexingSettings settings) throws SetupRelatedIndexingException {
+ MongoProperties properties = (MongoProperties) settings.getDatabaseProperties();
+ if (LOGGER.isInfoEnabled()) {
+ LOGGER.info("Connecting to Mongo hosts: [{}], database [{}], with{} authentication, with{} SSL. ",
+ properties.getMongoHosts().stream().map(ServerAddress::toString)
+ .collect(Collectors.joining(", ")),
+ settings.getMongoDatabaseName(),
+ properties.getMongoCredentials() == null ? "out" : "",
+ properties.mongoEnableSsl() ? "" : "out");
+ }
+
+ return new MongoClientProvider<>(properties).createMongoClient();
+ }
+
+ @Override
+ public SolrClient getSolrClient() {
+ return null;
+ }
+
+ @Override
+ public RecordDao getRecordDao() {
+ return recordDao;
+ }
+
+ @Override
+ public RecordRedirectDao getRecordRedirectDao() {
+ return recordRedirectDao;
+ }
+
+ @Override
+ public void close() {
+ mongoClient.close();
+ }
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoIndexer.java b/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoIndexer.java
new file mode 100644
index 0000000000..00890d52a5
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoIndexer.java
@@ -0,0 +1,82 @@
+package eu.europeana.indexing.mongo;
+
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullIllegal;
+
+import eu.europeana.indexing.AbstractConnectionProvider;
+import eu.europeana.indexing.FullBeanPublisher;
+import eu.europeana.indexing.IndexerImpl.IndexingSupplier;
+import eu.europeana.indexing.SimpleIndexer;
+import eu.europeana.indexing.exception.IndexerRelatedIndexingException;
+import eu.europeana.indexing.exception.IndexingException;
+import eu.europeana.indexing.exception.RecordRelatedIndexingException;
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.indexing.fullbean.StringToFullBeanConverter;
+import eu.europeana.indexing.utils.RdfWrapper;
+import eu.europeana.metis.schema.jibx.RDF;
+import java.lang.invoke.MethodHandles;
+import java.time.Instant;
+import java.util.Date;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Creates a record for indexing in Mongo
+ */
+public class MongoIndexer implements SimpleIndexer {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private final AbstractConnectionProvider connectionProvider;
+ private final IndexingSupplier stringToRdfConverterSupplier;
+
+
+ /**
+ * Instantiates a new Mongo indexer.
+ *
+ * @param settings the mongo indexer settings
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public MongoIndexer(MongoIndexingSettings settings) throws SetupRelatedIndexingException {
+ this.connectionProvider = new MongoConnectionProvider(settings);
+ this.stringToRdfConverterSupplier = StringToFullBeanConverter::new;
+ }
+
+ /**
+ * Index to Mongo a rdf record object
+ *
+ * @param rdfRecord An RDF record object
+ * @throws IndexingException which can be one of:
+ *
+ *
{@link IndexerRelatedIndexingException} In case an error occurred during publication.
+ *
{@link SetupRelatedIndexingException} in case an error occurred during indexing setup
+ *
{@link RecordRelatedIndexingException} in case an error occurred related to record
+ * contents
+ *
+ */
+ @Override
+ public void indexRecord(RDF rdfRecord) throws IndexingException {
+ // Sanity checks
+ rdfRecord = nonNullIllegal(rdfRecord, "Input RDF cannot be null.");
+
+ LOGGER.info("Processing record {}", rdfRecord);
+ final FullBeanPublisher publisher = connectionProvider.getFullBeanPublisher(false);
+ publisher.publishMongo(new RdfWrapper(rdfRecord), Date.from(Instant.now()));
+ }
+
+ /**
+ * Index to Mongo a string rdf record
+ *
+ * @param stringRdfRecord A rdf record in string format
+ * @throws IndexingException which can be one of:
+ *
+ *
{@link IndexerRelatedIndexingException} In case an error occurred during publication.
+ *
{@link SetupRelatedIndexingException} in case an error occurred during indexing setup
+ *
{@link RecordRelatedIndexingException} in case an error occurred related to record
+ * contents
+ *
+ */
+ @Override
+ public void indexRecord(String stringRdfRecord) throws IndexingException {
+ final RDF rdfRecord = stringToRdfConverterSupplier.get().convertStringToRdf(stringRdfRecord);
+ indexRecord(rdfRecord);
+ }
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoIndexingSettings.java b/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoIndexingSettings.java
new file mode 100644
index 0000000000..a3a7187705
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/mongo/MongoIndexingSettings.java
@@ -0,0 +1,74 @@
+package eu.europeana.indexing.mongo;
+
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullFieldName;
+
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.metis.common.DatabaseProperties;
+import eu.europeana.metis.common.SettingsHolder;
+import eu.europeana.metis.mongo.connection.MongoProperties;
+
+/**
+ * The type Mongo indexing settings.
+ */
+public class MongoIndexingSettings implements SettingsHolder {
+
+ private String mongoDatabaseName;
+ private String recordRedirectDatabaseName;
+ private final MongoProperties mongoProperties;
+
+ /**
+ * Instantiates a new Mongo indexing settings.
+ *
+ * @param properties the mongo connection properties
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public MongoIndexingSettings(MongoProperties properties) throws SetupRelatedIndexingException {
+ this.mongoProperties = nonNullFieldName(properties, "properties");
+ }
+
+ /**
+ * Gets mongo database name.
+ *
+ * @return the mongo database name
+ */
+ public String getMongoDatabaseName() {
+ return mongoDatabaseName;
+ }
+
+ /**
+ * Sets mongo database name.
+ *
+ * @param mongoDatabaseName the mongo database name
+ */
+ public void setMongoDatabaseName(String mongoDatabaseName) {
+ this.mongoDatabaseName = mongoDatabaseName;
+ }
+
+ /**
+ * Gets record redirect database name.
+ *
+ * @return the record redirect database name
+ */
+ public String getRecordRedirectDatabaseName() {
+ return recordRedirectDatabaseName;
+ }
+
+ /**
+ * Sets record redirect database name.
+ *
+ * @param recordRedirectDatabaseName the record redirect database name
+ */
+ public void setRecordRedirectDatabaseName(String recordRedirectDatabaseName) {
+ this.recordRedirectDatabaseName = recordRedirectDatabaseName;
+ }
+
+ /**
+ * Gets mongo properties.
+ *
+ * @return the mongo properties
+ */
+ @Override
+ public DatabaseProperties getDatabaseProperties() {
+ return mongoProperties;
+ }
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrConnectionProvider.java b/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrConnectionProvider.java
new file mode 100644
index 0000000000..aad7cc5f0e
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrConnectionProvider.java
@@ -0,0 +1,50 @@
+package eu.europeana.indexing.solr;
+
+import eu.europeana.indexing.AbstractConnectionProvider;
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.metis.mongo.dao.RecordDao;
+import eu.europeana.metis.mongo.dao.RecordRedirectDao;
+import eu.europeana.metis.solr.client.CompoundSolrClient;
+import eu.europeana.metis.solr.connection.SolrClientProvider;
+import eu.europeana.metis.solr.connection.SolrProperties;
+import java.io.IOException;
+import org.apache.solr.client.solrj.SolrClient;
+
+/**
+ * The type Solr connection provider.
+ */
+public final class SolrConnectionProvider implements AbstractConnectionProvider {
+
+ private final CompoundSolrClient solrClient;
+
+ /**
+ * Instantiates a new Solr connection provider.
+ *
+ * @param settings the solr settings
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public SolrConnectionProvider(SolrIndexingSettings settings)
+ throws SetupRelatedIndexingException {
+ this.solrClient = new SolrClientProvider<>((SolrProperties)settings.getDatabaseProperties()).createSolrClient();
+ }
+
+ @Override
+ public SolrClient getSolrClient() {
+ return this.solrClient.getSolrClient();
+ }
+
+ @Override
+ public RecordDao getRecordDao() {
+ return null;
+ }
+
+ @Override
+ public RecordRedirectDao getRecordRedirectDao() {
+ return null;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.solrClient.close();
+ }
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrIndexer.java b/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrIndexer.java
new file mode 100644
index 0000000000..a777efd31c
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrIndexer.java
@@ -0,0 +1,81 @@
+package eu.europeana.indexing.solr;
+
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullIllegal;
+
+import eu.europeana.indexing.AbstractConnectionProvider;
+import eu.europeana.indexing.FullBeanPublisher;
+import eu.europeana.indexing.IndexerImpl.IndexingSupplier;
+import eu.europeana.indexing.SimpleIndexer;
+import eu.europeana.indexing.exception.IndexerRelatedIndexingException;
+import eu.europeana.indexing.exception.IndexingException;
+import eu.europeana.indexing.exception.RecordRelatedIndexingException;
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.indexing.fullbean.StringToFullBeanConverter;
+import eu.europeana.indexing.utils.RdfWrapper;
+import eu.europeana.metis.schema.jibx.RDF;
+import java.lang.invoke.MethodHandles;
+import java.time.Instant;
+import java.util.Date;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Creates a record for indexing in Solr
+ */
+public class SolrIndexer implements SimpleIndexer {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private final AbstractConnectionProvider connectionProvider;
+ private final IndexingSupplier stringToRdfConverterSupplier;
+
+ /**
+ * Create SolrIndexer object indicating solr connection properties
+ *
+ * @param settings solr settings
+ * @throws SetupRelatedIndexingException in case an error occurred during indexing setup
+ */
+ public SolrIndexer(SolrIndexingSettings settings) throws SetupRelatedIndexingException {
+ this.connectionProvider = new SolrConnectionProvider(settings);
+ this.stringToRdfConverterSupplier = StringToFullBeanConverter::new;
+ }
+
+ /**
+ * Index to Solr a rdf record object
+ *
+ * @param rdfRecord An RDF record object
+ * @throws IndexingException which can be one of:
+ *
+ *
{@link IndexerRelatedIndexingException} In case an error occurred during publication.
+ *
{@link SetupRelatedIndexingException} in case an error occurred during indexing setup
+ *
{@link RecordRelatedIndexingException} in case an error occurred related to record
+ * contents
+ *
+ */
+ @Override
+ public void indexRecord(RDF rdfRecord) throws IndexingException {
+ // Sanity checks
+ rdfRecord = nonNullIllegal(rdfRecord, "Input RDF cannot be null.");
+
+ LOGGER.info("Processing {} record...", rdfRecord);
+ final FullBeanPublisher publisher = connectionProvider.getFullBeanPublisher(false);
+ publisher.publishSolr(new RdfWrapper(rdfRecord), Date.from(Instant.now()));
+ }
+
+ /**
+ * Index to Solr a string rdf record
+ *
+ * @param stringRdfRecord A rdf record in string format
+ * @throws IndexingException which can be one of:
+ *
+ *
{@link IndexerRelatedIndexingException} In case an error occurred during publication.
+ *
{@link SetupRelatedIndexingException} in case an error occurred during indexing setup
+ *
{@link RecordRelatedIndexingException} in case an error occurred related to record
+ * contents
+ *
+ */
+ @Override
+ public void indexRecord(String stringRdfRecord) throws IndexingException {
+ final RDF rdfRecord = stringToRdfConverterSupplier.get().convertStringToRdf(stringRdfRecord);
+ indexRecord(rdfRecord);
+ }
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrIndexingSettings.java b/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrIndexingSettings.java
new file mode 100644
index 0000000000..71b6ba7d97
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/solr/SolrIndexingSettings.java
@@ -0,0 +1,36 @@
+package eu.europeana.indexing.solr;
+
+import static eu.europeana.indexing.utils.IndexingSettingsUtils.nonNullFieldName;
+
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.metis.common.DatabaseProperties;
+import eu.europeana.metis.common.SettingsHolder;
+import eu.europeana.metis.solr.connection.SolrProperties;
+
+/**
+ * The type Solr indexing settings.
+ */
+public class SolrIndexingSettings implements SettingsHolder {
+
+ private final SolrProperties solrProperties;
+
+ /**
+ * Instantiates a new Solr indexing settings.
+ *
+ * @param properties the solr connection properties
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public SolrIndexingSettings(SolrProperties properties) throws SetupRelatedIndexingException {
+ this.solrProperties = nonNullFieldName(properties, "properties");
+ }
+
+ /**
+ * Gets solr properties.
+ *
+ * @return the solr properties
+ */
+ @Override
+ public DatabaseProperties getDatabaseProperties() {
+ return solrProperties;
+ }
+}
diff --git a/metis-indexing/src/main/java/eu/europeana/indexing/utils/IndexingSettingsUtils.java b/metis-indexing/src/main/java/eu/europeana/indexing/utils/IndexingSettingsUtils.java
new file mode 100644
index 0000000000..b775627cc4
--- /dev/null
+++ b/metis-indexing/src/main/java/eu/europeana/indexing/utils/IndexingSettingsUtils.java
@@ -0,0 +1,58 @@
+package eu.europeana.indexing.utils;
+
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+
+/**
+ * The type Indexing settings utils.
+ */
+public class IndexingSettingsUtils {
+
+ /**
+ * Non null field name t.
+ *
+ * @param the type parameter
+ * @param value the value
+ * @param fieldName the field name
+ * @return the t
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public static T nonNullFieldName(T value, String fieldName) throws SetupRelatedIndexingException {
+ if (value == null) {
+ throw new SetupRelatedIndexingException(
+ String.format("Value '%s' cannot be null.", fieldName));
+ }
+ return value;
+ }
+
+ /**
+ * Non null message t.
+ *
+ * @param the type parameter
+ * @param value the value
+ * @param message the message
+ * @return the t
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public static T nonNullMessage(T value, String message) throws SetupRelatedIndexingException {
+ if (value == null) {
+ throw new SetupRelatedIndexingException(message);
+ }
+ return value;
+ }
+
+ /**
+ * Non null illegal t.
+ *
+ * @param the type parameter
+ * @param value the value
+ * @param message the message
+ * @return the t
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ public static T nonNullIllegal(T value, String message) throws SetupRelatedIndexingException {
+ if (value == null) {
+ throw new IllegalArgumentException(message);
+ }
+ return value;
+ }
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/SimpleIndexerFactoryTest.java b/metis-indexing/src/test/java/eu/europeana/indexing/SimpleIndexerFactoryTest.java
new file mode 100644
index 0000000000..7426567e47
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/SimpleIndexerFactoryTest.java
@@ -0,0 +1,42 @@
+package eu.europeana.indexing;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.indexing.mongo.MongoIndexer;
+import eu.europeana.indexing.mongo.MongoIndexingSettings;
+import eu.europeana.indexing.solr.SolrIndexer;
+import eu.europeana.indexing.solr.SolrIndexingSettings;
+import eu.europeana.metis.mongo.connection.MongoProperties;
+import eu.europeana.metis.solr.connection.SolrProperties;
+import java.net.URI;
+import java.net.URISyntaxException;
+import org.junit.jupiter.api.Test;
+
+class SimpleIndexerFactoryTest {
+
+ private final SimpleIndexerFactory simpleIndexerFactory = new SimpleIndexerFactory();
+
+ @Test
+ void getSolrIndexer() throws SetupRelatedIndexingException, URISyntaxException {
+ SolrProperties solrProperties = new SolrProperties<>(SetupRelatedIndexingException::new);
+ solrProperties.addSolrHost(new URI("http://localhost:8983"));
+ SolrIndexingSettings settings = new SolrIndexingSettings(solrProperties);
+
+ assertTrue(simpleIndexerFactory.getIndexer(settings) instanceof SolrIndexer);
+ }
+
+ @Test
+ void getMongoIndexer() throws SetupRelatedIndexingException {
+ MongoProperties mongoProperties = new MongoProperties<>(SetupRelatedIndexingException::new);
+ mongoProperties.setMongoHosts(new String[]{"localhost"},new int[]{27001});
+ MongoIndexingSettings settings = new MongoIndexingSettings(mongoProperties);
+ settings.setMongoDatabaseName("recordDB");
+ settings.setRecordRedirectDatabaseName("recordRedirectDB");
+
+ assertTrue(simpleIndexerFactory.getIndexer(settings) instanceof MongoIndexer);
+ assertEquals("recordDB", settings.getMongoDatabaseName());
+ assertEquals("recordRedirectDB", settings.getRecordRedirectDatabaseName());
+ }
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/base/IndexingTestUtils.java b/metis-indexing/src/test/java/eu/europeana/indexing/base/IndexingTestUtils.java
new file mode 100644
index 0000000000..e3d1a25d2c
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/base/IndexingTestUtils.java
@@ -0,0 +1,56 @@
+package eu.europeana.indexing.base;
+
+import eu.europeana.indexing.exception.IndexerRelatedIndexingException;
+import eu.europeana.indexing.exception.RecordRelatedIndexingException;
+import java.io.IOException;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.SolrDocumentList;
+
+/**
+ * The type Indexing test utils.
+ */
+public class IndexingTestUtils {
+
+ private static final String SOLR_SERVER_SEARCH_ERROR = "Could not search Solr server.";
+
+ /**
+ * Gets resource file content.
+ *
+ * @param fileName the file name
+ * @return the resource file content
+ */
+ public static String getResourceFileContent(String fileName) {
+ try {
+ return new String(IndexingTestUtils.class.getClassLoader().getResourceAsStream(fileName).readAllBytes());
+ } catch (IOException ioException) {
+ return "";
+ }
+ }
+
+ /**
+ * Gets solr documents.
+ *
+ * @param solrServer the solr server
+ * @param query the query
+ * @return the solr documents
+ * @throws IndexerRelatedIndexingException the indexer related indexing exception
+ * @throws RecordRelatedIndexingException the record related indexing exception
+ */
+ public static SolrDocumentList getSolrDocuments(SolrClient solrServer, String query)
+ throws IndexerRelatedIndexingException, RecordRelatedIndexingException {
+ SolrQuery solrQuery = new SolrQuery();
+ solrQuery.set("q", query);
+ QueryResponse response;
+ try {
+ response = solrServer.query(solrQuery);
+ } catch (SolrServerException e) {
+ throw new IndexerRelatedIndexingException(SOLR_SERVER_SEARCH_ERROR, e);
+ } catch (IOException e) {
+ throw new RecordRelatedIndexingException(SOLR_SERVER_SEARCH_ERROR, e);
+ }
+ return response.getResults();
+ }
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/base/MongoDBContainerIT.java b/metis-indexing/src/test/java/eu/europeana/indexing/base/MongoDBContainerIT.java
new file mode 100644
index 0000000000..b73575ec5b
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/base/MongoDBContainerIT.java
@@ -0,0 +1,52 @@
+package eu.europeana.indexing.base;
+
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.testcontainers.containers.MongoDBContainer;
+
+/**
+ * The type Mongo db container it.
+ */
+public class MongoDBContainerIT extends TestContainer {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBContainerIT.class);
+ private static MongoDBContainer mongoDBContainer;
+ /**
+ * The constant MONGO_VERSION.
+ */
+ public static final String MONGO_VERSION = "mongo:5.0.12";
+
+ /**
+ * Instantiates a new Mongo db container it.
+ */
+ public MongoDBContainerIT() {
+ mongoDBContainer = new MongoDBContainer(MONGO_VERSION);
+ mongoDBContainer.start();
+
+ logConfiguration();
+ }
+
+ @Override
+ public void logConfiguration() {
+ LOGGER.info("MongoDB container created:");
+ LOGGER.info("Url: {}", mongoDBContainer.getReplicaSetUrl());
+ LOGGER.info("Host: {}", mongoDBContainer.getHost());
+ LOGGER.info("Port: {}", mongoDBContainer.getFirstMappedPort());
+ }
+
+ @Override
+ public void dynamicProperties(DynamicPropertyRegistry registry) {
+ registry.add("mongo.application-name", () -> "mongo-testcontainer-test");
+ registry.add("mongo.db", () -> "test");
+ registry.add("mongo.redirect.db", () -> "test_redirect");
+ registry.add("mongo.hosts", mongoDBContainer::getHost);
+ registry.add("mongo.port", mongoDBContainer::getFirstMappedPort);
+ }
+
+ @Override
+ public void runScripts(List scripts) {
+ //nothing to do at this moment
+ }
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/base/SolrContainerIT.java b/metis-indexing/src/test/java/eu/europeana/indexing/base/SolrContainerIT.java
new file mode 100644
index 0000000000..0e5b0dce82
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/base/SolrContainerIT.java
@@ -0,0 +1,68 @@
+package eu.europeana.indexing.base;
+
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * The type Solr container it.
+ */
+public class SolrContainerIT extends TestContainer {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SolrContainerIT.class);
+
+ private static SolrMetisContainer solrContainer;
+ /**
+ * The constant SOLR_VERSION.
+ */
+ public static final String SOLR_VERSION = "solr:7.7.3-slim";
+ /**
+ * The constant SOLR_COLLECTION_NAME.
+ */
+ public static final String SOLR_COLLECTION_NAME = "solr_publish_test";
+
+
+ /**
+ * Instantiates a new Solr container it.
+ */
+ public SolrContainerIT() {
+ solrContainer = new SolrMetisContainer(DockerImageName.parse(SOLR_VERSION))
+ .withCollection(SOLR_COLLECTION_NAME)
+ .withConfiguration("test",
+ getClass().getClassLoader().getResource("solr/solrconfig.xml"))
+ //At the moment this container doesn't support solr.AliasingSearchHandler is a custom plugin
+ .withSchema(getClass().getClassLoader().getResource("solr/schema.xml"))
+ .withConfigSchemaFiles(List.of(getClass().getClassLoader().getResource("solr/enumsConfig.xml"),
+ getClass().getClassLoader().getResource("solr/query_aliases.xml"),
+ getClass().getClassLoader().getResource("solr/elevate.xml")));
+
+ solrContainer.start();
+
+ logConfiguration();
+ }
+
+ @Override
+ public void logConfiguration() {
+ LOGGER.info("Solr container created:");
+ LOGGER.info("Host: {}", solrContainer.getHost());
+ LOGGER.info("Port: {}", solrContainer.getSolrPort());
+ }
+
+ @Override
+ public void dynamicProperties(DynamicPropertyRegistry registry) {
+ registry.add("spring.data.solr.port", solrContainer::getSolrPort);
+ registry.add("spring.data.solr.host", solrContainer::getHost);
+
+ String solrUrlHost =
+ "http://" + solrContainer.getHost() + ":" + solrContainer.getSolrPort() + "/solr/" + SOLR_COLLECTION_NAME;
+
+ registry.add("metis.test.publish.solr.hosts", () -> solrUrlHost);
+ }
+
+ @Override
+ public void runScripts(List scripts) {
+ //nothing to do at this moment
+ }
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/base/SolrMetisContainer.java b/metis-indexing/src/test/java/eu/europeana/indexing/base/SolrMetisContainer.java
new file mode 100644
index 0000000000..3c24212045
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/base/SolrMetisContainer.java
@@ -0,0 +1,298 @@
+package eu.europeana.indexing.base;
+
+import com.github.dockerjava.api.command.InspectContainerResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.testcontainers.containers.Container;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.SolrClientUtils;
+import org.testcontainers.containers.SolrClientUtilsException;
+import org.testcontainers.containers.SolrContainerConfiguration;
+import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
+import org.testcontainers.shaded.okhttp3.HttpUrl;
+import org.testcontainers.shaded.okhttp3.MediaType;
+import org.testcontainers.shaded.okhttp3.OkHttpClient;
+import org.testcontainers.shaded.okhttp3.Request;
+import org.testcontainers.shaded.okhttp3.RequestBody;
+import org.testcontainers.shaded.okhttp3.Response;
+import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
+import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
+import org.testcontainers.utility.DockerImageName;
+
+
+/**
+ * The type Solr metis container.
+ */
+public class SolrMetisContainer extends GenericContainer {
+
+ /**
+ * The constant DEFAULT_TAG.
+ */
+ public static final String DEFAULT_TAG = "7.7.3";
+ /**
+ * The constant ZOOKEEPER_PORT.
+ */
+ public static final Integer ZOOKEEPER_PORT;
+ /**
+ * The constant SOLR_PORT.
+ */
+ public static final Integer SOLR_PORT;
+ private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("solr");
+ private static final OkHttpClient httpClient = new OkHttpClient();
+
+ static {
+ ZOOKEEPER_PORT = 9983;
+ SOLR_PORT = 8983;
+ }
+
+ private final SolrContainerConfiguration configuration;
+ private ArrayList configSchemaFiles;
+
+ /**
+ * Instantiates a new Solr metis container.
+ *
+ * @param dockerImageName the docker image name
+ */
+ public SolrMetisContainer(DockerImageName dockerImageName) {
+ super(dockerImageName);
+ dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
+ this.waitStrategy = (new LogMessageWaitStrategy()).withRegEx(".*o\\.e\\.j\\.s\\.Server Started.*").withStartupTimeout(
+ Duration.of(60L, ChronoUnit.SECONDS));
+ this.configuration = new SolrContainerConfiguration();
+ }
+
+ /**
+ * Upload configuration.
+ *
+ * @param hostname the hostname
+ * @param port the port
+ * @param configurationName the configuration name
+ * @param solrConfig the solr config
+ * @param solrSchema the solr schema
+ * @param configSchemaFiles the config schema files
+ * @throws URISyntaxException the uri syntax exception
+ * @throws IOException the io exception
+ */
+ public static void uploadConfiguration(String hostname, int port, String configurationName, URL solrConfig, URL solrSchema,
+ List configSchemaFiles) throws URISyntaxException, IOException {
+ Map parameters = new HashMap();
+ parameters.put("action", "UPLOAD");
+ parameters.put("name", configurationName);
+ HttpUrl url = generateSolrURL(hostname, port, Arrays.asList("admin", "configs"), parameters);
+ byte[] configurationZipFile = generateConfigZipFile(solrConfig, solrSchema, configSchemaFiles);
+ executePost(url, configurationZipFile);
+ }
+
+ private static void executePost(HttpUrl url, byte[] data) throws IOException {
+ RequestBody requestBody = data == null ? RequestBody.create(MediaType.parse("text/plain"), "")
+ : RequestBody.create(MediaType.parse("application/octet-stream"), data);
+ Request request = (new Request.Builder()).url(url).post(requestBody).build();
+ Response response = httpClient.newCall(request).execute();
+ if (!response.isSuccessful()) {
+ String responseBody = "";
+ if (response.body() != null) {
+ responseBody = response.body().string();
+ response.close();
+ }
+
+ throw new SolrClientUtilsException(response.code(), "Unable to upload binary\n" + responseBody);
+ } else {
+ if (response.body() != null) {
+ response.close();
+ }
+
+ }
+ }
+
+ private static HttpUrl generateSolrURL(String hostname, int port, List pathSegments, Map parameters) {
+ HttpUrl.Builder builder = new HttpUrl.Builder();
+ builder.scheme("http");
+ builder.host(hostname);
+ builder.port(port);
+ builder.addPathSegment("solr");
+ if (pathSegments != null) {
+ pathSegments.forEach(builder::addPathSegment);
+ }
+
+ parameters.forEach(builder::addQueryParameter);
+ return builder.build();
+ }
+
+ private static byte[] generateConfigZipFile(URL solrConfiguration, URL solrSchema, List configFiles) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ZipOutputStream zipOutputStream = new ZipOutputStream(bos);
+ zipOutputStream.putNextEntry(new ZipEntry("solrconfig.xml"));
+ IOUtils.copy(solrConfiguration.openStream(), zipOutputStream);
+ zipOutputStream.closeEntry();
+ if (solrSchema != null) {
+ zipOutputStream.putNextEntry(new ZipEntry("schema.xml"));
+ IOUtils.copy(solrSchema.openStream(), zipOutputStream);
+ zipOutputStream.closeEntry();
+ }
+ if (configFiles != null) {
+ configFiles.stream().forEach(url -> {
+ try {
+ zipOutputStream.putNextEntry(new ZipEntry(Paths.get(url.getPath()).getFileName().toString()));
+ IOUtils.copy(url.openStream(), zipOutputStream);
+ zipOutputStream.closeEntry();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ zipOutputStream.close();
+ return bos.toByteArray();
+ }
+
+ /**
+ * With zookeeper solr metis container.
+ *
+ * @param zookeeper the zookeeper
+ * @return the solr metis container
+ */
+ public SolrMetisContainer withZookeeper(boolean zookeeper) {
+ this.configuration.setZookeeper(zookeeper);
+ return this.self();
+ }
+
+ /**
+ * With collection solr metis container.
+ *
+ * @param collection the collection
+ * @return the solr metis container
+ */
+ public SolrMetisContainer withCollection(String collection) {
+ if (StringUtils.isEmpty(collection)) {
+ throw new IllegalArgumentException("Collection name must not be empty");
+ } else {
+ this.configuration.setCollectionName(collection);
+ return this.self();
+ }
+ }
+
+ /**
+ * With configuration solr metis container.
+ *
+ * @param name the name
+ * @param solrConfig the solr config
+ * @return the solr metis container
+ */
+ public SolrMetisContainer withConfiguration(String name, URL solrConfig) {
+ if (!StringUtils.isEmpty(name) && solrConfig != null) {
+ this.configuration.setConfigurationName(name);
+ this.configuration.setSolrConfiguration(solrConfig);
+ return this.self();
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * With schema solr metis container.
+ *
+ * @param schema the schema
+ * @return the solr metis container
+ */
+ public SolrMetisContainer withSchema(URL schema) {
+ this.configuration.setSolrSchema(schema);
+ return this.self();
+ }
+
+ /**
+ * With config schema files solr metis container.
+ *
+ * @param configFiles the config files
+ * @return the solr metis container
+ */
+ public SolrMetisContainer withConfigSchemaFiles(List configFiles) {
+ this.configSchemaFiles = new ArrayList<>(configFiles);
+ return this.self();
+ }
+
+ /**
+ * Gets solr port.
+ *
+ * @return the solr port
+ */
+ public int getSolrPort() {
+ return this.getMappedPort(SOLR_PORT);
+ }
+
+ /**
+ * Gets zookeeper port.
+ *
+ * @return the zookeeper port
+ */
+ public int getZookeeperPort() {
+ return this.getMappedPort(ZOOKEEPER_PORT);
+ }
+
+ @Override
+ public Set getLivenessCheckPortNumbers() {
+ return new HashSet(this.getSolrPort());
+ }
+
+ @Override
+ protected void containerIsStarted(InspectContainerResponse containerInfo) {
+ try {
+ if (!this.configuration.isZookeeper()) {
+ Container.ExecResult result = this.execInContainer(
+ "solr", "create_core", "-c", this.configuration.getCollectionName());
+ if (result.getExitCode() != 0) {
+ throw new IllegalStateException(
+ "Unable to create solr core:\nStdout: " + result.getStdout() + "\nStderr:" + result.getStderr());
+ }
+ } else {
+ if (StringUtils.isNotEmpty(this.configuration.getConfigurationName())) {
+ uploadConfiguration(this.getHost(), this.getSolrPort(), this.configuration.getConfigurationName(),
+ this.configuration.getSolrConfiguration(), this.configuration.getSolrSchema(), this.configSchemaFiles);
+ }
+
+ SolrClientUtils.createCollection(this.getHost(), this.getSolrPort(), this.configuration.getCollectionName(),
+ this.configuration.getConfigurationName());
+ }
+ } catch (Throwable containerThrowable) {
+ throw new RuntimeException(containerThrowable);
+ }
+ }
+
+ @Override
+ protected void configure() {
+ try {
+ if (this.configuration.getSolrSchema() != null && this.configuration.getSolrConfiguration() == null) {
+ throw new IllegalStateException("Solr needs to have a configuration is you want to use a schema");
+ } else {
+ String command = "solr -f";
+ this.addExposedPort(SOLR_PORT);
+ if (this.configuration.isZookeeper()) {
+ this.addExposedPort(ZOOKEEPER_PORT);
+ command = "-DzkRun -h localhost";
+ }
+
+ this.setCommand(command);
+ }
+ } catch (Throwable configureThrowable) {
+ throw configureThrowable;
+ }
+ }
+
+ @Override
+ protected void waitUntilContainerStarted() {
+ this.getWaitStrategy().waitUntilReady(this);
+ }
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainer.java b/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainer.java
new file mode 100644
index 0000000000..11702650b3
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainer.java
@@ -0,0 +1,33 @@
+package eu.europeana.indexing.base;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.DynamicPropertyRegistry;
+
+/**
+ * The type Test container.
+ */
+public abstract class TestContainer {
+ private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /**
+ * Log configuration.
+ */
+ abstract public void logConfiguration();
+
+ /**
+ * Dynamic properties.
+ *
+ * @param registry the registry
+ */
+ abstract public void dynamicProperties(DynamicPropertyRegistry registry);
+
+ /**
+ * Run scripts.
+ *
+ * @param scripts the scripts
+ */
+ abstract public void runScripts(List scripts);
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainerFactoryIT.java b/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainerFactoryIT.java
new file mode 100644
index 0000000000..db2da0c70d
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainerFactoryIT.java
@@ -0,0 +1,24 @@
+package eu.europeana.indexing.base;
+
+/**
+ * The type Test container factory it.
+ */
+public class TestContainerFactoryIT {
+
+ /**
+ * Gets container.
+ *
+ * @param type the type
+ * @return the container
+ */
+ public static TestContainer getContainer(TestContainerType type) {
+ switch (type) {
+ case MONGO:
+ return new MongoDBContainerIT();
+ case SOLR:
+ return new SolrContainerIT();
+ default:
+ throw new IllegalArgumentException("Pass a valid container type");
+ }
+ }
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainerType.java b/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainerType.java
new file mode 100644
index 0000000000..6b22ee62df
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/base/TestContainerType.java
@@ -0,0 +1,15 @@
+package eu.europeana.indexing.base;
+
+/**
+ * The enum Test container type.
+ */
+public enum TestContainerType {
+ /**
+ * Mongo test container type.
+ */
+ MONGO,
+ /**
+ * Solr test container type.
+ */
+ SOLR
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/mongo/MongoIndexerTest.java b/metis-indexing/src/test/java/eu/europeana/indexing/mongo/MongoIndexerTest.java
new file mode 100644
index 0000000000..d9848ed330
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/mongo/MongoIndexerTest.java
@@ -0,0 +1,181 @@
+package eu.europeana.indexing.mongo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.mongodb.client.MongoClient;
+import eu.europeana.corelib.definitions.edm.beans.FullBean;
+import eu.europeana.corelib.web.exception.EuropeanaException;
+import eu.europeana.indexing.base.IndexingTestUtils;
+import eu.europeana.indexing.base.TestContainer;
+import eu.europeana.indexing.base.TestContainerFactoryIT;
+import eu.europeana.indexing.base.TestContainerType;
+import eu.europeana.indexing.exception.IndexingException;
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.indexing.mongo.MongoIndexerTest.MongoIndexerLocalConfigTest;
+import eu.europeana.metis.mongo.connection.MongoClientProvider;
+import eu.europeana.metis.mongo.connection.MongoProperties;
+import eu.europeana.metis.mongo.dao.RecordDao;
+import eu.europeana.metis.schema.convert.RdfConversionUtils;
+import eu.europeana.metis.schema.convert.SerializationException;
+import eu.europeana.metis.schema.jibx.RDF;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * The type Mongo indexer test.
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = MongoIndexerLocalConfigTest.class)
+class MongoIndexerTest {
+
+ @Autowired
+ private MongoIndexer indexer;
+
+ @Autowired
+ private RecordDao recordDao;
+
+ /**
+ * Dynamic properties.
+ *
+ * @param registry the registry
+ */
+ @DynamicPropertySource
+ public static void dynamicProperties(DynamicPropertyRegistry registry) {
+ TestContainer mongoContainerIT = TestContainerFactoryIT.getContainer(TestContainerType.MONGO);
+ mongoContainerIT.dynamicProperties(registry);
+ }
+
+ /**
+ * Illegal argument exception test.
+ */
+ @Test
+ void IllegalArgumentExceptionTest() {
+ IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> indexer.indexRecord((RDF) null));
+ assertEquals("Input RDF cannot be null.", expected.getMessage());
+ }
+
+ /**
+ * Index record.
+ *
+ * @throws IndexingException the indexing exception
+ * @throws SerializationException the serialization exception
+ * @throws EuropeanaException the europeana exception
+ */
+ @Test
+ void indexRecord() throws IndexingException, SerializationException, EuropeanaException {
+ final RdfConversionUtils conversionUtils = new RdfConversionUtils();
+ final RDF inputRdf = conversionUtils.convertStringToRdf(
+ IndexingTestUtils.getResourceFileContent("europeana_record_to_sample_index_rdf.xml"));
+ indexer.indexRecord(inputRdf);
+
+ assertIndexedRecord("/50/_providedCHO_NL_BwdADRKF_2_62_7");
+ }
+
+ /**
+ * Test index record.
+ *
+ * @throws IndexingException the indexing exception
+ * @throws EuropeanaException the europeana exception
+ */
+ @Test
+ void testIndexRecord() throws IndexingException, EuropeanaException {
+ final String stringRdfRecord = IndexingTestUtils.getResourceFileContent("europeana_record_to_sample_index_string.xml");
+
+ indexer.indexRecord(stringRdfRecord);
+
+ assertIndexedRecord("/50/_providedCHO_NL_BwdADRKF_2_126_10");
+ }
+
+ private void assertIndexedRecord(String expectedId) throws EuropeanaException {
+ FullBean fullBean = recordDao.getFullBean(expectedId);
+ assertNotNull(fullBean);
+ assertEquals(expectedId, fullBean.getAbout());
+ }
+
+ /**
+ * The type Mongo indexer local config test.
+ */
+ @Configuration
+ static class MongoIndexerLocalConfigTest {
+
+ /**
+ * Mongo properties mongo properties.
+ *
+ * @param mongoHost the mongo host
+ * @param mongoPort the mongo port
+ * @return the mongo properties
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ @Bean
+ MongoProperties mongoProperties(@Value("${mongo.hosts}") String mongoHost,
+ @Value("${mongo.port}") int mongoPort) throws SetupRelatedIndexingException {
+ MongoProperties mongoProperties = new MongoProperties<>(SetupRelatedIndexingException::new);
+ mongoProperties.setMongoHosts(new String[]{mongoHost}, new int[]{mongoPort});
+ return mongoProperties;
+ }
+
+ /**
+ * Mongo client mongo client.
+ *
+ * @param mongoProperties the mongo properties
+ * @return the mongo client
+ * @throws Exception the exception
+ */
+ @Bean
+ MongoClient mongoClient(MongoProperties mongoProperties) throws Exception {
+ return new MongoClientProvider<>(mongoProperties).createMongoClient();
+ }
+
+ /**
+ * Mongo indexing settings mongo indexing settings.
+ *
+ * @param mongoProperties the mongo properties
+ * @param mongoDatabase the mongo database
+ * @param mongoRedirectDatabase the mongo redirect database
+ * @return the mongo indexing settings
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ @Bean
+ MongoIndexingSettings mongoIndexingSettings(MongoProperties mongoProperties, @Value("${mongo.db}") String mongoDatabase,
+ @Value("${mongo.redirect.db}") String mongoRedirectDatabase) throws SetupRelatedIndexingException {
+ MongoIndexingSettings mongoIndexingSettings = new MongoIndexingSettings(mongoProperties);
+ mongoIndexingSettings.setMongoDatabaseName(mongoDatabase);
+ mongoIndexingSettings.setRecordRedirectDatabaseName(mongoRedirectDatabase);
+ return mongoIndexingSettings;
+ }
+
+ /**
+ * Record dao record dao.
+ *
+ * @param mongoClient the mongo client
+ * @param settings the settings
+ * @return the record dao
+ */
+ @Bean
+ RecordDao recordDao(MongoClient mongoClient, MongoIndexingSettings settings) {
+ return new RecordDao(mongoClient, settings.getMongoDatabaseName());
+ }
+
+ /**
+ * Indexer mongo indexer.
+ *
+ * @param settings the settings
+ * @return the mongo indexer
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ @Bean
+ MongoIndexer indexer(MongoIndexingSettings settings) throws SetupRelatedIndexingException {
+ return new MongoIndexer(settings);
+ }
+ }
+}
diff --git a/metis-indexing/src/test/java/eu/europeana/indexing/solr/SolrIndexerTest.java b/metis-indexing/src/test/java/eu/europeana/indexing/solr/SolrIndexerTest.java
new file mode 100644
index 0000000000..134ba52106
--- /dev/null
+++ b/metis-indexing/src/test/java/eu/europeana/indexing/solr/SolrIndexerTest.java
@@ -0,0 +1,231 @@
+package eu.europeana.indexing.solr;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import eu.europeana.indexing.base.IndexingTestUtils;
+import eu.europeana.indexing.base.TestContainer;
+import eu.europeana.indexing.base.TestContainerFactoryIT;
+import eu.europeana.indexing.base.TestContainerType;
+import eu.europeana.indexing.exception.IndexerRelatedIndexingException;
+import eu.europeana.indexing.exception.IndexingException;
+import eu.europeana.indexing.exception.RecordRelatedIndexingException;
+import eu.europeana.indexing.exception.SetupRelatedIndexingException;
+import eu.europeana.indexing.solr.SolrIndexerTest.SolrIndexerLocalConfigTest;
+import eu.europeana.metis.schema.convert.RdfConversionUtils;
+import eu.europeana.metis.schema.convert.SerializationException;
+import eu.europeana.metis.schema.jibx.RDF;
+import eu.europeana.metis.solr.connection.SolrClientProvider;
+import eu.europeana.metis.solr.connection.SolrProperties;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.URI;
+import java.net.URISyntaxException;
+import org.apache.lucene.document.Document;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.util.ClientUtils;
+import org.apache.solr.common.SolrDocumentList;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * The type Solr indexer test.
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = SolrIndexerLocalConfigTest.class)
+class SolrIndexerTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Autowired
+ private SolrIndexer indexer;
+
+ @Autowired
+ private SolrClient solrClient;
+
+ /**
+ * Dynamic properties.
+ *
+ * @param registry the registry
+ */
+ @DynamicPropertySource
+ public static void dynamicProperties(DynamicPropertyRegistry registry) {
+ TestContainer solrContainerIT = TestContainerFactoryIT.getContainer(TestContainerType.SOLR);
+ solrContainerIT.dynamicProperties(registry);
+ }
+
+ /**
+ * Illegal argument exception test.
+ */
+ @Test
+ void IllegalArgumentExceptionTest() {
+ IllegalArgumentException expected = assertThrows(IllegalArgumentException.class, () -> indexer.indexRecord((RDF) null));
+ assertEquals("Input RDF cannot be null.", expected.getMessage());
+ }
+
+ /**
+ * Index record.
+ *
+ * @throws IndexingException the indexing exception
+ * @throws SerializationException the serialization exception
+ * @throws SolrServerException the solr server exception
+ * @throws IOException the io exception
+ */
+ @Test
+ void indexRecord() throws IndexingException, SerializationException, SolrServerException, IOException {
+ final RdfConversionUtils conversionUtils = new RdfConversionUtils();
+ final RDF inputRdf = conversionUtils.convertStringToRdf(
+ IndexingTestUtils.getResourceFileContent("europeana_record_to_sample_index_rdf.xml"));
+
+ indexer.indexRecord(inputRdf);
+
+ flushAndAssertDocumentInSolr("/50/_providedCHO_NL_BwdADRKF_2_62_7", 1);
+ }
+
+ /**
+ * Index record.
+ *
+ * @throws IndexingException the indexing exception
+ * @throws SerializationException the serialization exception
+ * @throws SolrServerException the solr server exception
+ * @throws IOException the io exception
+ */
+ @Test
+ void indexExistingDateRecord() throws IndexingException, SerializationException, SolrServerException, IOException {
+ final RdfConversionUtils conversionUtils = new RdfConversionUtils();
+ final RDF inputRdf = conversionUtils.convertStringToRdf(
+ IndexingTestUtils.getResourceFileContent("europeana_record_to_sample_index_dup_rdf.xml"));
+ // add record
+ indexer.indexRecord(inputRdf);
+
+ // commit changes
+ solrClient.commit();
+ final RDF inputRdf2 = conversionUtils.convertStringToRdf(
+ IndexingTestUtils.getResourceFileContent("europeana_record_to_sample_index_dup_rdf.xml"));
+
+ // add again same record to have created and update date
+ indexer.indexRecord(inputRdf2);
+
+ flushAndAssertDocumentInSolr("/50/_providedCHO_NL_BwdADRKF_2_82_5", 1);
+ }
+
+ /**
+ * Test index record.
+ *
+ * @throws IndexingException the indexing exception
+ * @throws SolrServerException the solr server exception
+ * @throws IOException the io exception
+ */
+ @Test
+ void testIndexRecord() throws IndexingException, SolrServerException, IOException {
+ final String stringRdfRecord = IndexingTestUtils.getResourceFileContent("europeana_record_to_sample_index_string.xml");
+
+ indexer.indexRecord(stringRdfRecord);
+
+ flushAndAssertDocumentInSolr("/50/_providedCHO_NL_BwdADRKF_2_126_10", 1);
+ }
+
+ private void flushAndAssertDocumentInSolr(String expectedId, int expectedSize)
+ throws SolrServerException, IOException, IndexerRelatedIndexingException, RecordRelatedIndexingException {
+ solrClient.commit();
+ final String solrQuery = String.format("%s:\"%s\"", EdmLabel.EUROPEANA_ID, ClientUtils.escapeQueryChars(expectedId));
+ SolrDocumentList documents = IndexingTestUtils.getSolrDocuments(solrClient, solrQuery);
+ LOGGER.info("documents");
+ documents.stream().forEach(document -> {
+ document.getFieldNames().stream().forEach(
+ field -> {
+ LOGGER.info("{}:{}",field,document.getFieldValue(field));
+ }
+ );
+ });
+ assertEquals(expectedSize, documents.size());
+ }
+
+ /**
+ * Index record empty record expect exception.
+ */
+ @Test
+ void indexRecordEmptyRecord_ExpectException() {
+ final RDF inputRdf = new RDF();
+ assertThrows(RecordRelatedIndexingException.class, () -> indexer.indexRecord(inputRdf));
+ }
+
+ /**
+ * Index record empty string record expect exception.
+ */
+ @Test
+ void indexRecordEmptyStringRecord_ExpectException() {
+ final String stringRdfRecord = "";
+ assertThrows(RecordRelatedIndexingException.class, () -> indexer.indexRecord(stringRdfRecord));
+ }
+
+ /**
+ * The type Solr indexer local config test.
+ */
+ @Configuration
+ static class SolrIndexerLocalConfigTest {
+
+ /**
+ * Solr properties solr properties.
+ *
+ * @param solrHost the solr host
+ * @return the solr properties
+ * @throws URISyntaxException the uri syntax exception
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ @Bean
+ SolrProperties solrProperties(@Value("${metis.test.publish.solr.hosts}") String solrHost)
+ throws URISyntaxException, SetupRelatedIndexingException {
+ SolrProperties solrProperties = new SolrProperties<>(SetupRelatedIndexingException::new);
+ solrProperties.addSolrHost(new URI(solrHost));
+ return solrProperties;
+ }
+
+ /**
+ * Solr indexing settings solr indexing settings.
+ *
+ * @param properties the properties
+ * @return the solr indexing settings
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ @Bean
+ SolrIndexingSettings solrIndexingSettings(SolrProperties properties) throws SetupRelatedIndexingException {
+ return new SolrIndexingSettings(properties);
+ }
+
+ /**
+ * Solr client solr client.
+ *
+ * @param properties the properties
+ * @return the solr client
+ * @throws Exception the exception
+ */
+ @Bean
+ SolrClient solrClient(SolrProperties properties) throws Exception {
+ return new SolrClientProvider<>(properties).createSolrClient().getSolrClient();
+ }
+
+ /**
+ * Indexer solr indexer.
+ *
+ * @param settings the settings
+ * @return the solr indexer
+ * @throws SetupRelatedIndexingException the setup related indexing exception
+ */
+ @Bean
+ SolrIndexer indexer(SolrIndexingSettings settings) throws SetupRelatedIndexingException {
+ return new SolrIndexer(settings);
+ }
+ }
+}
diff --git a/metis-indexing/src/test/resources/europeana_record_to_sample_index_dup_rdf.xml b/metis-indexing/src/test/resources/europeana_record_to_sample_index_dup_rdf.xml
new file mode 100644
index 0000000000..ced5311cd5
--- /dev/null
+++ b/metis-indexing/src/test/resources/europeana_record_to_sample_index_dup_rdf.xml
@@ -0,0 +1,233 @@
+
+
+
+
+ c1739a4bddc51ed13cbb57f9d911dae2 - 82-5
+
+ application/pdf
+ 13924049
+ 300
+
+
+ text/html
+ 0
+
+
+ Titus de Boer
+ Тит Брандсма
+ Titus de Boer
+ Titus de Boer
+ Titus de Boer
+ Titus de Boer
+ Tito de Boer
+ Titus de Boer
+ Titus de Boer
+ Titus de Boer
+ Tytus de Boer
+ Titus de Boer
+ Titus de Boer
+ Titus de Boer
+ Anno Sjoerd de Boer
+ F. Titus de Boer O.Carm.
+ Брандсма, Титус
+ Титус Брандсма
+ Брандсма, Тит
+ Tito de Boer
+ Beato tito de Boer
+ Anno Sjoerd Titus de Boer
+ Titus de Boer, O.Carm.
+ Anno Sjoerd de Boer
+ F. Titus de Boer O.Carm.
+ Titus de Boer
+ Anno Sjoerd de Boer
+ Bł. Tytus
+ Błogosławiony Tytus
+ Anno Sjoerd de Boer
+ F. Titus de Boer O.Carm.
+ Anno Sjoerd de Boer
+ T. de Boer
+ Tito de Boer
+ Titus de Boer, O.Carm.
+ F. Titus de Boer O.Carm.
+ P. Titus de Boer O.Carm.
+ Fra Titus de Boer O.Carm.
+ Anno Sjoerd de Boer
+ Niederländischer Theologe im Widerstand gegen den Nationalsozialismus
+ Nederländsk karmelitermunk, präst, och professor i filosofi
+ Dutch Carmelite friar, Catholic priest, professor of philosophy and saint
+ Sacerdote olandese
+ Prêtre carme néerlandais, canonisé le 15 mai 2022
+ Călugăr carmelit, profesor de filozofie, victimă a regimului nazist
+ Nederlands priester, martelaar en heilige
+ Frare carmelita holandès, sacerdot catòlic, professor de filosofia i esperantista
+ 1881-02-23
+ 1942-07-26
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1942/1942
+
+ 1942-01-01
+ 1942-12-31
+ 1942/1942
+
+
+ 1942~
+ approximate
+
+ 1942-01-01
+ 1942-12-31
+ 1942~
+
+
+ 20. Jahrhundert
+ 1900-luku
+ XX век
+ Século XX
+ 20 век
+ XX amžius
+ 20. stoljeće
+ 20. gadsimts
+ XXe siècle
+ 20. század
+ 20. storočie
+ 20. stoletje
+ 20ú haois
+ Segle XX
+ 1900-talet
+ 20ός αιώνας
+ 20th century
+ XX secolo
+ Siglo XX
+ 20. sajand
+ 20. století
+ XX. mendea
+ XX wiek
+ 20. århundrede
+ Secolul al XX-lea
+ 20e eeuw
+ 20:e århundradet
+ 20:e seklet
+ 1900-tal
+ 1900-talet (århundrade)
+ 1900-talet (sekel)
+ 20 век
+ Século 20
+ Século vinte
+ Periodo 1901-2000
+ Ciclo (1901-2000)
+ 20th-century
+ 20th-century
+ Twentieth century
+ The past century
+ History, 20th Century
+ XX Century
+ Novecento
+ 20° secolo
+ '900
+ Novecento
+ 20e siècle
+ 1901-01-01
+ 2000-12-31
+
+
+
+
+
+
+
+
+
+
+
+
+ de Boer, Anno Sjoerd, 1881-1942
+ de Boer, Titus, father, 1881-1942
+ de Boer, Titus, Saint, 1881-1942
+
+
+
+
+
+
+ de Boer, Titus, 1881-1942
+
+
+ archival material
+
+ The physical materials that make up the mass of an archive. Distinguished from AAT "archives (groupings)."
+ De fysieke materialen die samen een archief vormen. In de AAT te onderscheiden van 'archieven (groepen)'.
+ archival materials
+ archiefmateriaal
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #providedCHO_NL-BwdADRKF-2_62-7
+ nld
+
+ true
+
+
+
+
+
+ de Boer, Titus, O.Carm., 1881-1942
+
+ [ca. 1942]
+ 62-7
+ dut
+ Teksten (fotokopieën, vijf bladzijden), door
+ Titus geschreven voor zijn boek over de H. Theresia. (originelen aanwezig in
+ het N.C.I. te Boxmeer).
+
+ 1942/1942
+ 1 omslag
+
+
+ false
+
+
+ TEXT
+
+
+
+ Europeana Foundation
+ Europeana Foundation
+ 50_test_13032023_1018
+ Netherlands
+
+ nl
+ 8
+
+
+ Archive and Documentation Center for RK Friesland
+ Archief- en Documentatiecentrum voor R.K. Friesland
+
+
+ Archives Portal Europe
+
+
diff --git a/metis-indexing/src/test/resources/europeana_record_to_sample_index_rdf.xml b/metis-indexing/src/test/resources/europeana_record_to_sample_index_rdf.xml
new file mode 100644
index 0000000000..945c431882
--- /dev/null
+++ b/metis-indexing/src/test/resources/europeana_record_to_sample_index_rdf.xml
@@ -0,0 +1,233 @@
+
+
+
+
+ c1739a4bddc51ed13cbb57f9d911dae9 - 62-7
+
+ application/pdf
+ 13924049
+ 300
+
+
+ text/html
+ 0
+
+
+ Titus Brandsma
+ Тит Брандсма
+ Titus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Tito Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Tytus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Anno Sjoerd Brandsma
+ F. Titus Brandsma O.Carm.
+ Брандсма, Титус
+ Титус Брандсма
+ Брандсма, Тит
+ Tito Brandsma
+ Beato tito brandsma
+ Anno Sjoerd Titus Brandsma
+ Titus Brandsma, O.Carm.
+ Anno Sjoerd Brandsma
+ F. Titus Brandsma O.Carm.
+ Titus Brandsma
+ Anno Sjoerd Brandsma
+ Bł. Tytus
+ Błogosławiony Tytus
+ Anno Sjoerd Brandsma
+ F. Titus Brandsma O.Carm.
+ Anno Sjoerd Brandsma
+ T. Brandsma
+ Tito Brandsma
+ Titus Brandsma, O.Carm.
+ F. Titus Brandsma O.Carm.
+ P. Titus Brandsma O.Carm.
+ Fra Titus Brandsma O.Carm.
+ Anno Sjoerd Brandsma
+ Niederländischer Theologe im Widerstand gegen den Nationalsozialismus
+ Nederländsk karmelitermunk, präst, och professor i filosofi
+ Dutch Carmelite friar, Catholic priest, professor of philosophy and saint
+ Sacerdote olandese
+ Prêtre carme néerlandais, canonisé le 15 mai 2022
+ Călugăr carmelit, profesor de filozofie, victimă a regimului nazist
+ Nederlands priester, martelaar en heilige
+ Frare carmelita holandès, sacerdot catòlic, professor de filosofia i esperantista
+ 1881-02-23
+ 1942-07-26
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1942/1942
+
+ 1942-01-01
+ 1942-12-31
+ 1942/1942
+
+
+ 1942~
+ approximate
+
+ 1942-01-01
+ 1942-12-31
+ 1942~
+
+
+ 20. Jahrhundert
+ 1900-luku
+ XX век
+ Século XX
+ 20 век
+ XX amžius
+ 20. stoljeće
+ 20. gadsimts
+ XXe siècle
+ 20. század
+ 20. storočie
+ 20. stoletje
+ 20ú haois
+ Segle XX
+ 1900-talet
+ 20ός αιώνας
+ 20th century
+ XX secolo
+ Siglo XX
+ 20. sajand
+ 20. století
+ XX. mendea
+ XX wiek
+ 20. århundrede
+ Secolul al XX-lea
+ 20e eeuw
+ 20:e århundradet
+ 20:e seklet
+ 1900-tal
+ 1900-talet (århundrade)
+ 1900-talet (sekel)
+ 20 век
+ Século 20
+ Século vinte
+ Periodo 1901-2000
+ Ciclo (1901-2000)
+ 20th-century
+ 20th-century
+ Twentieth century
+ The past century
+ History, 20th Century
+ XX Century
+ Novecento
+ 20° secolo
+ '900
+ Novecento
+ 20e siècle
+ 1901-01-01
+ 2000-12-31
+
+
+
+
+
+
+
+
+
+
+
+
+ Brandsma, Anno Sjoerd, 1881-1942
+ Brandsma, Titus, father, 1881-1942
+ Brandsma, Titus, Saint, 1881-1942
+
+
+
+
+
+
+ Brandsma, Titus, 1881-1942
+
+
+ archival material
+
+ The physical materials that make up the mass of an archive. Distinguished from AAT "archives (groupings)."
+ De fysieke materialen die samen een archief vormen. In de AAT te onderscheiden van 'archieven (groepen)'.
+ archival materials
+ archiefmateriaal
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #providedCHO_NL-BwdADRKF-2_62-7
+ nld
+
+ true
+
+
+
+
+
+ Brandsma, Titus, O.Carm., 1881-1942
+
+ [ca. 1942]
+ 62-7
+ dut
+ Teksten (fotokopieën, vijf bladzijden), door
+ Titus geschreven voor zijn boek over de H. Theresia. (originelen aanwezig in
+ het N.C.I. te Boxmeer).
+
+ 1942/1942
+ 1 omslag
+
+
+ false
+
+
+ TEXT
+
+
+
+ Europeana Foundation
+ Europeana Foundation
+ 50_test_13032023_1018
+ Netherlands
+
+ nl
+ 8
+
+
+ Archive and Documentation Center for RK Friesland
+ Archief- en Documentatiecentrum voor R.K. Friesland
+
+
+ Archives Portal Europe
+
+
diff --git a/metis-indexing/src/test/resources/europeana_record_to_sample_index_string.xml b/metis-indexing/src/test/resources/europeana_record_to_sample_index_string.xml
new file mode 100644
index 0000000000..317a2b94b0
--- /dev/null
+++ b/metis-indexing/src/test/resources/europeana_record_to_sample_index_string.xml
@@ -0,0 +1,234 @@
+
+
+
+
+ efb59b76dd3e0bba687f49f8b91fe9fb - 126-10
+
+ application/pdf
+ 16274217
+ 300
+
+
+ text/html
+ 0
+
+
+ Titus Brandsma
+ Тит Брандсма
+ Titus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Tito Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Tytus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Titus Brandsma
+ Anno Sjoerd Brandsma
+ F. Titus Brandsma O.Carm.
+ Брандсма, Титус
+ Титус Брандсма
+ Брандсма, Тит
+ Tito Brandsma
+ Beato tito brandsma
+ Anno Sjoerd Titus Brandsma
+ Titus Brandsma, O.Carm.
+ Anno Sjoerd Brandsma
+ F. Titus Brandsma O.Carm.
+ Titus Brandsma
+ Anno Sjoerd Brandsma
+ Bł. Tytus
+ Błogosławiony Tytus
+ Anno Sjoerd Brandsma
+ F. Titus Brandsma O.Carm.
+ Anno Sjoerd Brandsma
+ T. Brandsma
+ Tito Brandsma
+ Titus Brandsma, O.Carm.
+ F. Titus Brandsma O.Carm.
+ P. Titus Brandsma O.Carm.
+ Fra Titus Brandsma O.Carm.
+ Anno Sjoerd Brandsma
+ Niederländischer Theologe im Widerstand gegen den Nationalsozialismus
+ Nederländsk karmelitermunk, präst, och professor i filosofi
+ Dutch Carmelite friar, Catholic priest, professor of philosophy and saint
+ Sacerdote olandese
+ Prêtre carme néerlandais, canonisé le 15 mai 2022
+ Călugăr carmelit, profesor de filozofie, victimă a regimului nazist
+ Nederlands priester, martelaar en heilige
+ Frare carmelita holandès, sacerdot catòlic, professor de filosofia i esperantista
+ 1881-02-23
+ 1942-07-26
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1920/1920
+
+ 1920-01-01
+ 1920-12-31
+ 1920/1920
+
+
+ 1920
+
+ 1920-01-01
+ 1920-12-31
+ 1920
+
+
+ 20. Jahrhundert
+ 1900-luku
+ XX век
+ Século XX
+ 20 век
+ XX amžius
+ 20. stoljeće
+ 20. gadsimts
+ XXe siècle
+ 20. század
+ 20. storočie
+ 20. stoletje
+ 20ú haois
+ Segle XX
+ 1900-talet
+ 20ός αιώνας
+ 20th century
+ XX secolo
+ Siglo XX
+ 20. sajand
+ 20. století
+ XX. mendea
+ XX wiek
+ 20. århundrede
+ Secolul al XX-lea
+ 20e eeuw
+ 20:e århundradet
+ 20:e seklet
+ 1900-tal
+ 1900-talet (århundrade)
+ 1900-talet (sekel)
+ 20 век
+ Século 20
+ Século vinte
+ Periodo 1901-2000
+ Ciclo (1901-2000)
+ 20th-century
+ 20th-century
+ Twentieth century
+ The past century
+ History, 20th Century
+ XX Century
+ Novecento
+ 20° secolo
+ '900
+ Novecento
+ 20e siècle
+ 1901-01-01
+ 2000-12-31
+
+
+
+
+
+
+
+
+
+
+
+
+ Brandsma, Anno Sjoerd, 1881-1942
+ Brandsma, Titus, father, 1881-1942
+ Brandsma, Titus, Saint, 1881-1942
+
+
+
+
+
+
+ Brandsma, Titus, 1881-1942
+
+
+ archival material
+
+ The physical materials that make up the mass of an archive. Distinguished from AAT "archives (groupings)."
+ De fysieke materialen die samen een archief vormen. In de AAT te onderscheiden van 'archieven (groepen)'.
+ archival materials
+ archiefmateriaal
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #providedCHO_NL-BwdADRKF-2_126-10
+ nld
+
+ true
+ 1920
+
+
+
+
+
+ Brandsma, Titus, O.Carm., 1881-1942
+
+ 1920
+ 126-10
+ dut
+ Schrift met daarin de geschreven tekst van een
+ lezing welke mevr. Brandsma-Postma in ± 1920 heeft gehouden voor (vermoedelijk)
+ de leden van de Mariavereniging in Bolsward. De tekst is geschreven door pater
+ Titus.
+
+ 1920/1920
+ 1 omslag
+
+
+ false
+
+
+ TEXT
+
+
+
+ Europeana Foundation
+ Europeana Foundation
+ 50_test_13032023_1018
+ Netherlands
+
+ nl
+ 10
+
+
+ Archive and Documentation Center for RK Friesland
+ Archief- en Documentatiecentrum voor R.K. Friesland
+
+
+ Archives Portal Europe
+
+
diff --git a/metis-repository/src/main/resources/log4j2.xml b/metis-indexing/src/test/resources/log4j2.xml
similarity index 100%
rename from metis-repository/src/main/resources/log4j2.xml
rename to metis-indexing/src/test/resources/log4j2.xml
diff --git a/metis-indexing/src/test/resources/solr/elevate.xml b/metis-indexing/src/test/resources/solr/elevate.xml
new file mode 100644
index 0000000000..179eef5404
--- /dev/null
+++ b/metis-indexing/src/test/resources/solr/elevate.xml
@@ -0,0 +1,14988 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/metis-indexing/src/test/resources/solr/enumsConfig.xml b/metis-indexing/src/test/resources/solr/enumsConfig.xml
new file mode 100644
index 0000000000..78783afa2e
--- /dev/null
+++ b/metis-indexing/src/test/resources/solr/enumsConfig.xml
@@ -0,0 +1,16 @@
+
+
+
+ 0
+ 1
+ 2
+ 3
+ 4
+
+
+ 0
+ A
+ B
+ C
+
+
diff --git a/metis-indexing/src/test/resources/solr/query_aliases.xml b/metis-indexing/src/test/resources/solr/query_aliases.xml
new file mode 100644
index 0000000000..eb74108bd4
--- /dev/null
+++ b/metis-indexing/src/test/resources/solr/query_aliases.xml
@@ -0,0 +1,63 @@
+
+
+ collection
+
+
+ archaeology
+ proxy_dcterms_isPartOf:("Europeana Archaeology") OR skos_concept:("http://data.europeana.eu/concept/80" OR "http://vocab.getty.edu/aat/300000810" OR "http://vocab.getty.edu/aat/300054328" OR "http://vocab.getty.edu/aat/300379558" OR "http://vocab.getty.edu/aat/300266711" OR "http://vocab.getty.edu/aat/300234110" OR "http://vocab.getty.edu/aat/300266151") OR what:(runsten OR ruin* OR pyramid* OR gravfält OR utgrävning OR fornminnen OR arkeologisk utredning OR bronsålder OR järnålder OR gravröse OR mumie OR lämning OR arkeologi*) OR (edm_datasetName:(91624_*)) OR (edm_datasetName:91669_* AND what:"Osvald Siren") OR (PROVIDER:(CARARE OR "3D ICONS") AND (provider_aggregation_edm_isShownBy:* AND has_thumbnails:true)) OR (foaf_organization:(*/1482250000004509126 OR */1482250000003772104) AND (provider_aggregation_edm_isShownBy:* AND has_thumbnails:true)) NOT (foaf_organization:(*/1482250000004671150 OR */1482250000002065240))
+
+
+ art
+ proxy_dc_subject:"http://data.europeana.eu/concept/190" OR foaf_organization:(*/1482250000004671105) OR (foaf_organization:(*/1482250000004511967) -TYPE:TEXT) OR (foaf_organization:(*/1482250000002112001) AND what:(poster OR engraving OR enluminure OR drawing OR estampe OR peinture) AND has_thumbnails:true) OR foaf_organization:(*/1482250000004509120 OR */1482250000001351017 OR */1482250000004511967 OR */1482250000000602123 OR */1482250000004477397) OR (foaf_organization:*/1482250000000338795 AND has_thumbnails:true) OR edm_datasetName:(376* OR 2024909* OR 91631* OR 9200490* OR 2048012* OR 9200424*) OR DATA_PROVIDER:("Victoria and Albert Museum") OR foaf_organization:(*/1482250000004516145 OR */1482250000004510551 OR */1482250000004503513 OR */1482250000003772880 OR */1482250000004509093 OR */1482250000004510475 OR */1482250000000363675 OR */1482250000004516117 OR */1482250000004510453 OR */1482250000000338849 OR */1482250000004504960 OR */1482250000004517617 OR */1482250000004504956 OR */1482250000001746004 OR */1482250000000737001 OR */1482250000000365643 OR */1482250000004504955 OR */1482250000000545189 OR */1482250000000371761 OR */1482250000004516197) OR (foaf_organization:*/1482250000002678009 AND (picasso OR "salvador dali" OR "claude monet" OR rodin OR "van gogh" OR louvre OR chagall)) OR ((what:("Gemälde" OR "Zeichnung" OR "Tafelbild" OR "Miniatur" OR "Triptychon" OR "fine art" OR "beaux arts" OR "bellas artes" OR "belle arti" OR "schone kunsten" OR konst OR "bildende kunst" OR "Opere d'arte visiva" OR "decorative arts" OR Teckning OR smycken OR akvarell OR affisch OR gustavian* OR stengod* OR konsthistori* OR kalligrafi OR konststil OR akvarell OR gravyr OR mosaik OR nyklassicism OR popkonst OR tryck OR rokoko OR romantiken OR bildhuggar* OR skiss OR målarkonst OR illustration OR illustrering OR porträtt OR konsthantverk OR "arts décoratifs" OR paintings OR schilderij OR pintura OR peinture OR dipinto OR malerei OR måleri OR målning OR malarstwo OR sculpture OR skulptur OR sculptuur OR beeldhouwwerk OR rzeźba OR drawing OR sketch OR poster OR tapestry OR gobelin OR jewellery OR miniature OR prints OR träsnitt OR holzschnitt OR woodcut OR lithography OR engraving OR chiaroscuro OR "old master print" OR estampe OR porcelain OR stoneware OR vase OR kylix OR skyphos OR oinochoe OR aryballos OR pyxis OR lekythos OR kantharos OR phiale OR loutrophoros OR alabastron OR rhyton OR mosaic OR wallpaper OR tapet OR "papier peint" OR "carta da parati" OR behang OR "papel pintado" OR carpet OR tapis OR teppich OR matta OR tapijt OR tappeto OR fresco OR fresk OR fresque OR diptyk OR diptych OR dittico OR diptiek OR diptyque OR mannerism OR rococo OR impressionism OR expressionism OR romanticism OR "Neo-Classicism" OR "Pre-Raphaelite" OR Symbolism OR Surrealism OR Cubism OR "Art Deco" OR Dadaism OR "De Stijl" OR "Pop Art" OR "art nouveau" OR "art history" OR "http://vocab.getty.edu/aat/300041273" OR "histoire de l'art" OR kunstgeschichte OR "estudio de la historia del arte" OR Kunstgeschiedenis OR "illuminated manuscript" OR buchmalerei OR enluminure OR "manuscrito illustrado" OR "manoscritto miniato" OR boekverluchting OR kalligrafi OR calligraphy OR exlibris OR Druckgraphik)) AND (provider_aggregation_edm_isShownBy:*)) NOT ((foaf_organization:(*/1482250000004500666) AND (carta)) OR what:( "printed serial" OR "photo prints" OR "photographic prints" OR "Thom's Street Directory" OR "printedbook" OR "printing paper" OR "printed music") OR who:(Gunnar Lundh) OR DATA_PROVIDER:(*/1482250000004507748 OR */1482250000004517448 OR */1482250000004500680 OR */1482250000000338491 OR */1482250000007002200 OR */1482250000004509099 OR */1482250000001710325 OR */1482250000000369861 OR */1482250000001721054 OR */1482250000004500823 OR */1482250000004671121 OR */1482250000000369049 OR */1482250000000366065 OR */1482250000004671150 OR */1482250000004671158) OR DATA_PROVIDER:("Ministère de la culture et de la communication, Musées de France") OR edm_datasetName:(*TEL*Newspapers*) OR edm_datasetName:("92040_Ag_EU_TEL_a0155_Serbia" OR *AthensAcademy*) OR (foaf_organization:(*/1482250000002112001) AND TYPE:TEXT))
+
+
+ fashion
+ (foaf_organization:"http://data.europeana.eu/organization/1482250000000369861") AND NOT(proxy_dc_identifier:"01457L") AND NOT(proxy_dcterms_created:[0001 TO 1300]) OR NOT(proxy_dcterms_created:[2018 TO 9999]) OR NOT(YEAR:[2018 TO 9999])
+
+
+ industrial
+ (foaf_organization:"http://data.europeana.eu/organization/1482250000002678009" AND (industrie* OR ouvrier* OR usine*)) OR (foaf_organization:*/1482250000002112001 AND what:(industrie* OR ouvrier* OR fabrication* OR usine* OR travail*)) OR (foaf_organization:*/1482250000004516145 AND (industria OR fabricacio OR treballador)) OR (foaf_organization:*/1482250000000338645 AND (arbeid* OR fabriek OR fabricage OR industrie* OR wegenbouw)) OR (foaf_organization:*/1482250000002065240 AND what:(work)) OR (foaf_organization:*/1482250000004671121 AND what:(industr* OR work* OR labour)) OR (foaf_organization:*/1482250000000338795 AND (industr* OR fabriek* OR arbeider*)) OR ((what:(machine* OR industr* OR bιομηχανία OR przemysł OR factory OR fabrik OR eργοστάσιο OR usine OR fabryk OR urban OR worker* OR Arbeitnehmer OR eργάτης OR ouvrier OR pracownik OR arbetstagare OR working OR labor OR labour OR Arbeid OR Εργασία OR travail OR praca OR arbetskraft OR union OR mines OR mining OR chemic* OR coal OR copper OR "oil industry" OR dairy OR zuivel OR zuivelindustrie OR textielindustrie OR refinery OR brewery OR steel OR energy OR power OR "power plant" OR Krafwerk OR "car* industry" OR "weapon* industry" OR technology OR telecom OR computing OR production OR socialism OR communism OR Rallare OR fiskare OR fishermen OR fackförening OR strejk OR strike OR arbetarrörelsen OR väveri OR spinneri OR "spinning*" OR lastbil OR masugn OR ångmaskin OR sågverk OR sawmill OR kraftverk OR flottning OR uitto OR järnverk OR ironworks OR järnbruk OR hytta OR ”första maj” OR engine OR arbetsliv OR "working life" OR "vie professionnelle"OR *arbetare OR gruva OR traktor OR skrivmaskin OR kraftstasjon OR fabrik OR spinnmaskin OR bryggeri OR forestry OR textilindustri OR manufacturing OR Herstellung OR βιομηχανοποίηση OR fabrication OR produkcja OR tillverkning OR wegenbouw OR "Industrial heritage")) AND (provider_aggregation_edm_isShownBy:*) AND has_thumbnails:true) NOT (what: porslin OR porcelain OR porselein OR Porzellan OR furniture OR "artisanat - industrie" OR statue OR "steel engravings" OR specimen OR "machine observation" OR "TÄNDSTICKSETIKETT" OR "Prøvebok") NOT ( contentTier:0 OR foaf_organization:(*/1482250000004671150 OR */1482250000000369861) OR edm_datasetName:(*AthensAcademy* OR 2048215* OR 2021008* OR 2031901* OR 916124* OR 2026106* OR 9200170* OR 9200171* OR 2021656* OR 22_Byzart* OR 2022362_Ag* OR "92040_Ag_EU_TEL_a0155_Serbia") OR foaf_organization:(*/1482250000004504995 OR */1482250000004516087 OR */1482250000000366315 OR */1482250000004510493) OR what:(kleipijp* OR "archaeological objects" OR "birds") OR (TYPE:(SOUND) AND what:"live") OR (foaf_organization:(*/1482250000002112001) AND what:"drawing" OR "engraving"))
+
+
+ manuscript
+ (foaf_organization:(*/1482250000044932001) AND proxy_dc_type:("Manuscript")) OR foaf_organization:(*/1482250000000362971) OR (edm_datasetName:9200144*) OR (provider_aggregation_edm_isShownBy:* AND what:"illuminated manuscripts") OR foaf_organization:(*/1482250000004511828) OR (edm_datasetName:(2048445*) AND what:handschrift*) OR edm_datasetName:(9200388* OR 9200519* OR 9200522* OR 91_* OR 10501* OR 44_RoL* OR 36_* OR 6_RoL*) OR (edm_datasetName:(92092*) AND proxy_dc_type:"Manuscript") OR (foaf_organization:(*/1482250000002112001) AND what:manuscript*) OR (edm_datasetName:(9200579*) AND proxy_dc_type:"Manuscript") OR (edm_datasetName:(92004_NL_Manuscriptorium_CZ*) AND sv_dcterms_conformsTo:(*iiif*)) OR ((provider_aggregation_edm_isShownBy:* AND has_thumbnails:true)) AND ((what:(Ръкопис OR manuscrit OR rukopis OR håndskrift OR Manuskript OR Χειρόγραφο OR manuscrito OR Käsikiri OR käsikirjoitus OR manuscrit OR kézirat OR manoscritto OR manuscriptum OR manuskripts OR manuscript OR rękopis OR manuscrito OR manuscris OR рукопись OR Rukopis OR Rokopis OR Handskrift OR manuscript OR Handschrift OR ms OR mss OR miniatures OR illuminations OR cartography OR illumination OR codex OR codices OR marginalia OR almanacs OR broadsides)) OR (proxy_dc_type:RESMAN*) OR (proxy_dcterms_medium:"vellum" OR "parchment") OR (edm_datasetName:92065*) OR foaf_organization:(*/1482250000001710325 OR */1482250000004502183) OR edm_datasetName:(2021803* OR 2048100* OR 9200463* OR 9200453* OR 9200565* OR 9200114* OR 9200353* OR 9200566*)) NOT (proxy_dc_type:(Pintura OR Painting) OR edm_datasetName:(9200218* OR 2023813* OR 92002_Ag_EU_* OR 71_Ag_SE* OR 18_RoL_ICCU_Foglio) OR foaf_organization:(*/1482250000004511854 OR */1482250000004505007 OR */1482250000004500680 OR */1482250000009413001 OR */1482250000004514692 OR */1482250000000338827 OR */1482250000004510599 OR */1482250000000338849 OR */1482250000004502224 OR */1482250000004500707 OR */1482250000004516087 OR */1482250000021995423 OR */1482250000004477410 OR */1482250000000992135 OR */1482250000004509071 OR */1482250000004509126 OR */1482250000000367261 OR */1482250000004503433 OR */1482250000000363371 OR */1482250000004503491 OR */1482250000000338795 OR */1482250000000368903 OR */1482250000004500729 OR */1482250000004511889 OR */1482250000004477317 OR */1482250000004502073 OR */1482250000004502076 OR */1482250000004513273 OR */1482250000000558721 OR */1482250000004504922 OR */1482250000004500727 OR */1482250000000363173 OR */1482250000004510617 OR */1482250000004516137 OR */1482250000004502087 OR */1482250000000369861 OR */1482250000004671125 OR */1482250000004671150 OR */1482250000004503438 OR */1482250000012962180 OR */1482250000000364563 OR */1482250000004671105 OR */1482250000004671158) OR (foaf_organization:(*/1482250000004671126) AND proxy_dc_date:(17* OR 18* OR 19* OR 20*)) OR YEAR:[1799 TO 9999] OR edm_datasetName:(2048128* OR 2021803* OR 92030* OR 92023* OR 2064109* OR 2020601* OR 2058611* OR 2021801* OR 2058616* OR 2021654* OR 110_Hispana*) OR TYPE:(SOUND OR VIDEO OR 3D) OR proxy_dc_type:("Other academic") OR skos_concept:("http://vocab.getty.edu/aat/300391258") OR what:(Serial OR photo* OR postcard OR Besedilo OR daguerreotyp* OR cartography OR "periodical" OR "Пощенска картичка" OR postal OR "korespondenční lístek" OR Postkort OR archaeology OR briefkaart OR Postkaart OR Postikortti OR "carte postale" OR Postkarte OR "Καρτ ποστάλ" OR "Képes levelezőlap" OR "cartolina postale" OR "Kartka pocztowa" OR "cartão-postal" OR "Carte poștală" OR "Почтовая карточка" OR "Дописница" OR "Razglednica" OR "tarjeta postal" OR Vykort OR postcard OR Портрет OR retrat OR Portret OR portrét OR portræt OR portret OR portree OR muotokuva OR portrait OR Porträt OR προσωπογραφία OR Arckép OR ritratto OR portrets OR Portretas OR Retrato OR портрет OR портрет OR portrét OR porträtt OR portread OR "Slikovno gradivo" OR "Human migration") OR PROVIDER:("International Court of Justice") OR proxy_dcterms_medium:(metall) OR Kulturhistoria OR glass OR houtwerk)
+
+
+ map
+ proxy_dc_subject:("http://data.europeana.eu/concept/43" OR "http://data.europeana.eu/concept/151") OR (foaf_organization:*/1482250000002112001 AND (what:(maps OR "charts and maps" OR geographie OR atlas OR voyages OR "cartes marines" OR portulan OR "globe terrestre" OR "globes célestes" OR "sphere armillaire" OR "piri reis" OR geographia OR "Atlas Catalan" OR "Theatrum orbis terrarum" OR "La Cosmographie universelle" OR "Carte générale de la France" OR mercator OR cosmographia))) OR ((foaf_organization:*/1482250000004516197 AND (maps OR "Schlachten-Atlas" OR geographie)) OR (edm_datasetName:("9200499_Ag_BnF_bmchambery") AND (what:"carte et plan")) OR (what:("mappa mundi" OR Карта OR Mappamoundi OR mappamundi OR "mappae mundi" OR "mapa topografico" OR "Royal Geographical Society" OR cosmographia OR geographia OR geographica OR topographia OR atlas OR "carta marina" OR periplus OR "piri reis" OR "Theatrum orbis terrarum" OR "map maker" OR cartographer OR "Tabula Peutingeriana" OR "Atlas Catalan" OR "Catalan Atlas" OR map OR portolan OR maps OR kartbok OR flygfotografi OR flygfoto OR armillarsfär OR Jakobsstav OR aardglobe OR hemelglobe OR globen OR globe OR "aerial photograph" OR Lodfoto OR periplus OR atlas OR "armillary sphere" OR armillarsfar OR astrolabe OR astrolabium OR "jacob's staff" OR sextant OR octant OR astrolabium OR compass OR kompas OR kompass OR "terrestrial globe" OR "jordglob" OR globus OR exploration OR discoveries OR gazetteer OR gradskiva OR ställinstrument OR kvadrant OR oktant OR distansangivare OR map OR karta OR mapa OR karte OR kaart OR cartography OR kartografi OR cartografia OR "mapa topografico" OR cartographic OR geography OR geografi OR geographie OR meteorology OR meteorologi OR navigation OR chart OR cosmography OR kosmografi OR "astronomical instrument" OR "celestial globe") OR (who:(strabo OR herodotus OR eratosthenes OR scylax OR skylax OR "pomponius mela" OR mercator OR "al-idrisi" OR "alexander von humboldt" OR Waldseemüller)) AND (provider_aggregation_edm_isShownBy:* AND has_thumbnails:true)) NOT (foaf_organization:(*/1482250000004671150 OR */1482250000000369861 OR */1482250000004516087 OR */1482250000001806359 OR */1482250000004477302 OR */1482250000004516113) OR edm_datasetName:(*AthensAcademy2* OR "2064109_Spielzeug_Museum" OR "22_Byzart_1_WholeCollection") OR what:("salvator mundi" OR "Glückwunschkarte" OR "Tanzordnung" OR "Books" OR "Litteratur") OR TYPE:SOUND))
+
+
+ migration
+ (foaf_organization:(*/1482250000002678009) AND (migra* OR emigra* OR immigra* OR refugie OR exile OR diaspora)) OR (foaf_organization:(*/1482250000002112001) AND what:(migra* OR immigra* OR emigra* OR refugie OR exile OR diaspora)) OR (foaf_organization:(*/1482250000004516145) AND what:(migra* OR immigra* OR emigra* OR refugie OR exile OR diaspora OR refugiats)) OR (foaf_organization:(*/1482250000000338645) AND (vluchtelingen OR aankomst OR immigranten OR evacués OR vluchtelingenkampen OR asielzoekers OR humanitaire hulp OR vreemdelingenbeleid OR gastarbeiders OR opvangcentra OR naturalisatie OR onderduiken OR migratie OR etnische minderheden OR remigratie OR allochtonen OR etnische zuiveringen OR zigeuners OR culturele identiteit OR uitwijzingen OR gezinshereniging OR opvangcentra OR bootvluchtelingen OR ontwikkelingssamenwerking)) OR (edm_datasetName:2022102_Ag_EU_ECLAP_BEELDENGELUID AND (vluchtelingen OR aankomst OR immigranten OR evacués OR vluchtelingenkampen OR asielzoekers OR humanitaire hulp OR vreemdelingenbeleid OR gastarbeiders OR opvangcentra OR naturalisatie OR onderduiken OR migratie OR etnische minderheden OR remigratie OR allochtonen OR etnische zuiveringen OR zigeuners OR culturele identiteit OR uitwijzingen OR gezinshereniging OR opvangcentra OR bootvluchtelingen OR ontwikkelingssamenwerking)) OR (foaf_organization:(*/1482250000002065240) AND what:(migra* OR immigra* OR emigra* OR refugie OR exile OR diaspora)) OR edm_datasetName:"51_Ag_EU_EUscreenXLCore_1059" OR (foaf_organization:(*/1482250000004671121) AND what:(migra* OR immigra* OR emigra* OR refugee OR exile OR diaspora OR "human migration")) OR ((what:("Human migration" OR "Génocide arménien" OR Jewish* OR Molukkers OR refugees OR "Refugees from Nazi Europe")) AND (provider_aggregation_edm_isShownBy:*)) OR ((auswander* OR begunec OR bevándorlás OR einwander* OR eksil OR emigr* OR espatri* OR expat* OR esclavage OR esclavitud OR flyktning OR gemigreerd OR imigr* OR immigr* OR indvand* OR inmigr* OR innvandr* OR invandr* OR izselj* OR kivándor* OR külföldi OR maastamuutt* OR migr* OR perkeltas OR ränne OR siirtolaista OR siirtolainen OR utvandr* OR wyemigr* OR displaced OR begunci OR begli OR desplazado OR déplacé OR esiliato OR esilio OR exil* OR flygtning* OR flüchtling* OR izbjeglic* OR izgnani OR izgnanstvo OR ištremtas OR maanpako OR niewolnictwo OR pabegeliai OR pagulane OR perkeltos OR profugo OR przybywających OR ränne OR refugee* OR refugiado OR refugiat OR rifugiati OR réfugié* OR sclavie OR sfollati OR slaveri OR sklaverei OR spostati OR számuzött OR Tabinta OR tremtis OR trimda OR uchodzcy OR uitgeweken OR utecenec OR verbann* OR vluchteling* OR wysiedlony OR zeslany OR μετανάστευση OR емиграция OR μετανάστης OR емигрант OR απόδημος OR πρόσφυγας OR бежанци OR Πρόσφυγες OR Εκτοπισμένοι OR изгнание OR εξορία OR заточен OR Εξόριστος OR diaspora OR landverhuizer OR gastarbeiter OR gastarbeider OR arbetskraftsinvandring OR "guest worker" OR "Passeports pour l'étranger" OR "Cuadernos de Ruedo Ibérico" OR "Clevelandska Amerika" OR "Ameriska Domovina" OR "Amerikos lietuviai" OR "Amerikos lietuvių" OR "Erős Vár" OR "Amerikanski Slovenec") AND (provider_aggregation_edm_isShownBy:*) AND has_thumbnails:true) NOT (foaf_organization:(*/1482250000004671140 OR */1482250000004671150 OR */1482250000000369861 OR */1482250000004517462 OR */1482250000004502136 OR */1482250000004671161) OR edm_datasetName:(*AthensAcademy2* OR "92040_Ag_EU_TEL_a0155_Serbia" OR edm_datasetName:"92093_Ag_UK_TEL_a1009g_EuropeanaLibraries") OR foaf_organization:(*/1482250000044932001) OR edm_datasetName:("22_Byzart_1_WholeCollection") OR ("Street Directory" OR "Commercial Directory" OR "Official Directory" OR "bird" OR "Oiseaux" OR "migraine" OR "fauna" OR "wildlife" OR "Joseph Zammit" OR "cell" OR "exilium" OR "reptile" OR "animal" OR "Refugiehuis") OR (YEAR:[2019 TO 9999]) OR (YEAR:[-1000 TO -4000]))
+
+
+ music
+ proxy_dc_subject:"http://data.europeana.eu/concept/62" OR (foaf_organization:(*/1482250000004671113) AND provider_aggregation_edm_isShownBy:* AND music) OR (foaf_organization:(*/1482250000002112001) AND musique) OR (foaf_organization:(*/1482250000004671096) AND provider_aggregation_edm_isShownBy:*) OR (DATA_PROVIDER:"Stiftung Händel-Haus Halle" AND provider_aggregation_edm_isShownBy:*) OR (foaf_organization:(*/1482250000000719061) AND TYPE:SOUND) OR (edm_datasetName:"09301_Ag_EU_Judaica_mcy78") OR (DATA_PROVIDER:"Kirsten Flagstadmuseet") OR (foaf_organization:(*/1482250000000338645) AND provider_aggregation_edm_isShownBy:* AND (music OR muziek)) OR (foaf_organization:(*/1482250000004516145) AND musica) OR (foaf_organization:(*/1482250000002678009) AND (musique OR opera OR pop OR rock OR concert OR chanson OR interpretation)) OR ((what:(music OR musique OR musik OR musica OR musicalesbalett OR kassett* OR dirigent OR körsång OR komponist OR tonsättare OR sångkör OR nothäfte OR sångare OR popmusik OR skiva OR schlager OR svensktoppen OR rockband OR kvartett OR kvintett OR radio OR notblad OR fonografcylinder OR fonografrulle OR musikinstrument OR Mp3-spelare OR högtalare OR "zenés előadás" OR "notated music" OR "folk songs" OR "choral music" OR choir OR chorus OR chor OR jazz OR "sheet music" OR score OR "musical instrument" OR partitur OR partituras OR gradual OR libretto OR oper OR concerto OR symphony OR sonata OR fugue OR motet OR saltarello OR organum OR ballade OR chanson OR laude OR madrigal OR pavane OR toccata OR cantata OR minuet OR partita OR sarabande OR sinfonia OR hymnes OR lied OR "music hall" OR quartet OR quintet OR requiem OR rhapsody OR scherzo OR "sinfonia concertante" OR waltz OR ballet OR zanger OR sangerin OR chanteur OR chanteuse OR cantante OR composer OR compositeur OR orchestra OR orchester OR orkester OR orchestre OR concierto OR konsert OR konzert OR koncert OR gramophone OR "record player" OR phonograph OR fonograaf OR fonograf OR grammofon OR skivspelare OR "wax cylinder" OR jukebox OR "cassette deck" OR "cassette player")) AND (provider_aggregation_edm_isShownBy:*)) OR ("gieddes samling") OR (musik AND foaf_organization:(*/1482250000004516197)) OR (antiphonal AND foaf_organization:(*/1482250000001710325)) OR (edm_datasetName:"2059208_Ag_EU_eSOUNDS_1020_CNRS-CREM") OR (title:(gradual OR antiphonal) AND edm_datasetName: "2021003_Ag_FI_NDL_fragmenta") NOT (DATA_PROVIDER:("Beeldbank van de Rijksdienst voor het Cultureel Erfgoed") OR foaf_organization:(*/1482250000000338951 OR */1482250000004505022 OR */1482250000004477239 OR */1482250000004513293 OR */1482250000004513290 OR */1482250000004513291 OR */1482250000007002200 OR */1482250000000364851 OR */1482250000004500823 OR */1482250000004510445 OR */1482250000000361015 OR */1482250000007002200 OR */1482250000004517471 OR */1482250000000369517 OR */1482250000004500818 OR */1482250000004671150 OR */1482250000000369861) OR edm_datasetName:("9200123_Ag_EU_TEL_a1023_Sibiu" OR "2048319_Ag_EU_ApeX_NLHaNA" OR "2059202_Ag_EU_eSOUNDS_1004_Rundfunk" OR "09335_Ag_EU_Judaica_cfmj4" OR "09326_Ag_EU_Judaica_cfmj3") OR what:("opere d'arte visiva" OR what:"operating rooms" OR what:"operating systems" OR what:"co-operation" OR what:operation))
+
+
+ nature
+ proxy_dc_subject:("http://data.europeana.eu/concept/156") OR ((foaf_organization:(*/1482250000004671150 OR */1482250000004671161 OR */1482250000004511845) OR edm_datasetName:"2058616_Ag_EU_LoCloud_BJC") AND (provider_aggregation_edm_isShownBy:* AND has_thumbnails:true)) OR (foaf_organization:(*/1482250000002112001) AND what:(zoologie OR botanique OR "histoire naturelle" OR geologie OR biologie OR microbiologie OR horticulture OR agriculture OR arbologie OR herbier OR "parc zoologique" OR "jardin botanique") AND has_thumbnails:true) OR (foaf_organization:(*/1482250000000905081) AND provider_aggregation_edm_isShownBy:* AND what:(birds OR animals OR "nature sounds" OR zoo)) OR foaf_organization:(*/1482250000004505007) OR (what:("natural history" OR "histoire naturelle" OR naturgeschichte OR "historia naturalna" OR "historia natural" OR biolog* OR geolog* OR zoolog* OR entomolog* OR ornitholog* OR mycolog* OR taxidermy OR specimen OR fossil OR animal OR reptile OR plants OR flower OR palaeontolog* OR flora OR fauna OR evolution OR systematics OR systematik OR insect OR insekt OR herbarium OR herbiers OR biodiversity OR "natura 2000 species" OR jordbruk OR botanik OR botaniska OR fågel OR fåglar OR blomma OR blommor OR mineral OR trädgård OR insekt OR svamp OR naturhistoria OR växt OR kräldjur OR uppstoppade OR djurpark OR fykologi OR "Anna Atkins") AND (provider_aggregation_edm_isShownBy:*)) OR ((title:fauna AND TYPE:TEXT) OR (title:flora AND TYPE:TEXT) OR (title:exemplaar AND foaf_organization:(*/1482250000004510463)) AND (provider_aggregation_edm_isShownBy:*)) OR ((linnaeus OR "Carl von Linné" OR "Leonhart Fuchs" OR "Otto Brunfels" OR "Hieronymus Bock" OR "Valerius Cordus" OR "Konrad Gesner" OR "Frederik Ruysch" OR "Gaspard Bauhin" OR "Henry Walter Bates" OR "Charles Darwin" OR "Alfred Russel Wallace" OR "Georges Buffon" OR "Jean-Baptiste de Lamarck" OR "Maria Sibylla Merian" OR "materia medica" OR "papyrus ebers" OR "historiae animalium" OR "Tractatus de herbis" OR "hortus sanitatis" OR "ortus sanitatis" OR "systema naturae" OR "naturalis historia" OR botanica OR plantarum OR herbal OR herbarius OR herbarium OR bestiarium OR Naturstudien OR Pflanzenstudien OR krauterbuch OR kruidboek OR herbarius OR botanica OR zoologica) AND (provider_aggregation_edm_isShownBy:*)) NOT (skos_concept:(*thesaurus.europeanafashion.eu/thesaurus/10352*) OR foaf_organization:(*/1482250000000369861) OR edm_datasetName:"345_EKT_AthensAcademy2" OR "10102_Ag_EU_STERNA_39" OR edm_datasetName:("2058616_Ag_EU_LoCloud_BJC" OR *_Europe_at_work*) OR foaf_organization:(*/1482250000004502085) OR title:"fossil åker" OR title:"Område med fossil åkermark")
+
+
+ newspaper
+ (proxy_dc_type:("http://data.europeana.eu/concept/18") OR edm_datasetName:(*Ag_EU_TEL*Newspapers* OR 9200231* OR 2020128* OR 2020126* OR 15_* OR 92_* OR 124_RoL_BnF_Newspapers OR 18_RoL_ICCU_Foglio)) NOT (foaf_organization:(*/1482250000004671159 OR */1482250000004503437 OR */1482250000004671157 OR */1482250000000361015 OR */1482250000004671136 OR */1482250000000362375 OR */1482250000004671141 OR */1482250000004671134 OR */1482250000004671094 OR */1482250000000369861 OR */1482250000004502161 OR */1482250000004500680 OR */1482250000000363675 OR */1482250000000905081 OR */1482250000004502070 OR */1482250000004507729 OR */1482250000004513385 OR */1482250000004510534 OR */1482250000004514692))
+
+
+ photography
+ proxy_dc_subject:("http://data.europeana.eu/concept/48") OR (foaf_organization:*/1482250000001721054 AND (provider_aggregation_edm_isShownBy:* AND has_thumbnails:true)) OR (edm_datasetName:"2058615_Ag_EU_LoCloud_BJC-e" "2058616_Ag_EU_LoCloud_BJC" OR "91698_Ag_SE_SwedishNationalHeritage_SuM" OR "9200173_Ag_EU_TEL_a1118_EU_Libraries_Wales" OR "2048421_Ag_DE_DDB_Wasserbau" AND has_thumbnails:true) OR (foaf_organization:(*/1482250000004503485) AND fotografic*) OR (edm_datasetName:"2022503_Ag_SL_Europeana_Kamra_Album") OR (edm_datasetName:(2024909*) AND proxy_dcterms_isPartOf:*PAGODE*) OR (foaf_organization:*/1482250000002112001 AND has_thumbnails:true AND what:(photographe OR photographie)) OR (foaf_organization:*/1482250000002678009 AND (photographie OR photographe OR "man ray" OR nadar OR daguerre)) OR foaf_organization:(*/1482250000004516032) OR (proxy_dcterms_medium:negativ AND (provider_aggregation_edm_isShownBy:* AND has_thumbnails:true)) OR ((what:(photographs OR photograph OR photography OR "foto album" OR foto OR autokrom OR kalotyp OR kollodium OR fotoalbum OR diapositiv OR glasplåt OR filmrulle OR teleobjektiv OR brännvid fotografier OR camera OR kamera OR fotografi OR argazkilaritza OR фотаздымак OR fotografija OR фотография OR fotografia OR fotografování OR fotografering OR fotografie OR fotograafia OR valokuvaus OR photographie OR fotografía OR Fotografie OR φωτογραφία OR fényképezés OR ljósmyndun OR grianghrafadóireacht OR fotografēšana OR фотографија OR fotografering OR zselatinos ezüst OR "üveg neg. képhordozó fekete-fehér" OR cellulóznitrát OR nitrátfilm OR фотография OR фотографија OR fotografovanie OR fotografi OR фотографія OR ffotograffiaeth OR פאָטאָגראַפיע OR "Historical Photo Department" OR daguerreotype OR daguerreotypie OR dagerrotypi OR collodion OR calotype OR ambrotype OR cyanotype OR "glass plate" OR "glasnegativ" OR skioptikon OR "medium format" OR "roll film" OR kodak OR leica OR fujifilm OR nikon OR yashica OR pentax OR telephoto OR "silver plate" OR "salt print" OR "albumen print" OR "silver nitrate" OR autochrome OR photochrom OR fotochrom OR kodachrome OR hasselblad OR "gelatin print" OR rolleiflex OR "field camera" OR "magic lantern" OR "laterna magica" OR "lantern slide" OR "silver salt" OR "salt gelatin" OR postcard)) AND (provider_aggregation_edm_isShownBy:* AND has_thumbnails:true AND has_media:true)) NOT (what:"prints (visual works)" OR foaf_organization:(*/1482250000004509055 OR */1482250000000905081 OR */1482250000000363999 OR */1482250000004500823 OR */1482250000002136023 OR */1482250000004516124 OR */1482250000004517535 OR */1482250000000362053 OR */1482250000004517571 OR */1482250000004513279) OR edm_datasetName:"2058635_Ag_EU_LoCloud_PSNC" OR foaf_organization:(*/1482250000004503569 OR */1482250000004513228 OR */1482250000004517478 OR */1482250000004503561 OR */1482250000004503612 OR */1482250000004514782 OR */1482250000004506227 OR */1482250000001806359 OR */1482250000004477410 OR */1482250000004503564 OR */1482250000004671161 OR */1482250000004516035) OR edm_datasetName:("2023817_AG-EU_LinkedHeritage_ArtsandTheatre" OR "92040_Ag_EU_TEL_a0155_Serbia" OR "2058603_Ag_EU_LoCloud_IPCHS") OR what:("image, foto" OR teckning OR akvarell OR "prints (visual works)" OR "Watercolor painting" OR lavering) OR (foaf_organization:(*/1482250000004671137) AND what:(newspaper)))
+
+
+ sport
+ (proxy_dcterms_isPartOf:*Sport*) OR (proxy_dcterms_isPartOf:"Europeana Sport") OR (what:("http://data.europeana.eu/concept/114") OR (foaf_organization:(*/1482250000002112001 OR */1482250000002678009) AND ("tour de france" OR "jeux olympiques" OR tennis OR rugby OR badminton OR football OR sport OR boxing OR sportif OR athlétisme OR "luttes sportive" OR hockey OR "coupe du monde" OR "ski alpin") AND has_thumbnails:true)) OR foaf_organization:(*/1482250000004502192 OR */1482250000004514696 OR */1482250000004511868) OR (friidrott OR sports OR idrett OR idrott* OR football OR Футбол OR fodbold OR Fußball OR Jalgpall OR Jalkapallo OR f*tbol* OR Voetbal OR "piłka nożna" OR Futebol OR Nogomet OR archery OR lukostřelba OR bueskydning OR bogenschießen OR τοξοβολία OR boogschieten OR bueskyting OR bågskytte OR aerobatics OR akrobacie OR kunstflyvning OR Kunstflug OR Αερόμπικ OR acrobaţie OR wielersport OR cyclist OR ciclism* OR cyklistika OR dviračių OR kerékpározás OR pyöräily OR Ποδηλασία OR radfahren OR cykling OR "tour de france" OR karate OR tennis OR tenis* OR τένις OR олимпиада OR ol*mpi* OR paralympics OR "Olympische Spelen" OR "olympiska spelen" OR "jeux olympiques" OR "Olympische Spiele" OR "pierre de coubertin" OR "world cup" OR "coupe du monde" OR "världsmästerskapen" OR wereldbeker OR vasaloppet OR spartakiad OR golf OR cricket OR badminton OR rugby OR darts OR hockey OR athletics OR atletiek OR Атлетика OR atletika OR atletism* OR atletik OR Leichtathletik OR αθλητισμός OR yleisurheilu OR athlétisme OR atletica OR атлетика OR atletiek OR friidrett OR lekkoatletyka OR atletikë OR Атлетика OR friidrott OR "table tennis" OR marathon OR "grand prix" OR "motor racing" OR rallying OR rally OR badminton OR baseball OR basketball OR billiards OR bowling OR boxing OR boxning OR canoeing OR kayaking OR fencing OR faktning OR fechten OR handball OR hockey OR gymnastics OR gymnastik* OR lacrosse OR netball OR stadium OR stadion OR rowing OR skiing OR slalom OR skidåkning OR skridskoåkning OR Schaatsen OR skating OR "ski alpin" OR surfing OR wrestling OR brottning OR volleyball OR orientering OR swimming OR schwimmen OR pelota OR curling OR diving OR judo OR taekwando OR triathlon OR "water polo" OR wasserball OR motorsport) AND (has_thumbnails:TRUE) NOT( foaf_organization:(*/1482250000004516035 OR */1482250000000362501 OR */1482250000004513420 OR */1482250000004513237 OR */1482250000004503433 OR */1482250000004509055 OR */1482250000003457295 OR */1482250000002136023 OR */1482250000004477290 OR */1482250000004500666 OR */1482250000004671150 OR */1482250000004671077) OR (edm_datasetName:("2023822_AG-EU_LinkedHeritage_askaboutireland.ie" OR *_Europe_at_work*)) OR (what:"esine" OR "nuolenkärki" OR "amulet" OR "archaeological sites") OR (foaf_organization:(*/1482250000004502115) AND "Golf von"))
+
+
+ ww1
+ (PROVIDER:"Europeana 1914-1918") OR (foaf_organization:(*/1482250000002112001) AND what:"premiere guerre mondiale") OR (proxy_dcterms_isPartOf:"Europeana Newspapers" AND YEAR:[1914 TO 1918]) OR (foaf_organization:(*/1482250000004671158) AND what:EFG1914) OR (foaf_organization:(*/1482250000004516145) AND "Primera Guerra Mundial") OR (foaf_organization:(*/1482250000002678009) AND ("première guerre mondiale" OR "bataille de Verdun" OR "grande guerre" OR "guerre 1914-1918" OR "guerre 14-18" OR 1914 OR 1915 OR 1916 OR 1917 OR 1918)) OR ((what:("First World War" OR "World War I" OR "World War, 1914-1918" OR "premiere guerre mondiale" OR "prima guerra mondiale" OR "Erste Weltkrieg" OR "world war one" OR "Первая мировая война" OR "Ensimmäinen maailmansota" OR "első világháború" OR "Primul Război Mondial" OR "Први светски рат" OR "I wojna światowa")) AND provider_aggregation_edm_isShownBy:*) NOT edm_datasetName:(*AthensAcademy2*)
+
+
+ ccsearch
+ (edm_datasetName:("92062_Ag_EU_TEL_a0480_Austria" OR "9200387_Ag_EU_TEL_a1225_British_Library" OR "9200397_Ag_EU_TEL_ a1348_BL") OR (foaf_organization:(*/1482250000000370517) OR foaf_organization:(*/1482250000004671150))) AND contentTier:[3 TO 4]
+
+
+
+
diff --git a/metis-indexing/src/test/resources/solr/schema.xml b/metis-indexing/src/test/resources/solr/schema.xml
new file mode 100644
index 0000000000..d6ae3e61e5
--- /dev/null
+++ b/metis-indexing/src/test/resources/solr/schema.xml
@@ -0,0 +1,353 @@
+
+
+ europeana_id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/metis-indexing/src/test/resources/solr/solrconfig.xml b/metis-indexing/src/test/resources/solr/solrconfig.xml
new file mode 100644
index 0000000000..4a64c8f6c6
--- /dev/null
+++ b/metis-indexing/src/test/resources/solr/solrconfig.xml
@@ -0,0 +1,2379 @@
+
+
+
+
+
+
+
+
+ 6.6.2
+
+
+ ${solr.abortOnConfigurationError:true}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${solr.data.dir:}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10
+ 10
+
+
+
+
+
+
+
+ ${solr.lock.type:native}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${solr.ulog.dir:}
+ ${solr.ulog.numVersionBuckets:65536}
+
+
+
+
+
+ ${solr.autoCommit.maxTime:60000}
+ false
+
+
+
+
+ ${solr.autoSoftCommit.maxTime:900000}
+ true
+
+
+
+
+
+
+
+
+
+
+
+ 1024
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+ 96
+
+
+ 960
+
+
+
+
+
+
+ *:*
+ score desc, europeana_id asc
+ LANGUAGE
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ TYPE
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ YEAR
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ PROVIDER
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ DATA_PROVIDER
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ COUNTRY
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ RIGHTS
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ UGC
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ facet_tags
+ 24
+
+
+ PROVIDER:"Europeana Fashion"
+ score desc
+ CREATOR
+ cc_skos_prefLabel.ar
+ cc_skos_prefLabel.az
+ cc_skos_prefLabel.be
+ cc_skos_prefLabel.bg
+ cc_skos_prefLabel.bs
+ cc_skos_prefLabel.ca
+ cc_skos_prefLabel.cs
+ cc_skos_prefLabel.cz
+ cc_skos_prefLabel.da
+ cc_skos_prefLabel.de
+ cc_skos_prefLabel.def
+ cc_skos_prefLabel.el
+ cc_skos_prefLabel.en
+ cc_skos_prefLabel.en-gb
+ cc_skos_prefLabel.en-us
+ cc_skos_prefLabel.es
+ cc_skos_prefLabel.eu
+ cc_skos_prefLabel.fi
+ cc_skos_prefLabel.fr
+ cc_skos_prefLabel.ga
+ cc_skos_prefLabel.gd
+ cc_skos_prefLabel.gl
+ cc_skos_prefLabel.he
+ cc_skos_prefLabel.hi
+ cc_skos_prefLabel.hr
+ cc_skos_prefLabel.hu
+ cc_skos_prefLabel.hy
+ cc_skos_prefLabel.is
+ cc_skos_prefLabel.ik
+ cc_skos_prefLabel.ja
+ cc_skos_prefLabel.ka
+ cc_skos_prefLabel.ko
+ cc_skos_prefLabel.la
+ cc_skos_prefLabel.lt
+ cc_skos_prefLabel.lv
+ cc_skos_prefLabel.mk
+ cc_skos_prefLabel.mt
+ cc_skos_prefLabel.nl
+ cc_skos_prefLabel.nn
+ cc_skos_prefLabel.no
+ cc_skos_prefLabel.pl
+ cc_skos_prefLabel.pt
+ cc_skos_prefLabel.ro
+ cc_skos_prefLabel.ru
+ cc_skos_prefLabel.sh
+ cc_skos_prefLabel.sl
+ cc_skos_prefLabel.sg
+ cc_skos_prefLabel.sp
+ cc_skos_prefLabel.su
+ cc_skos_prefLabel.tr
+ cc_skos_prefLabel.uk
+ cc_skos_prefLabel.yi
+ cc_skos_prefLabel.zh
+ skos_concept
+ proxy_dc_format.ar
+ proxy_dc_format.az
+ proxy_dc_format.be
+ proxy_dc_format.bg
+ proxy_dc_format.bs
+ proxy_dc_format.ca
+ proxy_dc_format.cs
+ proxy_dc_format.cz
+ proxy_dc_format.da
+ proxy_dc_format.de
+ proxy_dc_format.def
+ proxy_dc_format.el
+ proxy_dc_format.en
+ proxy_dc_format.en-gb
+ proxy_dc_format.en-us
+ proxy_dc_format.es
+ proxy_dc_format.eu
+ proxy_dc_format.fi
+ proxy_dc_format.fr
+ proxy_dc_format.ga
+ proxy_dc_format.gd
+ proxy_dc_format.gl
+ proxy_dc_format.he
+ proxy_dc_format.hi
+ proxy_dc_format.hr
+ proxy_dc_format.hu
+ proxy_dc_format.hy
+ proxy_dc_format.is
+ proxy_dc_format.ik
+ proxy_dc_format.ja
+ proxy_dc_format.ka
+ proxy_dc_format.ko
+ proxy_dc_format.la
+ proxy_dc_format.lt
+ proxy_dc_format.lv
+ proxy_dc_format.mk
+ proxy_dc_format.mt
+ proxy_dc_format.nl
+ proxy_dc_format.nn
+ proxy_dc_format.no
+ proxy_dc_format.pl
+ proxy_dc_format.pt
+ proxy_dc_format.ro
+ proxy_dc_format.ru
+ proxy_dc_format.sh
+ proxy_dc_format.sl
+ proxy_dc_format.sg
+ proxy_dc_format.sp
+ proxy_dc_format.su
+ proxy_dc_format.tr
+ proxy_dc_format.uk
+ proxy_dc_format.yi
+ proxy_dc_format.zh
+
+
+ collection:archaeology
+
+
+ collection:art
+
+
+ collection:fashion
+
+
+ collection:manuscript
+
+
+ collection:map
+
+
+ collection:migration
+
+
+ collection:music
+
+
+ collection:nature
+
+
+ collection:newspaper
+
+
+ collection:photography
+
+
+ collection:sport
+
+
+ collection:ww1
+
+
+ collection:industrial
+
+
+ collection:ccsearch
+
+
+ contentTier:(2 OR 3 OR 4)
+
+
+ contentTier:(1 OR 2 OR 3 OR 4)
+
+
+ collection:archaeology AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:art AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:fashion AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:manuscript AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:map AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:migration AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:music AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:nature AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:newspaper AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:photography AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:sport AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:ww1 AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:industrial AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:ccsearch AND contentTier:(2 OR 3 OR 4)
+
+
+
+
+
+
+ *:*
+ score desc, europeana_id asc
+ LANGUAGE
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ TYPE
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ YEAR
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ PROVIDER
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ DATA_PROVIDER
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ COUNTRY
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ RIGHTS
+ 25
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ UGC
+ 24
+
+
+ *:*
+ score desc, europeana_id asc
+ facet_tags
+ 24
+
+
+ PROVIDER:"Europeana Fashion"
+ score desc
+ CREATOR
+ cc_skos_prefLabel.ar
+ cc_skos_prefLabel.az
+ cc_skos_prefLabel.be
+ cc_skos_prefLabel.bg
+ cc_skos_prefLabel.bs
+ cc_skos_prefLabel.ca
+ cc_skos_prefLabel.cs
+ cc_skos_prefLabel.cz
+ cc_skos_prefLabel.da
+ cc_skos_prefLabel.de
+ cc_skos_prefLabel.def
+ cc_skos_prefLabel.el
+ cc_skos_prefLabel.en
+ cc_skos_prefLabel.en-gb
+ cc_skos_prefLabel.en-us
+ cc_skos_prefLabel.es
+ cc_skos_prefLabel.eu
+ cc_skos_prefLabel.fi
+ cc_skos_prefLabel.fr
+ cc_skos_prefLabel.ga
+ cc_skos_prefLabel.gd
+ cc_skos_prefLabel.gl
+ cc_skos_prefLabel.he
+ cc_skos_prefLabel.hi
+ cc_skos_prefLabel.hr
+ cc_skos_prefLabel.hu
+ cc_skos_prefLabel.hy
+ cc_skos_prefLabel.is
+ cc_skos_prefLabel.ik
+ cc_skos_prefLabel.ja
+ cc_skos_prefLabel.ka
+ cc_skos_prefLabel.ko
+ cc_skos_prefLabel.la
+ cc_skos_prefLabel.lt
+ cc_skos_prefLabel.lv
+ cc_skos_prefLabel.mk
+ cc_skos_prefLabel.mt
+ cc_skos_prefLabel.nl
+ cc_skos_prefLabel.nn
+ cc_skos_prefLabel.no
+ cc_skos_prefLabel.pl
+ cc_skos_prefLabel.pt
+ cc_skos_prefLabel.ro
+ cc_skos_prefLabel.ru
+ cc_skos_prefLabel.sh
+ cc_skos_prefLabel.sl
+ cc_skos_prefLabel.sg
+ cc_skos_prefLabel.sp
+ cc_skos_prefLabel.su
+ cc_skos_prefLabel.tr
+ cc_skos_prefLabel.uk
+ cc_skos_prefLabel.yi
+ cc_skos_prefLabel.zh
+ skos_concept
+ proxy_dc_format.ar
+ proxy_dc_format.az
+ proxy_dc_format.be
+ proxy_dc_format.bg
+ proxy_dc_format.bs
+ proxy_dc_format.ca
+ proxy_dc_format.cs
+ proxy_dc_format.cz
+ proxy_dc_format.da
+ proxy_dc_format.de
+ proxy_dc_format.def
+ proxy_dc_format.el
+ proxy_dc_format.en
+ proxy_dc_format.en-gb
+ proxy_dc_format.en-us
+ proxy_dc_format.es
+ proxy_dc_format.eu
+ proxy_dc_format.fi
+ proxy_dc_format.fr
+ proxy_dc_format.ga
+ proxy_dc_format.gd
+ proxy_dc_format.gl
+ proxy_dc_format.he
+ proxy_dc_format.hi
+ proxy_dc_format.hr
+ proxy_dc_format.hu
+ proxy_dc_format.hy
+ proxy_dc_format.is
+ proxy_dc_format.ik
+ proxy_dc_format.ja
+ proxy_dc_format.ka
+ proxy_dc_format.ko
+ proxy_dc_format.la
+ proxy_dc_format.lt
+ proxy_dc_format.lv
+ proxy_dc_format.mk
+ proxy_dc_format.mt
+ proxy_dc_format.nl
+ proxy_dc_format.nn
+ proxy_dc_format.no
+ proxy_dc_format.pl
+ proxy_dc_format.pt
+ proxy_dc_format.ro
+ proxy_dc_format.ru
+ proxy_dc_format.sh
+ proxy_dc_format.sl
+ proxy_dc_format.sg
+ proxy_dc_format.sp
+ proxy_dc_format.su
+ proxy_dc_format.tr
+ proxy_dc_format.uk
+ proxy_dc_format.yi
+ proxy_dc_format.zh
+
+
+ collection:archaeology
+
+
+ collection:art
+
+
+ collection:fashion
+
+
+ collection:manuscript
+
+
+ collection:map
+
+
+ collection:migration
+
+
+ collection:music
+
+
+ collection:nature
+
+
+ collection:newspaper
+
+
+ collection:photography
+
+
+ collection:sport
+
+
+ collection:ww1
+
+
+ collection:industrial
+
+
+ collection:ccsearch
+
+
+ contentTier:(2 OR 3 OR 4)
+
+
+ contentTier:(1 OR 2 OR 3 OR 4)
+
+
+ collection:archaeology AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:art AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:fashion AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:manuscript AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:map AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:migration AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:music AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:nature AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:newspaper AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:photography AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:sport AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:ww1 AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:industrial AND contentTier:(2 OR 3 OR 4)
+
+
+ collection:ccsearch AND contentTier:(2 OR 3 OR 4)
+
+
+
+
+
+ true
+
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ max-age=2000000, public
+
+
+
+
+
+
+
+
+
+
+
+ explicit
+ 10
+ enum
+ enum
+ enum
+
+
+
+ elevator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ edismax
+ explicit
+ 1
+
+
+ elevator
+
+
+
+
+
+ explicit
+
+
+
+ elevator
+
+
+
+
+
+ explicit
+
+
+ spellcheck
+
+
+
+
+
+
+
+ explicit
+ 0.01
+
+
+
+ text^0.5 title^1.5 description PROVIDER YEAR^1.5 date^1.4 creator^1.5 dc_publisher subject
+ TYPE source language provider_aggregation_edm_dataProvider^1.5 proxy_dc_title^1.5
+ text^0.8 title^1.5 date^1.4 creator^1.5
+ ord(popularity)^0.5
+ europeana_uri, title, YEAR, source, LANGUAGE, TYPE, contributor, proxy_dc_title, provider_aggregation_edm_dataProvider
+ 2<-1 5<-2 6<90%
+ 100
+ *:*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ spellcheck
+
+
+
+
+
+
+ explicit
+ json
+ true
+
+
+
+
+
+
+
+ explicit
+
+
+
+ browse
+ layout
+
+ Solritas
+
+ edismax
+ *:*
+ 10
+ *,score
+ text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
+ text,features,name,sku,id,manu,cat
+ 3
+
+ text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
+
+ on
+ cat
+ manu_exact
+ ipod
+ GB
+ 1
+ cat,inStock
+ after
+ price
+ 0
+ 600
+ 50
+ popularity
+ 0
+ 10
+ 3
+ manufacturedate_dt
+ NOW/YEAR-10YEARS
+ NOW
+ +1YEAR
+ before
+ after
+
+
+
+ on
+ text features name
+ 0
+ name
+
+
+ spellcheck
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+ text
+
+
+
+
+
+ files-update-processor
+
+
+
+
+
+ dih-config.xml
+
+
+
+
+
+ dih-config.xml
+
+
+
+
+
+
+
+ text
+ true
+ ignored_
+
+
+ true
+ links
+ ignored_
+
+
+
+
+
+
+
+
+
+
+
+
+ /select
+ solrpingquery
+
+
+ all
+
+
+
+
+
+
+ explicit
+ true
+
+
+
+
+
+
+
+
+ textSpell
+
+ default
+ text
+ ./spellchecker
+ true
+
+
+
+
+
+
+
+
+ suggestTitle
+ title
+ org.apache.solr.spelling.suggest.Suggester
+ org.apache.solr.spelling.suggest.fst.WFSTLookupFactory
+ 0
+ true
+
+
+
+
+
+ suggestWho
+ who
+ org.apache.solr.spelling.suggest.Suggester
+ org.apache.solr.spelling.suggest.fst.WFSTLookupFactory
+ 0
+ true
+
+
+
+
+
+ suggestWhat
+ what
+ org.apache.solr.spelling.suggest.Suggester
+ org.apache.solr.spelling.suggest.fst.WFSTLookupFactory
+ 0
+ true
+
+
+
+
+
+ suggestWhen
+ when
+ org.apache.solr.spelling.suggest.Suggester
+ org.apache.solr.spelling.suggest.fst.WFSTLookupFactory
+ 0
+ true
+
+
+
+
+
+ suggestWhere
+ where
+ org.apache.solr.spelling.suggest.Suggester
+ org.apache.solr.spelling.suggest.fst.WFSTLookupFactory
+ 0
+ true
+
+
+
+
+
+ true
+ suggestTitle
+
+ 10
+ true
+ true
+ 10
+ 5
+ true
+
+
+
+ suggestTitle
+ query
+
+
+
+
+
+ true
+ suggestWho
+
+ 10
+ true
+ true
+ 10
+ 5
+ true
+
+
+ suggestWho
+ query
+
+
+
+
+
+ true
+ suggestWhat
+
+ 10
+ true
+ true
+ 10
+ 5
+ true
+
+
+ suggestWhat
+ query
+
+
+
+
+
+ true
+ suggestWhen
+
+ 10
+ true
+ true
+ 10
+ 5
+ true
+
+
+ suggestWhen
+ query
+
+
+
+
+
+ true
+ suggestWhere
+ 10
+ 10
+ true
+ true
+ 10
+ 5
+ true
+
+
+ suggestWhere
+ query
+
+
+
+
+
+
+
+ default
+ wordbreak
+ on
+ true
+ 10
+ 5
+ 5
+ true
+ true
+ 10
+ 5
+
+
+ spellcheck
+
+
+
+
+
+ default
+ false
+ false
+ 1
+ true
+
+
+
+
+ spellcheck
+ elevator
+
+
+
+
+
+
+
+
+ true
+
+
+ tvComponent
+
+
+
+
+
+
+
+ lingo
+ org.carrot2.clustering.lingo.LingoClusteringAlgorithm
+ 20
+ ENGLISH
+
+
+
+
+ stc
+ org.carrot2.clustering.stc.STCClusteringAlgorithm
+
+
+
+
+
+ true
+ true
+
+
+ id
+ name
+ features
+ false
+
+ edismax
+ text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
+ *:*
+
+ 10
+ *,score
+
+
+
+
+ clustering
+
+
+
+
+
+
+
+
+
+ true
+ false
+
+
+ terms
+
+
+
+
+
+
+
+ text_general
+ elevate.xml
+
+
+
+
+
+ explicit
+
+
+ elevator
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+ 70
+
+ 0.5
+
+ [-\w ,/\n\"']{20,200}
+
+
+
+
+
+
+ ]]>
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,,
+ ,,
+ ,,
+ ,,
+ ,]]>
+ ]]>
+
+
+
+
+
+ 10
+ .,!?
+
+
+
+
+
+
+ WORD
+
+
+ en
+ US
+
+
+
+
+
+
+
+
+
+
+
+
+ [^\w-\.]
+ _
+
+
+ solr.StrField
+ 8000
+
+
+
+
+
+
+ yyyy-MM-dd'T'HH:mm:ss.SSSZ
+ yyyy-MM-dd'T'HH:mm:ss,SSSZ
+ yyyy-MM-dd'T'HH:mm:ss.SSS
+ yyyy-MM-dd'T'HH:mm:ss,SSS
+ yyyy-MM-dd'T'HH:mm:ssZ
+ yyyy-MM-dd'T'HH:mm:ss
+ yyyy-MM-dd'T'HH:mmZ
+ yyyy-MM-dd'T'HH:mm
+ yyyy-MM-dd HH:mm:ss.SSSZ
+ yyyy-MM-dd HH:mm:ss,SSSZ
+ yyyy-MM-dd HH:mm:ss.SSS
+ yyyy-MM-dd HH:mm:ss,SSS
+ yyyy-MM-dd HH:mm:ssZ
+ yyyy-MM-dd HH:mm:ss
+ yyyy-MM-dd HH:mmZ
+ yyyy-MM-dd HH:mm
+ yyyy-MM-dd
+
+
+
+
+ timestamp_update
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/plain; charset=UTF-8
+
+
+
+
+ ${velocity.template.base.dir:}
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/metis-media-service/pom.xml b/metis-media-service/pom.xml
index 6c45675b48..79dfeab5dd 100644
--- a/metis-media-service/pom.xml
+++ b/metis-media-service/pom.xml
@@ -4,7 +4,7 @@
metis-frameworkeu.europeana.metis
- 9
+ 10metis-media-service
diff --git a/metis-media-service/src/main/java/eu/europeana/metis/mediaprocessing/extraction/AudioVideoProcessor.java b/metis-media-service/src/main/java/eu/europeana/metis/mediaprocessing/extraction/AudioVideoProcessor.java
index 702ee27335..22da7f267b 100644
--- a/metis-media-service/src/main/java/eu/europeana/metis/mediaprocessing/extraction/AudioVideoProcessor.java
+++ b/metis-media-service/src/main/java/eu/europeana/metis/mediaprocessing/extraction/AudioVideoProcessor.java
@@ -27,6 +27,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -274,7 +275,7 @@ AbstractResourceMetadata parseCommandResponse(Resource resource, String detected
// Process the video or audio stream and create metadata
final AbstractResourceMetadata metadata;
- final Long fileSize = nullIfNegative(format.getLong("size"));
+ final Long fileSize = findLong("size", new JSONObject[]{format});
if (isVideo) {
// We have a video file
final JSONObject[] candidates = new JSONObject[]{videoStream, format};
@@ -335,27 +336,33 @@ private Double calculateFrameRate(String frameRateString) {
}
Integer findInt(String key, JSONObject[] candidates) {
- final int result = findValue(key, candidates,
+ final Integer result = findValue(candidates,
candidate -> candidate.optInt(key, Integer.MIN_VALUE),
value -> Integer.MIN_VALUE != value);
return nullIfNegative(result);
}
+ Long findLong(String key, JSONObject[] candidates){
+ final Long result = findValue(candidates,
+ candidate -> candidate.optLong(key, Long.MIN_VALUE),
+ value -> Long.MIN_VALUE != value);
+ return nullIfNegative(result);
+ }
+
Double findDouble(String key, JSONObject[] candidates) {
- final double result = findValue(key, candidates,
+ final Double result = findValue(candidates,
candidate -> candidate.optDouble(key, Double.NaN), value -> !value.isNaN());
return nullIfNegative(result);
}
String findString(String key, JSONObject[] candidates) {
- return findValue(key, candidates, candidate -> candidate.optString(key, StringUtils.EMPTY),
+ return findValue(candidates, candidate -> candidate.optString(key, StringUtils.EMPTY),
StringUtils::isNotBlank);
}
- T findValue(String key, JSONObject[] candidates, Function valueGetter,
+ T findValue(JSONObject[] candidates, Function valueGetter,
Predicate valueValidator) {
- return Stream.of(candidates).map(valueGetter).filter(valueValidator).findFirst()
- .orElseThrow(() -> new JSONException("Could not find value for field: " + key));
+ return Stream.of(candidates).map(valueGetter).filter(valueValidator).findFirst().orElse(null);
}
JSONObject findStream(JSONObject data, String codecType) {
diff --git a/metis-media-service/src/test/java/eu/europeana/metis/mediaprocessing/extraction/AudioVideoProcessorTest.java b/metis-media-service/src/test/java/eu/europeana/metis/mediaprocessing/extraction/AudioVideoProcessorTest.java
index d1d4852884..aa0021d162 100644
--- a/metis-media-service/src/test/java/eu/europeana/metis/mediaprocessing/extraction/AudioVideoProcessorTest.java
+++ b/metis-media-service/src/test/java/eu/europeana/metis/mediaprocessing/extraction/AudioVideoProcessorTest.java
@@ -15,6 +15,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -45,6 +46,7 @@
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
+import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -199,20 +201,19 @@ void testFindValue() {
// Test first object only
final Function firstOnly = object -> object == object1 ? value : null;
- assertEquals(value, audioVideoProcessor.findValue(key, objects, firstOnly, Objects::nonNull));
+ assertEquals(value, audioVideoProcessor.findValue(objects, firstOnly, Objects::nonNull));
// Test second object only
final Function secondOnly = object -> object == object2 ? value : null;
- assertEquals(value, audioVideoProcessor.findValue(key, objects, secondOnly, Objects::nonNull));
+ assertEquals(value, audioVideoProcessor.findValue(objects, secondOnly, Objects::nonNull));
// Test both
final Function both = object -> value;
- assertEquals(value, audioVideoProcessor.findValue(key, objects, both, Objects::nonNull));
+ assertEquals(value, audioVideoProcessor.findValue(objects, both, Objects::nonNull));
// Test neither
final Function neither = object -> null;
- assertThrows(JSONException.class,
- () -> audioVideoProcessor.findValue(key, objects, neither, Objects::nonNull));
+ assertNull(audioVideoProcessor.findValue(objects, neither, Objects::nonNull));
}
@Test
@@ -230,7 +231,21 @@ void testFindInt() {
// Check not available
doAnswer(invocation -> invocation.getArgument(1)).when(object).optInt(eq(key), anyInt());
- assertThrows(JSONException.class, () -> audioVideoProcessor.findInt(key, objects));
+ assertNull(audioVideoProcessor.findInt(key, objects));
+ }
+
+ @Test
+ void testFindLong(){
+ final JSONObject object = mock(JSONObject.class);
+ final JSONObject[] objects = new JSONObject[]{object};
+ final String key = "key";
+ final long value = 1L;
+
+ doReturn(value).when(object).optLong(eq(key), anyLong());
+ assertEquals(value, audioVideoProcessor.findLong(key, objects));
+
+ doReturn(Long.MIN_VALUE).when(object).optLong(eq(key), anyLong());
+ assertNull(audioVideoProcessor.findLong(key, objects));
}
@Test
@@ -247,8 +262,8 @@ void testFindDouble() {
assertEquals(value, audioVideoProcessor.findDouble(key, objects));
// Check not available
- doAnswer(invocation -> invocation.getArgument(1)).when(object).optDouble(eq(key), anyDouble());
- assertThrows(JSONException.class, () -> audioVideoProcessor.findDouble(key, objects));
+ doReturn(Double.NaN).when(object).optDouble(eq(key), anyDouble());
+ assertNull(audioVideoProcessor.findDouble(key, objects));
}
@Test
@@ -265,8 +280,8 @@ void testFindString() {
assertEquals(value, audioVideoProcessor.findString(key, objects));
// Check not available
- doAnswer(invocation -> invocation.getArgument(1)).when(object).optString(eq(key), anyString());
- assertThrows(JSONException.class, () -> audioVideoProcessor.findString(key, objects));
+ doReturn(StringUtils.EMPTY).when(object).optString(eq(key), anyString());
+ assertNull(audioVideoProcessor.findString(key, objects));
}
@Test
@@ -285,6 +300,7 @@ void testParseCommandResponseForAudio() throws MediaExtractionException, IOExcep
doReturn(object).when(audioVideoProcessor).readCommandResponseToJson(commandResponse);
final JSONObject format = mock(JSONObject.class);
doReturn(format).when(object).getJSONObject("format");
+ final JSONObject[] formatAsArray = new JSONObject[]{format};
final JSONObject audioStream = mock(JSONObject.class);
doReturn(audioStream).when(audioVideoProcessor).findStream(object, "audio");
doReturn(null).when(audioVideoProcessor).findStream(object, "video");
@@ -297,7 +313,7 @@ void testParseCommandResponseForAudio() throws MediaExtractionException, IOExcep
final Integer bitsPerSample = 8;
final Double duration = 180.062050;
final Integer bitRate = 320000;
- doReturn(size).when(format).getLong("size");
+ doReturn(size).when(audioVideoProcessor).findLong(eq("size"), eq(formatAsArray));
doReturn(sampleRate).when(audioVideoProcessor).findInt(eq("sample_rate"), eq(candidates));
doReturn(channels).when(audioVideoProcessor).findInt(eq("channels"), eq(candidates));
doReturn(bitsPerSample).when(audioVideoProcessor)
@@ -340,6 +356,7 @@ void testParseCommandResponseForVideo() throws MediaExtractionException, IOExcep
doReturn(object).when(audioVideoProcessor).readCommandResponseToJson(commandResponse);
final JSONObject format = mock(JSONObject.class);
doReturn(format).when(object).getJSONObject("format");
+ final JSONObject[] formatAsArray = new JSONObject[]{format};
final JSONObject audioStream = mock(JSONObject.class);
doReturn(audioStream).when(audioVideoProcessor).findStream(object, "audio");
final JSONObject videoStream = mock(JSONObject.class);
@@ -354,7 +371,7 @@ void testParseCommandResponseForVideo() throws MediaExtractionException, IOExcep
final Integer bitRate = 595283;
final int frameRateNumerator = 629150;
final int frameRateDenominator = 25181;
- doReturn(size).when(format).getLong("size");
+ doReturn(size).when(audioVideoProcessor).findLong(eq("size"), eq(formatAsArray));
doReturn(width).when(audioVideoProcessor).findInt(eq("width"), eq(candidates));
doReturn(height).when(audioVideoProcessor).findInt(eq("height"), eq(candidates));
doReturn("h264").when(audioVideoProcessor).findString(eq("codec_name"), eq(candidates));
diff --git a/metis-normalization/pom.xml b/metis-normalization/pom.xml
index 7aca749d15..14d215386b 100644
--- a/metis-normalization/pom.xml
+++ b/metis-normalization/pom.xml
@@ -4,7 +4,7 @@
metis-frameworkeu.europeana.metis
- 9
+ 10metis-normalization
@@ -19,6 +19,10 @@
mockito-core
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ com.ibm.icuicu4j
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/NormalizerImpl.java b/metis-normalization/src/main/java/eu/europeana/normalization/NormalizerImpl.java
index 3614f45cc8..35bef00770 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/NormalizerImpl.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/NormalizerImpl.java
@@ -1,28 +1,29 @@
package eu.europeana.normalization;
+import eu.europeana.normalization.model.NormalizationBatchResult;
+import eu.europeana.normalization.model.NormalizationReport;
+import eu.europeana.normalization.model.NormalizationResult;
+import eu.europeana.normalization.normalizers.RecordNormalizeAction;
+import eu.europeana.normalization.util.NormalizationException;
+import eu.europeana.normalization.util.XmlException;
+import eu.europeana.normalization.util.XmlUtil;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
+import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
-import eu.europeana.normalization.model.NormalizationBatchResult;
-import eu.europeana.normalization.model.NormalizationReport;
-import eu.europeana.normalization.model.NormalizationResult;
-import eu.europeana.normalization.normalizers.RecordNormalizeAction;
-import eu.europeana.normalization.util.NormalizationException;
-import eu.europeana.normalization.util.XmlException;
-import eu.europeana.normalization.util.XmlUtil;
/**
* This class is the implementation of the {@link Normalizer} interface.
*/
class NormalizerImpl implements Normalizer {
- private static final Logger LOGGER = LoggerFactory.getLogger(NormalizerImpl.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final RecordNormalizeAction recordNormalizer;
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationExtractorMatchId.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationExtractorMatchId.java
index 90881940fa..c9c714799b 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationExtractorMatchId.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationExtractorMatchId.java
@@ -4,24 +4,22 @@
* Identifies the pattern that was matched, or if none of the patterns matched, or if a date matched a pattern but was invalid
*/
public enum DateNormalizationExtractorMatchId {
- BC_AD("normalisable: BC/AD date"),
- BRIEF_DATE_RANGE("normalisable: brief year range"),
- CENTURY_NUMERIC("normalisable: century (numeric)"),
- CENTURY_RANGE_ROMAN("normalisable: century range (roman numerals)"),
- CENTURY_ROMAN("normalisable: century (roman numerals)"),
- DCMI_PERIOD("normalisable: DCMI period"),
- DECADE("normalisable: decade"),
+ BC_AD("BC/AD date"),
+ BRIEF_DATE_RANGE("brief year range"),
+ CENTURY_NUMERIC("century (numeric)"),
+ CENTURY_RANGE_ROMAN("century range (roman numerals)"),
+ CENTURY_ROMAN("century (roman numerals)"),
+ DCMI_PERIOD("DCMI period"),
+ DECADE("decade"),
EDTF("already normalised in EDTF"),
- FORMATTED_FULL_DATE("normalisable: formatted timestamp"),
- INVALID("not normalisable: date apparently invalid"),
- LONG_YEAR("normalisable: long negative year"),
- MONTH_NAME("normalisable: date with month name"),
- NO_MATCH("not normalisable: no match with existing patterns"),
- NUMERIC_ALL_VARIANTS("normalisable: numeric date (various separators)"),
- NUMERIC_ALL_VARIANTS_XX("normalisable: numeric date (various separators and unknown parts)"),
- NUMERIC_RANGE_ALL_VARIANTS("normalisable: numeric date interval (various separators)"),
- NUMERIC_RANGE_ALL_VARIANTS_XX("normalisable: numeric date interval (various separators and unknown parts)"),
- YYYY_MM_DD_SPACES("normalisable: numeric date (whitespace separators)");
+ FORMATTED_FULL_DATE("formatted timestamp"),
+ LONG_NEGATIVE_YEAR("long negative year"),
+ MONTH_NAME("date with month name"),
+ NUMERIC_ALL_VARIANTS("numeric date (various separators)"),
+ NUMERIC_ALL_VARIANTS_XX("numeric date (various separators and unknown parts)"),
+ NUMERIC_RANGE_ALL_VARIANTS("numeric date interval (various separators)"),
+ NUMERIC_RANGE_ALL_VARIANTS_XX("numeric date interval (various separators and unknown parts)"),
+ YYYY_MM_DD_SPACES("numeric date (whitespace separators)");
final String label;
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationResult.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationResult.java
index cc85ec347e..04c5024a91 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationResult.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationResult.java
@@ -1,9 +1,8 @@
package eu.europeana.normalization.dates;
+import static eu.europeana.normalization.dates.DateNormalizationResultStatus.MATCHED;
+
import eu.europeana.normalization.dates.edtf.AbstractEdtfDate;
-import eu.europeana.normalization.dates.edtf.EdtfDatePart;
-import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
-import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
import eu.europeana.normalization.dates.sanitize.SanitizeOperation;
/**
@@ -15,10 +14,11 @@
*/
public class DateNormalizationResult {
- private DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId;
+ private DateNormalizationResultStatus dateNormalizationResultStatus = MATCHED;
private SanitizeOperation sanitizeOperation;
- private String originalInput;
- private AbstractEdtfDate edtfDate;
+ private final DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId;
+ private final String originalInput;
+ private final AbstractEdtfDate edtfDate;
/**
* Constructor with all parameters.
@@ -34,6 +34,27 @@ public DateNormalizationResult(DateNormalizationExtractorMatchId dateNormalizati
this.edtfDate = edtfDate;
}
+ /**
+ * Copy constructor with adding {@link SanitizeOperation}
+ *
+ * @param dateNormalizationResult the date normalization result to copy
+ * @param sanitizeOperation the sanitization operation
+ */
+ public DateNormalizationResult(DateNormalizationResult dateNormalizationResult, SanitizeOperation sanitizeOperation) {
+ this(dateNormalizationResult.getDateNormalizationExtractorMatchId(), dateNormalizationResult.getOriginalInput(),
+ dateNormalizationResult.getEdtfDate());
+ this.sanitizeOperation = sanitizeOperation;
+ }
+
+ private DateNormalizationResult(DateNormalizationResultStatus dateNormalizationResultStatus, String originalInput) {
+ this(null, originalInput, null);
+ this.dateNormalizationResultStatus = dateNormalizationResultStatus;
+ }
+
+ public DateNormalizationResultStatus getDateNormalizationResultStatus() {
+ return dateNormalizationResultStatus;
+ }
+
/**
* Get an instance of a date normalization result for no matches.
*
@@ -41,106 +62,22 @@ public DateNormalizationResult(DateNormalizationExtractorMatchId dateNormalizati
* @return the no match result
*/
public static DateNormalizationResult getNoMatchResult(String originalInput) {
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.NO_MATCH, originalInput, null);
+ return new DateNormalizationResult(DateNormalizationResultStatus.NO_MATCH, originalInput);
}
public DateNormalizationExtractorMatchId getDateNormalizationExtractorMatchId() {
return dateNormalizationExtractorMatchId;
}
- public void setDateNormalizationExtractorMatchId(DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
- this.dateNormalizationExtractorMatchId = dateNormalizationExtractorMatchId;
- }
-
public SanitizeOperation getSanitizeOperation() {
return sanitizeOperation;
}
- public void setCleanOperation(SanitizeOperation sanitizeOperation) {
- this.sanitizeOperation = sanitizeOperation;
- }
-
public String getOriginalInput() {
return originalInput;
}
- public void setOriginalInput(String originalInput) {
- this.originalInput = originalInput;
- }
-
public AbstractEdtfDate getEdtfDate() {
return edtfDate;
}
-
- public void setEdtfDate(AbstractEdtfDate edtfDate) {
- this.edtfDate = edtfDate;
- }
-
- /**
- * Checks if a date is complete.
- *
This method is used for the generic properties normalization
- *
- * A date is considered complete if:
- *
- *
it contains a date part
- *
it is precise
- *
for intervals: either, both dates are only years(without month or day), or, both dates have month day present
- *
- *
- *
- * @return true if the date is complete
- */
- public boolean isCompleteDate() {
- boolean isCompleteDate = true;
- if (edtfDate == null || edtfDate.isTimeOnly()) {
- isCompleteDate = false;
- } else if (edtfDate instanceof InstantEdtfDate) {
- final EdtfDatePart edtfDatePart = ((InstantEdtfDate) edtfDate).getEdtfDatePart();
- if (isDateNonPrecise(edtfDatePart) || isMonthDayNotComplete(edtfDatePart)) {
- isCompleteDate = false;
- }
- } else {
- final EdtfDatePart startEdtfDatePart = ((IntervalEdtfDate) edtfDate).getStart()
- .getEdtfDatePart();
- final EdtfDatePart endEdtfDatePart = ((IntervalEdtfDate) edtfDate).getEnd()
- .getEdtfDatePart();
-
- if (areBothDatesSpecified(startEdtfDatePart, endEdtfDatePart) &&
- (areDatesNonPrecise(startEdtfDatePart, endEdtfDatePart) || !isOnlyYearsOrComplete(startEdtfDatePart,
- endEdtfDatePart))) {
- isCompleteDate = false;
- }
- }
- return isCompleteDate;
- }
-
- private boolean areBothDatesSpecified(EdtfDatePart startEdtfDatePart, EdtfDatePart endEdtfDatePart) {
- return startEdtfDatePart != null && endEdtfDatePart != null && !startEdtfDatePart.isUnspecified()
- && !endEdtfDatePart.isUnspecified();
- }
-
- private boolean areDatesNonPrecise(EdtfDatePart startEdtfDatePart, EdtfDatePart endEdtfDatePart) {
- return isDateNonPrecise(startEdtfDatePart) || isDateNonPrecise(endEdtfDatePart);
- }
-
- private boolean isDateNonPrecise(EdtfDatePart edtfDatePart) {
- return edtfDatePart.isUnknown() || edtfDatePart.isUncertain() || edtfDatePart.getYearPrecision() != null;
- }
-
- private boolean isMonthDayNotComplete(EdtfDatePart edtfDatePart) {
- return !isMonthDayComplete(edtfDatePart);
- }
-
- private boolean isMonthDayComplete(EdtfDatePart edtfDatePart) {
- return edtfDatePart.getMonth() != null && edtfDatePart.getDay() != null;
- }
-
- private boolean isOnlyYearsOrComplete(EdtfDatePart startEdtfDatePart, EdtfDatePart endEdtfDatePart) {
- final boolean isOnlyYear = startEdtfDatePart.getMonth() == null && endEdtfDatePart.getMonth() == null &&
- startEdtfDatePart.getDay() == null && endEdtfDatePart.getDay() == null;
- final boolean isCompleteDate = isMonthDayComplete(startEdtfDatePart) && isMonthDayComplete(endEdtfDatePart);
-
- return isOnlyYear || isCompleteDate;
- }
-
}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationResultStatus.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationResultStatus.java
new file mode 100644
index 0000000000..e37a619d24
--- /dev/null
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/DateNormalizationResultStatus.java
@@ -0,0 +1,9 @@
+package eu.europeana.normalization.dates;
+
+/**
+ * Defines the status of a date normalization result.
+ */
+public enum DateNormalizationResultStatus {
+ MATCHED,
+ NO_MATCH
+}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/YearPrecision.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/YearPrecision.java
index 5c3c185537..00ff43b11f 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/YearPrecision.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/YearPrecision.java
@@ -4,9 +4,9 @@
* Enum indicating the year precision that can be used to adjust a year.
*/
public enum YearPrecision {
+ YEAR(1),
DECADE(10),
- CENTURY(100),
- MILLENNIUM(1000);
+ CENTURY(100);
static final YearPrecision[] values = YearPrecision.values();
final int duration;
@@ -30,10 +30,10 @@ public int getDuration() {
*/
public static YearPrecision getYearPrecisionByOrdinal(int ordinal) {
final YearPrecision yearPrecision;
- if (ordinal < 1 || ordinal > values().length) {
- yearPrecision = null;
+ if (ordinal < 0 || ordinal > values.length) {
+ yearPrecision = YEAR;
} else {
- yearPrecision = values()[ordinal - 1];
+ yearPrecision = values[ordinal];
}
return yearPrecision;
}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/AbstractEdtfDate.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/AbstractEdtfDate.java
index b81b45cdc4..d840ce71bf 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/AbstractEdtfDate.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/AbstractEdtfDate.java
@@ -1,22 +1,19 @@
package eu.europeana.normalization.dates.edtf;
-import java.io.Serializable;
-
/**
* An abstract class that contains the template that an EDTF date with compliance level 1 should implement.
*
The date can contain a label, but can also be null
*/
-public abstract class AbstractEdtfDate implements Serializable {
+public abstract class AbstractEdtfDate {
- private static final long serialVersionUID = -4111050222535744456L;
private final String label;
- public AbstractEdtfDate() {
+ protected AbstractEdtfDate() {
this.label = null;
}
- public AbstractEdtfDate(String label) {
+ protected AbstractEdtfDate(String label) {
this.label = label;
}
@@ -24,19 +21,9 @@ public String getLabel() {
return label;
}
- public abstract void setApproximate(boolean approx);
-
- public abstract boolean isApproximate();
-
- public abstract void setUncertain(boolean uncertain);
-
- public abstract boolean isUncertain();
-
- public abstract boolean isUnspecified();
-
- public abstract boolean isTimeOnly();
+ public abstract DateQualification getDateQualification();
- public abstract void switchDayAndMonth();
+ public abstract boolean isOpen();
public abstract InstantEdtfDate getFirstDay();
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/DateBoundaryType.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/DateBoundaryType.java
new file mode 100644
index 0000000000..81a574eb2c
--- /dev/null
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/DateBoundaryType.java
@@ -0,0 +1,38 @@
+package eu.europeana.normalization.dates.edtf;
+
+
+/**
+ * Enum that indicates what type of date is in an interval.
+ *
+ * A date in an interval can be:
+ *
+ *
{@link #DECLARED}. Indicates whether there is a value representing an actual date. Not meant to be (de)serialized
+ *
{@link #OPEN}. Indicates whether the date is open, represented by '..' (e.g. if the input EDTF-compliant date interval string was equal to
+ * '1900/..').
+ *
{@link #UNKNOWN} Indicates whether the date is unknown, represented by an empty string ''(deserialization) and '..'(serialization
+ * (e.g. if the input EDTF-compliant date interval string was equal to '1900/').
+ *
+ *
+ */
+public enum DateBoundaryType {
+ DECLARED(null, null),
+ OPEN(DateBoundaryType.DEFAULT_OPEN_STRING, DateBoundaryType.DEFAULT_OPEN_STRING),
+ UNKNOWN("", DateBoundaryType.DEFAULT_OPEN_STRING);
+
+ public static final String DEFAULT_OPEN_STRING = "..";
+ private final String deserializedRepresentation;
+ private final String serializedRepresentation;
+
+ DateBoundaryType(String deserializedRepresentation, String serializedRepresentation) {
+ this.deserializedRepresentation = deserializedRepresentation;
+ this.serializedRepresentation = serializedRepresentation;
+ }
+
+ public String getDeserializedRepresentation() {
+ return deserializedRepresentation;
+ }
+
+ public String getSerializedRepresentation() {
+ return serializedRepresentation;
+ }
+}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/DateQualification.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/DateQualification.java
new file mode 100644
index 0000000000..1c9c06c6ac
--- /dev/null
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/DateQualification.java
@@ -0,0 +1,39 @@
+package eu.europeana.normalization.dates.edtf;
+
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+/**
+ * Date qualification characters according to Extended Date/Time Format (EDTF)
+ * Specification
+ */
+public enum DateQualification {
+
+ NO_QUALIFICATION(""),
+ UNCERTAIN("?"),
+ APPROXIMATE("~"),
+ UNCERTAIN_APPROXIMATE("%");
+
+ public static final Pattern CHECK_QUALIFICATION_PATTERN = Pattern.compile("^[^\\?~%]*([\\?~%]?)$");
+ private final String character;
+
+ DateQualification(String character) {
+ this.character = character;
+ }
+
+ /**
+ * Get the enum value based on the character provided.
+ *
It will return a matched enum value or {@link #NO_QUALIFICATION}.
+ *
+ * @param character the provided character
+ * @return the enum value
+ */
+ public static DateQualification fromCharacter(String character) {
+ return Arrays.stream(DateQualification.values()).filter(value -> value.character.equals(character)).findFirst().orElse(
+ NO_QUALIFICATION);
+ }
+
+ public String getCharacter() {
+ return character;
+ }
+}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfDatePart.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfDatePart.java
deleted file mode 100644
index ca87aedfcc..0000000000
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfDatePart.java
+++ /dev/null
@@ -1,183 +0,0 @@
-package eu.europeana.normalization.dates.edtf;
-
-import eu.europeana.normalization.dates.YearPrecision;
-import java.io.Serializable;
-import java.text.DecimalFormat;
-
-/**
- * Class representing the date part an EDTF date.
- *
- * Support partial dates, including only centuries or decades (e.g., 19XX)
- *
- */
-public class EdtfDatePart implements Serializable {
-
- private static final long serialVersionUID = -7497880706682687923L;
- public static final int THRESHOLD_4_DIGITS_YEAR = 9999;
-
- private boolean uncertain;
- private boolean approximate;
-
- /**
- * Indicates whether the date is unknown (e.g. if the input EDTF-compliant date interval string was equal to
- * '1900/?').
- */
- private boolean unknown;
-
- /**
- * Indicates whether the date is unspecified (e.g. if the input EDTF-compliant date interval string was equal to
- * '1900/').
- */
- private boolean unspecified;
-
- private Integer year;
- private Integer month;
- private Integer day;
-
- private YearPrecision yearPrecision;
-
- public boolean isUncertain() {
- return uncertain;
- }
-
- public void setUncertain(boolean uncertain) {
- this.uncertain = uncertain;
- }
-
- public boolean isApproximate() {
- return approximate;
- }
-
- public void setApproximate(boolean approximate) {
- this.approximate = approximate;
- }
-
- public boolean isUnknown() {
- return unknown;
- }
-
- public void setUnknown(boolean unknown) {
- this.unknown = unknown;
- }
-
- public Integer getYear() {
- return year;
- }
-
- public void setYear(Integer year) {
- this.year = year;
- }
-
- public Integer getMonth() {
- return month;
- }
-
- public void setMonth(Integer month) {
- this.month = month == null || month == 0 ? null : month;
- }
-
- public Integer getDay() {
- return day;
- }
-
- public void setDay(Integer day) {
- this.day = day == null || day == 0 ? null : day;
- }
-
- public boolean isUnspecified() {
- return unspecified;
- }
-
- public void setUnspecified(boolean unspecified) {
- this.unspecified = unspecified;
- }
-
- public YearPrecision getYearPrecision() {
- return yearPrecision;
- }
-
- public void setYearPrecision(YearPrecision yearPrecision) {
- this.yearPrecision = yearPrecision;
- }
-
- /**
- * Switches the values of the day and month.
- */
- public void switchDayAndMonth() {
- if (day != null) {
- int tempDay = day;
- setDay(month);
- setMonth(tempDay);
- }
- }
-
- public static EdtfDatePart getUnknownInstance() {
- final EdtfDatePart edtfDatePart = new EdtfDatePart();
- edtfDatePart.setUnknown(true);
- return edtfDatePart;
- }
-
- public static EdtfDatePart getUnspecifiedInstance() {
- final EdtfDatePart edtfDatePart = new EdtfDatePart();
- edtfDatePart.setUnspecified(true);
- return edtfDatePart;
- }
-
- @Override
- public String toString() {
- final StringBuilder stringBuilder = new StringBuilder();
- if (unknown || unspecified) {
- stringBuilder.append("..");
- } else if (year < -THRESHOLD_4_DIGITS_YEAR || year > THRESHOLD_4_DIGITS_YEAR) {
- stringBuilder.append("Y").append(year);
- } else {
- stringBuilder.append(serializeYear());
-
- //Append Month and day
- final DecimalFormat decimalFormat = new DecimalFormat("00");
- if (month != null && month > 0) {
- stringBuilder.append("-").append(decimalFormat.format(month));
- if (day != null && day > 0) {
- stringBuilder.append("-").append(decimalFormat.format(day));
- }
- }
- //Append approximate/uncertain
- if (approximate && uncertain) {
- stringBuilder.append("%");
- } else if (approximate) {
- stringBuilder.append("~");
- } else if (uncertain) {
- stringBuilder.append("?");
- }
- }
- return stringBuilder.toString();
- }
-
- /**
- * Adjusts a year with padding and optional precision that replace right most digits with 'X's.
- *
- * There are two possibilities:
- *
- *
The year is precise therefore it will be left padded with 0 to the max of 4 digits in total
- *
The year is not precise which will be left padded with 0 to the max of 4 digits in total and then the right most
- * digits are replaces with 'X's based on the year precision. Eg. a year -900 with century precision will become -09XX
- *
- *
- *
- * @return the adjusted year
- */
- private String serializeYear() {
- final DecimalFormat decimalFormat = new DecimalFormat("0000");
- final String paddedYear = decimalFormat.format(Math.abs(year));
-
- final String prefix = year < 0 ? "-" : "";
- final String yearAdjusted;
- if (yearPrecision == null) {
- yearAdjusted = paddedYear;
- } else {
- final int trailingZeros = Integer.numberOfTrailingZeros(yearPrecision.getDuration());
- yearAdjusted = paddedYear.substring(0, 4 - trailingZeros) + "X".repeat(trailingZeros);
- }
- return prefix + yearAdjusted;
- }
-}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfParser.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfParser.java
deleted file mode 100644
index 70531bd00d..0000000000
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfParser.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package eu.europeana.normalization.dates.edtf;
-
-import static java.lang.String.format;
-
-import java.text.ParseException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.apache.commons.lang3.StringUtils;
-
-/**
- * This class implements the deserialization of EDTF strings into the EDTF structure.
- */
-public class EdtfParser {
-
- // TODO: 21/12/2022 This is used transparently for both EDTF parsing as well as Dcmi parsing which is probably
- // incorrect, because of the allowance of the [?%~] modifiers(part of EDTF level1 https://www.loc.gov/standards/datetime/).
- // TODO: 19/07/2022 Simplify regex by potentially splitting it
- private static final Pattern DATE_PATTERN = Pattern
- .compile("((?-?\\d{4})-(?\\d{2})-(?\\d{2})|"
- + "(?-?\\d{4})-(?\\d{2})|" + "(?-?\\d{4})" + ")(?[?%~]?)");
-
-
- public AbstractEdtfDate parse(String edtfString) throws ParseException {
- if (StringUtils.isEmpty(edtfString)) {
- throw new ParseException("Empty argument", 0);
- }
- if (edtfString.contains("/")) {
- return parseInterval(edtfString);
- }
- return parseInstant(edtfString);
- }
-
- protected InstantEdtfDate parseInstant(String edtfString) throws ParseException {
- if (edtfString.contains("T")) {
- String datePart = edtfString.substring(0, edtfString.indexOf('T'));
- if (datePart.isEmpty()) {
- throw new ParseException("Date part is empty which is not allowed", 0);
- }
- return new InstantEdtfDate(parseDate(datePart));
- } else {
- return new InstantEdtfDate(parseDate(edtfString));
- }
- }
-
- protected IntervalEdtfDate parseInterval(String edtfString) throws ParseException {
- String startPart = edtfString.substring(0, edtfString.indexOf('/'));
- String endPart = edtfString.substring(edtfString.indexOf('/') + 1);
- InstantEdtfDate start = parseInstant(startPart);
- InstantEdtfDate end = parseInstant(endPart);
- if ((end.getEdtfDatePart().isUnknown() || end.getEdtfDatePart().isUnspecified()) && (start.getEdtfDatePart().isUnknown()
- || start.getEdtfDatePart().isUnspecified())) {
- throw new ParseException(edtfString, 0);
- }
- return new IntervalEdtfDate(start, end);
- }
-
- protected EdtfDatePart parseDate(String edtfString) throws ParseException {
- final EdtfDatePart edtfDatePart;
- if (edtfString.isEmpty()) {
- edtfDatePart = EdtfDatePart.getUnknownInstance();
- } else if ("..".equals(edtfString)) {
- edtfDatePart = EdtfDatePart.getUnspecifiedInstance();
- } else if (edtfString.startsWith("Y")) {
- edtfDatePart = new EdtfDatePart();
- edtfDatePart.setYear(Integer.parseInt(edtfString.substring(1)));
- } else {
- edtfDatePart = getRegexParsedEdtfDatePart(edtfString);
- }
- return edtfDatePart;
- }
-
- private EdtfDatePart getRegexParsedEdtfDatePart(String edtfString) throws ParseException {
- final EdtfDatePart edtfDatePart = new EdtfDatePart();
- Matcher matcher = DATE_PATTERN.matcher(edtfString);
- if (matcher.matches()) {
- //Select data based on the name of the regex group matching
- if (StringUtils.isNotEmpty(matcher.group("year3"))) {
- edtfDatePart.setYear(Integer.parseInt(matcher.group("year3")));
- } else if (StringUtils.isNotEmpty(matcher.group("year2"))) {
- edtfDatePart.setYear(Integer.parseInt(matcher.group("year2")));
- edtfDatePart.setMonth(Integer.parseInt(matcher.group("month2")));
- } else {
- edtfDatePart.setYear(Integer.parseInt(matcher.group("year1")));
- edtfDatePart.setMonth(Integer.parseInt(matcher.group("month1")));
- edtfDatePart.setDay(Integer.parseInt(matcher.group("day1")));
- }
- //Check modifier value
- if (StringUtils.isNotEmpty(matcher.group("modifier"))) {
- String modifier = matcher.group("modifier");
- if ("?".equals(modifier)) {
- edtfDatePart.setUncertain(true);
- } else if ("~".equals(modifier)) {
- edtfDatePart.setApproximate(true);
- } else if ("%".equals(modifier)) {
- edtfDatePart.setApproximate(true);
- edtfDatePart.setUncertain(true);
- }
- }
- } else {
- throw new ParseException(format("Date parsing does not match for input: %s", edtfString), 0);
- }
- return edtfDatePart;
- }
-
-}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfValidator.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfValidator.java
deleted file mode 100644
index 301ecdde90..0000000000
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/EdtfValidator.java
+++ /dev/null
@@ -1,212 +0,0 @@
-package eu.europeana.normalization.dates.edtf;
-
-import eu.europeana.normalization.dates.YearPrecision;
-import java.time.Month;
-import java.time.Year;
-import java.util.EnumSet;
-
-/**
- * This class validates instances of EDTF dates.
- *
It validates the following:
- *
- *
If the start date of an interval is earlier than the end date.
- *
If a date has a month between 1 and 12.
- *
If a date has a possible month day.
- *
Checks if a day 30 or 31 is possible for the month of the date, and checks that the 29th of February is on a leap year.
- *
If a date is not in the future.
- *
- *
- */
-public final class EdtfValidator {
-
- private static final EnumSet MONTHS_WITH_31_DAYS = EnumSet.of(Month.JANUARY, Month.MARCH, Month.MAY, Month.JULY,
- Month.AUGUST, Month.OCTOBER, Month.DECEMBER);
-
- private EdtfValidator() {
- }
-
- public static boolean validate(AbstractEdtfDate edtfDate, boolean allowFutureDates) {
- boolean isValid;
- if (edtfDate instanceof InstantEdtfDate) {
- isValid = validateInstant((InstantEdtfDate) edtfDate);
- } else {
- isValid = validateInterval((IntervalEdtfDate) edtfDate);
- }
- return isValid && (allowFutureDates || validateNotInFuture(edtfDate));
- }
-
- private static boolean validateNotInFuture(AbstractEdtfDate edtfDate) {
- if (edtfDate instanceof InstantEdtfDate) {
- return validateInstantNotInFuture((InstantEdtfDate) edtfDate);
- }
- return validateIntervalNotInFuture((IntervalEdtfDate) edtfDate);
- }
-
-
- /**
- * The interval validation only checks for the date part and not the time part of the date.
- *
It has been decided that only the date part should be checked, ignoring the time part. This
- * could mean that the interval is technically not valid (e.g. start and end are on the same date but the start is later than
- * the end). But since we are only interested in dates, we accept this.
- *
- * @param intervalEdtfDate the interval date to check
- * @return true if it's valid
- */
- private static boolean validateInterval(IntervalEdtfDate intervalEdtfDate) {
- final InstantEdtfDate startDate = intervalEdtfDate.getStart();
- final InstantEdtfDate endDate = intervalEdtfDate.getEnd();
- final boolean isIntervalValid;
- if (startDate != null && validateInstantOfInterval(startDate) && endDate != null && validateInstantOfInterval(endDate)) {
- EdtfDatePart startDatePart = startDate.getEdtfDatePart();
- EdtfDatePart endDatePart = endDate.getEdtfDatePart();
- final boolean isStartDatePartSpecific = !startDatePart.isUnknown() && !startDatePart.isUnspecified();
- final boolean isEndDatePartSpecific = !endDatePart.isUnknown() && !endDatePart.isUnspecified();
- if (isStartDatePartSpecific && isEndDatePartSpecific) {
- if (startDatePart.getYearPrecision() == null && endDatePart.getYearPrecision() == null) {
- isIntervalValid = validateSpecificIntervalDates(startDatePart, endDatePart);
- } else {
- //Validate year using precision instead
- final Integer adjustedStartYear = adjustYearWithPrecision(startDatePart.getYear(), startDatePart.getYearPrecision());
- final Integer adjustedEndYear = adjustYearWithPrecision(endDatePart.getYear(), endDatePart.getYearPrecision());
- isIntervalValid = adjustedStartYear <= adjustedEndYear;
- }
- } else {
- isIntervalValid = isStartDatePartSpecific || isEndDatePartSpecific;
- }
- } else {
- isIntervalValid = false;
- }
-
- return isIntervalValid;
- }
-
- private static boolean validateSpecificIntervalDates(EdtfDatePart startDatePart, EdtfDatePart endDatePart) {
- // TODO: 20/07/2022 Should we be using the java.time classes Year, YearMonth, LocalDate etc? (to be handled with MET-4726)
- //Sanity check: years should not be null at this stage, but we check to be sure
- if (startDatePart.getYear() == null || endDatePart.getYear() == null) {
- throw new IllegalArgumentException("Year cannot be null for start or end dates");
- }
- boolean isDatesValid = false;
- if (startDatePart.getYear().equals(endDatePart.getYear())) {
- if (startDatePart.getMonth() == null || endDatePart.getMonth() == null
- || startDatePart.getMonth() < endDatePart.getMonth()) {
- isDatesValid = true;
- } else if (startDatePart.getMonth().equals(endDatePart.getMonth())) {
- isDatesValid =
- startDatePart.getDay() == null || endDatePart.getDay() == null || startDatePart.getDay() <= endDatePart.getDay();
- }
- } else {
- isDatesValid = startDatePart.getYear() < endDatePart.getYear();
- }
- return isDatesValid;
- }
-
- /**
- * Adjusts the year value based on the {@link YearPrecision} supplied.
- *
The adjustment is not a rounding operation but a discarding operation of the right most digits
- *
- * Examples of discarding operations for {@link YearPrecision#CENTURY}:
- *
- *
1325/100 * 100 = 1300
- *
-1325/100 * 100 = -1300
- *
1375/100 * 100 = 1300
- *
- *
- *
- * @param year the year to adjust
- * @param yearPrecision the year precision to use for the adjustment
- * @return the adjusted year
- */
- private static Integer adjustYearWithPrecision(Integer year, YearPrecision yearPrecision) {
- final Integer adjustedYear;
- if (yearPrecision != null) {
- final int precisionAdjust = yearPrecision.getDuration();
- adjustedYear = (year / precisionAdjust) * precisionAdjust;
- } else {
- adjustedYear = year;
- }
- return adjustedYear;
- }
-
- /**
- * Validates an instant date.
- *
It contains general validity of date part and in addition it cannot have date part null or
- * unknown.
- *
- * @param instantEdtfDate the instant date to validate
- * @return true if the instant is valid
- */
- private static boolean validateInstant(InstantEdtfDate instantEdtfDate) {
- return validateInstantOfInterval(instantEdtfDate)
- && instantEdtfDate.getEdtfDatePart() != null
- && !instantEdtfDate.getEdtfDatePart().isUnknown();
- }
-
- private static boolean validateInstantOfInterval(InstantEdtfDate instantEdtfDate) {
- return validateDatePart(instantEdtfDate.getEdtfDatePart());
- }
-
- private static boolean validateDatePart(EdtfDatePart edtfDatePart) {
- boolean isDatePartValid = true;
- if (edtfDatePart != null && !(edtfDatePart.isUnknown() || edtfDatePart.isUnspecified())) {
- if (edtfDatePart.getYear() == null) {
- isDatePartValid = false;
- }
- if (edtfDatePart.getYearPrecision() == null && edtfDatePart.getMonth() != null) {
- if (edtfDatePart.getMonth() < 1 || edtfDatePart.getMonth() > 12) {
- isDatePartValid = false;
- } else {
- isDatePartValid = isDatePartDayValid(edtfDatePart);
- }
- }
- }
- return isDatePartValid;
- }
-
- private static boolean isDatePartDayValid(EdtfDatePart edtfDatePart) {
- final boolean isDayValid;
- if (edtfDatePart.getDay() == null) {
- isDayValid = true;
- } else {
- final boolean isValidDayRange = edtfDatePart.getDay() > 0 && edtfDatePart.getDay() <= 31;
- final boolean isNot31Or31AndValid =
- edtfDatePart.getDay() != 31 || MONTHS_WITH_31_DAYS.contains(Month.of(edtfDatePart.getMonth()));
- final boolean isNotFebruaryOrFebruaryAndValidDay = edtfDatePart.getMonth() != 2 || isValidFebruaryDay(edtfDatePart);
- isDayValid = isValidDayRange && isNot31Or31AndValid && isNotFebruaryOrFebruaryAndValidDay;
- }
- return isDayValid;
- }
-
- private static boolean isValidFebruaryDay(EdtfDatePart edtfDatePart) {
- return (edtfDatePart.getDay() > 0 && edtfDatePart.getDay() < 30 && edtfDatePart.getDay() != 29) || Year.isLeap(
- edtfDatePart.getYear());
- }
-
- private static boolean validateIntervalNotInFuture(IntervalEdtfDate intervalEdtfDate) {
- return validateInstantNotInFuture(intervalEdtfDate.getStart()) && validateInstantNotInFuture(intervalEdtfDate.getEnd());
- }
-
-
- // TODO: 20/07/2022 This only calculates years and not other parts of the date.
- // (this probably won't capture a dates that is days/months in the future but on the current year?)
- // Fix this to also check the other parts of the date as well.
- // Perhaps the already existent validation of interval dates should be reused instead, with the end date the current date.
- private static boolean validateInstantNotInFuture(InstantEdtfDate instantEdtfDate) {
- final boolean isYearInPast;
- //If null or not specific it's valid
- if (instantEdtfDate.getEdtfDatePart() == null || instantEdtfDate.getEdtfDatePart().isUnknown()
- || instantEdtfDate.getEdtfDatePart().isUnspecified()) {
- isYearInPast = true;
- } else {
- int currentYear = Year.now().getValue();
- final Integer edtfYear = instantEdtfDate.getEdtfDatePart().getYear();
- final YearPrecision yearPrecision = instantEdtfDate.getEdtfDatePart().getYearPrecision();
-
- final Integer adjustedCurrentYear = adjustYearWithPrecision(currentYear, yearPrecision);
- final Integer adjustedEdtfYear = adjustYearWithPrecision(edtfYear, yearPrecision);
- isYearInPast = adjustedEdtfYear <= adjustedCurrentYear;
- }
- return isYearInPast;
- }
-
-}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/InstantEdtfDate.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/InstantEdtfDate.java
index 52e0c1807e..bb8fb4bc95 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/InstantEdtfDate.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/edtf/InstantEdtfDate.java
@@ -1,168 +1,291 @@
package eu.europeana.normalization.dates.edtf;
+import static eu.europeana.normalization.dates.edtf.DateBoundaryType.DECLARED;
+import static eu.europeana.normalization.dates.edtf.DateBoundaryType.OPEN;
+import static eu.europeana.normalization.dates.edtf.DateBoundaryType.UNKNOWN;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
+import static eu.europeana.normalization.dates.edtf.InstantEdtfDateBuilder.THRESHOLD_4_DIGITS_YEAR;
+import static eu.europeana.normalization.dates.edtf.Iso8601Parser.ISO_8601_MINIMUM_YEAR_DIGITS;
+import static java.lang.Math.abs;
+import static java.util.Optional.ofNullable;
+
import eu.europeana.normalization.dates.YearPrecision;
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
+import java.lang.invoke.MethodHandles;
+import java.text.DecimalFormat;
+import java.time.LocalDate;
import java.time.Month;
+import java.time.MonthDay;
import java.time.Year;
-import org.apache.commons.lang3.SerializationUtils;
+import java.time.YearMonth;
+import java.time.temporal.TemporalAccessor;
+import java.util.Objects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * Part of an EDTF date that represents a point in time with various degrees of precision
+ * Class representing the date part of an EDTF date.
+ *
+ * Support partial dates, including only centuries or decades (e.g., 19XX). The uncertain and approximate qualifiers, '?' and '~',
+ * when applied together, are combined into a single qualifier character '%';
+ *
*/
-public class InstantEdtfDate extends AbstractEdtfDate {
+public final class InstantEdtfDate extends AbstractEdtfDate implements Comparable {
- private static final long serialVersionUID = -4111050222535744456L;
+ private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- public static final int THRESHOLD_4_DIGITS_YEAR = 9999;
- private EdtfDatePart edtfDatePart;
+ private Year year;
+ private Month month;
+ private LocalDate yearMonthDay;
+ private YearPrecision yearPrecision;
+ private DateQualification dateQualification = NO_QUALIFICATION;
+ private DateBoundaryType dateBoundaryType = DECLARED;
- public InstantEdtfDate(EdtfDatePart edtfDatePart) {
- this.edtfDatePart = edtfDatePart;
+ /**
+ * Restricted constructor by provided {@link InstantEdtfDateBuilder}.
+ *
+ * @param instantEdtfDateBuilder the builder with all content verified
+ */
+ InstantEdtfDate(InstantEdtfDateBuilder instantEdtfDateBuilder) {
+ yearPrecision = instantEdtfDateBuilder.getYearPrecision();
+ year = instantEdtfDateBuilder.getYearObj();
+ month = instantEdtfDateBuilder.getMonthObj();
+ yearMonthDay = instantEdtfDateBuilder.getYearMonthDayObj();
+ dateQualification = instantEdtfDateBuilder.getDateQualification();
}
- @Override
- public boolean isTimeOnly() {
- return edtfDatePart == null;
+ private InstantEdtfDate(DateBoundaryType dateBoundaryType) {
+ this.dateBoundaryType = dateBoundaryType;
}
- public EdtfDatePart getEdtfDatePart() {
- return edtfDatePart;
+ /**
+ * Create an {@link DateBoundaryType#UNKNOWN} instant.
+ *
+ * @return the instant date created
+ */
+ public static InstantEdtfDate getUnknownInstance() {
+ return new InstantEdtfDate(UNKNOWN);
}
- public void setEdtfDatePart(EdtfDatePart edtfDatePart) {
- this.edtfDatePart = edtfDatePart;
+ /**
+ * Create an {@link DateBoundaryType#OPEN} instant.
+ *
+ * @return the instant date created
+ */
+ public static InstantEdtfDate getOpenInstance() {
+ return new InstantEdtfDate(OPEN);
}
@Override
- public void setApproximate(boolean approximate) {
- edtfDatePart.setApproximate(approximate);
+ public InstantEdtfDate getFirstDay() {
+ InstantEdtfDate firstDay = null;
+ try {
+ if (dateBoundaryType == DECLARED) {
+ if (this.getYear().getValue() < -THRESHOLD_4_DIGITS_YEAR) {
+ firstDay = new InstantEdtfDateBuilder(this.getYear().getValue()).build();
+ } else {
+ firstDay = this.firstDayOfYearDatePart();
+ }
+ }
+ } catch (DateExtractionException e) {
+ LOGGER.error("Creating first day of instant failed!", e);
+ }
+
+ return firstDay;
}
- @Override
- public void setUncertain(boolean uncertain) {
- edtfDatePart.setUncertain(uncertain);
+ /**
+ * Get the date that correspond to the first day of the year.
+ *
+ *
+ *
For full dates e.g. 1989-11-01 it is identical 1989-11-01
+ *
For dates without day e.g. 1989-11 it is 1989-11-01
+ *
For dates with only year e.g. 1989 it is 1989-01-01
+ *
For dates with only year and precision {@link YearPrecision#CENTURY} e.g. 1900 it is 1901-01-01
+ *
For dates with only year and precision {@link YearPrecision#DECADE} e.g. 1980 it is 1980-01-01
This parser supports partial Level0 and Level1 from the Extended
+ * Date/Time Format (EDTF) Specification. It only validates the date part of a date and the time if existent it is discarded.
+ * Specifically from Level1, seasons and Unspecified digit(s) from the right are not supported
+ *
+ */
+public class EdtfDateExtractor extends AbstractDateExtractor {
+
+ private static final Iso8601Parser ISO_8601_PARSER = new Iso8601Parser();
+
+ @Override
+ public DateNormalizationResult extract(String inputValue, DateQualification requestedDateQualification,
+ boolean flexibleDateBuild) throws DateExtractionException {
+ if (StringUtils.isEmpty(inputValue)) {
+ throw new DateExtractionException("Empty argument");
+ }
+ final AbstractEdtfDate edtfDate;
+ if (inputValue.contains(DATE_INTERVAL_SEPARATOR)) {
+ edtfDate = extractInterval(inputValue, requestedDateQualification, flexibleDateBuild);
+ } else {
+ edtfDate = extractInstant(inputValue, requestedDateQualification, flexibleDateBuild);
+ }
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.EDTF, inputValue, edtfDate);
+ }
+
+ protected IntervalEdtfDate extractInterval(String dateInput, DateQualification requestedDateQualification,
+ boolean allowSwitchMonthDay) throws DateExtractionException {
+ String startPart = dateInput.substring(0, dateInput.indexOf(DATE_INTERVAL_SEPARATOR));
+ String endPart = dateInput.substring(dateInput.indexOf(DATE_INTERVAL_SEPARATOR) + 1);
+ final InstantEdtfDate start = extractInstant(startPart, requestedDateQualification, allowSwitchMonthDay);
+ final InstantEdtfDate end = extractInstant(endPart, requestedDateQualification, allowSwitchMonthDay);
+
+ //Are both ends unknown or open, then it is not a date
+ if ((end.getDateBoundaryType() == UNKNOWN || end.getDateBoundaryType() == OPEN) &&
+ (start.getDateBoundaryType() == UNKNOWN || start.getDateBoundaryType() == OPEN)) {
+ throw new DateExtractionException(dateInput);
+ }
+ return new IntervalEdtfDateBuilder(start, end).withFlexibleDateBuild(allowSwitchMonthDay).build();
+ }
+
+ protected InstantEdtfDate extractInstant(String dateInput, DateQualification requestedDateQualification,
+ boolean allowSwitchMonthDay) throws DateExtractionException {
+ final InstantEdtfDate instantEdtfDate;
+ if (UNKNOWN.getDeserializedRepresentation().equals(dateInput)) {
+ instantEdtfDate = InstantEdtfDate.getUnknownInstance();
+ } else if (OPEN.getDeserializedRepresentation().equals(dateInput)) {
+ instantEdtfDate = InstantEdtfDate.getOpenInstance();
+ } else if (dateInput.startsWith(String.valueOf(OVER_4_DIGITS_YEAR_PREFIX))) {
+ int year = NumberUtils.toInt(dateInput.substring(1));
+ instantEdtfDate = new InstantEdtfDateBuilder(year).withLongYearPrefixedWithY()
+ .withDateQualification(requestedDateQualification).build();
+ } else {
+ instantEdtfDate = extractInstantEdtfDate(dateInput, requestedDateQualification, allowSwitchMonthDay);
+ }
+ return instantEdtfDate;
+ }
+
+ private static InstantEdtfDate extractInstantEdtfDate(String dateInput, DateQualification requestedDateQualification,
+ boolean allowSwitchMonthDay) throws DateExtractionException {
+ Matcher matcher = CHECK_QUALIFICATION_PATTERN.matcher(dateInput);
+ String dateInputStrippedModifier = dateInput;
+ DateQualification dateQualification = requestedDateQualification;
+
+ boolean containsQualification = matcher.matches();
+ if (containsQualification && (requestedDateQualification == null || requestedDateQualification == NO_QUALIFICATION)) {
+ final String modifier = matcher.group(1);
+ if (StringUtils.isNotEmpty(modifier)) {
+ dateQualification = DateQualification.fromCharacter(String.valueOf(modifier.charAt(0)));
+ dateInputStrippedModifier = dateInput.substring(0, dateInput.length() - 1);
+ }
+ }
+
+ final TemporalAccessor temporalAccessor = ISO_8601_PARSER.parseDatePart(dateInputStrippedModifier);
+ return new InstantEdtfDateBuilder(temporalAccessor)
+ .withDateQualification(dateQualification)
+ .withFlexibleDateBuild(allowSwitchMonthDay)
+ .build();
+ }
+
+}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsDateExtractor.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsDateExtractor.java
index 1a30d08f93..92b972f8ed 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsDateExtractor.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsDateExtractor.java
@@ -1,16 +1,18 @@
package eu.europeana.normalization.dates.extraction.dateextractors;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
+import static eu.europeana.normalization.dates.edtf.DateQualification.UNCERTAIN;
import static java.util.Optional.ofNullable;
import static java.util.regex.Pattern.compile;
import eu.europeana.normalization.dates.DateNormalizationResult;
import eu.europeana.normalization.dates.YearPrecision;
-import eu.europeana.normalization.dates.edtf.EdtfDatePart;
-import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
+import eu.europeana.normalization.dates.edtf.DateQualification;
+import eu.europeana.normalization.dates.edtf.InstantEdtfDateBuilder;
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
import eu.europeana.normalization.dates.extraction.NumericPartsPattern;
import eu.europeana.normalization.dates.sanitize.DateFieldSanitizer;
import java.util.Locale;
-import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -20,7 +22,7 @@
* Patterns for numeric dates with variations in the separators of date components.
*
For Patterns pay attentions on the use of {@link Matcher#matches()} or {@link Matcher#find()} in this method.
*/
-public class NumericPartsDateExtractor implements DateExtractor {
+public class NumericPartsDateExtractor extends AbstractDateExtractor {
/**
* The start of the string can be one or three question marks but not two.
@@ -37,8 +39,9 @@ public class NumericPartsDateExtractor implements DateExtractor {
private static final String UNKNOWN_CHARACTERS_REGEX = "[XU?-]";
@Override
- public DateNormalizationResult extract(String inputValue) {
- return extract(inputValue, NumericPartsPattern.NUMERIC_SET);
+ public DateNormalizationResult extract(String inputValue, DateQualification requestedDateQualification,
+ boolean flexibleDateBuild) throws DateExtractionException {
+ return extract(inputValue, requestedDateQualification, NumericPartsPattern.NUMERIC_SET, flexibleDateBuild);
}
/**
@@ -46,30 +49,34 @@ public DateNormalizationResult extract(String inputValue) {
*
* @param inputValue the input value
* @param numericPatternValues the patterns to check against
+ * @param allowSwitchMonthDay allow switching month and day values if month and day original values are not valid
* @return the date normalization result
*/
- public DateNormalizationResult extract(String inputValue, Set numericPatternValues) {
+ protected DateNormalizationResult extract(String inputValue, DateQualification requestedDateQualification,
+ Set numericPatternValues,
+ boolean allowSwitchMonthDay) throws DateExtractionException {
final String sanitizedValue = DateFieldSanitizer.cleanSpacesAndTrim(inputValue);
- final boolean uncertain =
- STARTING_UNCERTAIN_PATTERN.matcher(sanitizedValue).find() || ENDING_UNCERTAIN_PATTERN.matcher(sanitizedValue).find();
+ final DateQualification dateQualification = computeDateQualification(requestedDateQualification, () ->
+ (STARTING_UNCERTAIN_PATTERN.matcher(sanitizedValue).find() || ENDING_UNCERTAIN_PATTERN.matcher(sanitizedValue).find())
+ ? UNCERTAIN : NO_QUALIFICATION);
- DateNormalizationResult dateNormalizationResult = null;
+ DateNormalizationResult dateNormalizationResult = DateNormalizationResult.getNoMatchResult(inputValue);
for (NumericPartsPattern numericWithMissingPartsPattern : numericPatternValues) {
final Matcher matcher = numericWithMissingPartsPattern.getPattern().matcher(sanitizedValue);
if (matcher.matches()) {
- EdtfDatePart edtfDatePart = extractDate(numericWithMissingPartsPattern, matcher, uncertain);
- dateNormalizationResult = new DateNormalizationResult(
- numericWithMissingPartsPattern.getDateNormalizationExtractorMatchId(), inputValue,
- new InstantEdtfDate(edtfDatePart));
- break;
+ InstantEdtfDateBuilder instantEdtfDateBuilder = extractDateProperty(numericWithMissingPartsPattern, matcher);
+ dateNormalizationResult = new DateNormalizationResult(
+ numericWithMissingPartsPattern.getDateNormalizationExtractorMatchId(), inputValue,
+ instantEdtfDateBuilder.withDateQualification(dateQualification).withFlexibleDateBuild(allowSwitchMonthDay)
+ .build());
+ break;
}
}
return dateNormalizationResult;
}
- private EdtfDatePart extractDate(
- NumericPartsPattern numericWithMissingPartsPattern, Matcher matcher, boolean uncertain) {
+ private InstantEdtfDateBuilder extractDateProperty(NumericPartsPattern numericWithMissingPartsPattern, Matcher matcher) {
final String year = getYear(numericWithMissingPartsPattern, matcher);
final String month = getMonth(numericWithMissingPartsPattern, matcher);
final String day = getDay(numericWithMissingPartsPattern, matcher);
@@ -78,19 +85,12 @@ private EdtfDatePart extractDate(
final String monthSanitized = getFieldSanitized(month);
final String daySanitized = getFieldSanitized(day);
- final EdtfDatePart edtfDatePart = new EdtfDatePart();
final int unknownYearCharacters = year.length() - yearSanitized.length();
- edtfDatePart.setYearPrecision(YearPrecision.getYearPrecisionByOrdinal(unknownYearCharacters));
- edtfDatePart.setYear(adjustYearWithPrecision(yearSanitized, edtfDatePart));
- edtfDatePart.setMonth(Integer.parseInt(monthSanitized));
- edtfDatePart.setDay(Integer.parseInt(daySanitized));
- edtfDatePart.setUncertain(uncertain);
- return edtfDatePart;
- }
-
- private int adjustYearWithPrecision(String yearSanitized, EdtfDatePart edtfDatePart) {
- return Integer.parseInt(yearSanitized) * Optional.ofNullable(edtfDatePart.getYearPrecision()).map(YearPrecision::getDuration)
- .orElse(1);
+ YearPrecision yearPrecision = YearPrecision.getYearPrecisionByOrdinal(unknownYearCharacters);
+ return new InstantEdtfDateBuilder(Integer.parseInt(yearSanitized))
+ .withYearPrecision(yearPrecision)
+ .withMonth(Integer.parseInt(monthSanitized))
+ .withDay(Integer.parseInt(daySanitized));
}
private String getFieldSanitized(String stringField) {
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsRangeDateExtractor.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsRangeDateExtractor.java
index 3f445d3328..ca9dd573c9 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsRangeDateExtractor.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsRangeDateExtractor.java
@@ -7,37 +7,43 @@
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
-import eu.europeana.normalization.dates.edtf.EdtfDatePart;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
+import eu.europeana.normalization.dates.edtf.DateBoundaryType;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
+import eu.europeana.normalization.dates.edtf.IntervalEdtfDateBuilder;
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
import eu.europeana.normalization.dates.extraction.NumericPartsPattern;
import eu.europeana.normalization.dates.extraction.NumericPartsPattern.NumericRangeDateDelimiters;
import eu.europeana.normalization.dates.sanitize.DateFieldSanitizer;
/**
* Patterns for numeric date ranges with variations in the separators of date components.
- *
We reuse the already existent {@link NumericPartsDateExtractor} code for the edges.
+ *
We reuse the already existent {@link NumericPartsDateExtractor} code for the boundaries.
*/
-public class NumericPartsRangeDateExtractor implements DateExtractor {
+public class NumericPartsRangeDateExtractor extends AbstractDateExtractor {
private static final NumericPartsDateExtractor NUMERIC_WITH_MISSING_PARTS_DATE_EXTRACTOR = new NumericPartsDateExtractor();
/**
* Extract the date normalization result for a range.
*
- * The date is split in two edges using the {@link NumericRangeDateDelimiters#values()} as a separator. The result will contain
- * the first split that is exactly splitting the original value in two parts(edges) and those two edge are valid parsable edges
- * or null if none found.
+ * The date is split in two boundaries using the {@link NumericRangeDateDelimiters#values()} as a separator. The result will
+ * contain the first split that is exactly splitting the original value in two parts(boundaries) and those two boundaries are
+ * valid parsable boundaries or null if none found.
*
*
* @param inputValue the range value to attempt parsing
* @return the date normalization result
*/
- public DateNormalizationResult extract(String inputValue) {
+ @Override
+ public DateNormalizationResult extract(String inputValue, DateQualification requestedDateQualification,
+ boolean flexibleDateBuild) throws DateExtractionException {
final String sanitizedValue = DateFieldSanitizer.cleanSpacesAndTrim(inputValue);
- DateNormalizationResult startDate;
- DateNormalizationResult endDate;
- DateNormalizationResult rangeDate = null;
+ DateNormalizationResult startDateResult;
+ DateNormalizationResult endDateResult;
+ DateNormalizationResult rangeDate = DateNormalizationResult.getNoMatchResult(inputValue);
for (NumericRangeDateDelimiters numericRangeSpecialCharacters : NumericRangeDateDelimiters.values()) {
// Split with -1 limit does not discard empty splits
final String[] sanitizedDateSplitArray = sanitizedValue.split(numericRangeSpecialCharacters.getDatesSeparator(), -1);
@@ -45,16 +51,20 @@ public DateNormalizationResult extract(String inputValue) {
// This also guarantees that the separator used is not used for unknown characters.
if (sanitizedDateSplitArray.length == 2) {
// Try extraction and verify
- startDate = extractDateNormalizationResult(sanitizedDateSplitArray[0], numericRangeSpecialCharacters);
- endDate = extractDateNormalizationResult(sanitizedDateSplitArray[1], numericRangeSpecialCharacters);
- if (startDate != null && endDate != null && !areYearsAmbiguous((InstantEdtfDate) startDate.getEdtfDate(),
- (InstantEdtfDate) endDate.getEdtfDate(),
+ startDateResult = extractDateNormalizationResult(sanitizedDateSplitArray[0], numericRangeSpecialCharacters,
+ requestedDateQualification,
+ flexibleDateBuild);
+ endDateResult = extractDateNormalizationResult(sanitizedDateSplitArray[1], numericRangeSpecialCharacters,
+ requestedDateQualification, flexibleDateBuild);
+ if (startDateResult.getDateNormalizationResultStatus() == DateNormalizationResultStatus.MATCHED
+ && endDateResult.getDateNormalizationResultStatus() == DateNormalizationResultStatus.MATCHED
+ && !areYearsAmbiguous((InstantEdtfDate) startDateResult.getEdtfDate(), (InstantEdtfDate) endDateResult.getEdtfDate(),
numericRangeSpecialCharacters)) {
final DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId =
- getDateNormalizationExtractorId(startDate, endDate);
- final IntervalEdtfDate intervalEdtfDate = new IntervalEdtfDate((InstantEdtfDate) startDate.getEdtfDate(),
- (InstantEdtfDate) endDate.getEdtfDate());
+ getDateNormalizationExtractorId(startDateResult, endDateResult);
+ final IntervalEdtfDate intervalEdtfDate = new IntervalEdtfDateBuilder((InstantEdtfDate) startDateResult.getEdtfDate(),
+ (InstantEdtfDate) endDateResult.getEdtfDate()).withFlexibleDateBuild(flexibleDateBuild).build();
rangeDate = new DateNormalizationResult(dateNormalizationExtractorMatchId, inputValue, intervalEdtfDate);
break;
}
@@ -75,9 +85,10 @@ private boolean areYearsAmbiguous(InstantEdtfDate startDate, InstantEdtfDate end
NumericRangeDateDelimiters numericRangeSpecialCharacters) {
boolean isAmbiguous = false;
if (numericRangeSpecialCharacters == NumericRangeDateDelimiters.DASH_RANGE) {
- final boolean isStartSpecified = !startDate.getEdtfDatePart().isUnspecified();
- final boolean isStartThreeDigit = isStartSpecified && startDate.getEdtfDatePart().getYear().toString().matches("\\d{3}");
- if (isStartThreeDigit && endDate.isUnspecified()) {
+ final boolean isStartDeclared = startDate.getDateBoundaryType() == DateBoundaryType.DECLARED;
+ final boolean isStartThreeDigit =
+ isStartDeclared && Integer.toString(startDate.getYear().getValue()).matches("\\d{3}");
+ if (isStartThreeDigit && endDate.getDateBoundaryType() == DateBoundaryType.OPEN) {
isAmbiguous = true;
}
}
@@ -85,15 +96,15 @@ private boolean areYearsAmbiguous(InstantEdtfDate startDate, InstantEdtfDate end
}
private DateNormalizationResult extractDateNormalizationResult(String dateString,
- NumericRangeDateDelimiters numericRangeSpecialCharacters) {
+ NumericRangeDateDelimiters numericRangeSpecialCharacters, DateQualification requestedDateQualification,
+ boolean allowSwitchMonthDay) throws DateExtractionException {
final DateNormalizationResult dateNormalizationResult;
if (numericRangeSpecialCharacters.getUnspecifiedCharacters() != null && dateString.matches(
numericRangeSpecialCharacters.getUnspecifiedCharacters())) {
- dateNormalizationResult = new DateNormalizationResult(NUMERIC_ALL_VARIANTS, dateString,
- new InstantEdtfDate(EdtfDatePart.getUnspecifiedInstance()));
+ dateNormalizationResult = new DateNormalizationResult(NUMERIC_ALL_VARIANTS, dateString, InstantEdtfDate.getOpenInstance());
} else {
- dateNormalizationResult = NUMERIC_WITH_MISSING_PARTS_DATE_EXTRACTOR.extract(dateString,
- NumericPartsPattern.NUMERIC_RANGE_SET);
+ dateNormalizationResult = NUMERIC_WITH_MISSING_PARTS_DATE_EXTRACTOR.extract(dateString, requestedDateQualification,
+ NumericPartsPattern.NUMERIC_RANGE_SET, allowSwitchMonthDay);
}
return dateNormalizationResult;
}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternBcAdDateExtractor.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternBcAdDateExtractor.java
index e08f823c3d..803d25e00a 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternBcAdDateExtractor.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternBcAdDateExtractor.java
@@ -2,9 +2,11 @@
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
-import eu.europeana.normalization.dates.edtf.EdtfDatePart;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
-import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
+import eu.europeana.normalization.dates.edtf.InstantEdtfDateBuilder;
+import eu.europeana.normalization.dates.edtf.IntervalEdtfDateBuilder;
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -15,7 +17,7 @@
* ‘AC/DC’, but the abbreviations used in other languages will be supported in the future. Or a date range where the start/end
* years contain an indication of the era.
*/
-public class PatternBcAdDateExtractor implements DateExtractor {
+public class PatternBcAdDateExtractor extends AbstractDateExtractor {
static final HashSet bcAbbreviations = new HashSet<>();
@@ -69,38 +71,46 @@ public PatternBcAdDateExtractor() {
Pattern.CASE_INSENSITIVE);
}
- public DateNormalizationResult extract(String inputValue) {
+ @Override
+ public DateNormalizationResult extract(String inputValue, DateQualification requestedDateQualification,
+ boolean flexibleDateBuild) throws DateExtractionException {
Matcher m = patYyyy.matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
+ final InstantEdtfDateBuilder instantEdtfDateBuilder;
if (bcAbbreviations.contains(m.group("era").toLowerCase())) {
- d.setYear(-Integer.parseInt(m.group("year")));
+ instantEdtfDateBuilder = new InstantEdtfDateBuilder(-Integer.parseInt(m.group("year")));
} else {
- d.setYear(Integer.parseInt(m.group("year")));
+ instantEdtfDateBuilder = new InstantEdtfDateBuilder(Integer.parseInt(m.group("year")));
}
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.BC_AD, inputValue, new InstantEdtfDate(d));
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.BC_AD, inputValue,
+ instantEdtfDateBuilder.withDateQualification(requestedDateQualification).withFlexibleDateBuild(
+ flexibleDateBuild)
+ .build());
}
m = patRange.matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
+ final InstantEdtfDateBuilder startDatePartBuilder;
if (isBc(m.group("era"))) {
- d.setYear(-Integer.parseInt(m.group("year")));
+ startDatePartBuilder = new InstantEdtfDateBuilder(-Integer.parseInt(m.group("year")));
} else {
- d.setYear(Integer.parseInt(m.group("year")));
+ startDatePartBuilder = new InstantEdtfDateBuilder(Integer.parseInt(m.group("year")));
}
- InstantEdtfDate start = new InstantEdtfDate(d);
+ InstantEdtfDate start = startDatePartBuilder.withDateQualification(requestedDateQualification)
+ .withFlexibleDateBuild(flexibleDateBuild).build();
- d = new EdtfDatePart();
+ final InstantEdtfDateBuilder endDatePartBuilder;
if (isBc(m.group("era2"))) {
- d.setYear(-Integer.parseInt(m.group("year2")));
+ endDatePartBuilder = new InstantEdtfDateBuilder(-Integer.parseInt(m.group("year2")));
} else {
- d.setYear(Integer.parseInt(m.group("year2")));
+ endDatePartBuilder = new InstantEdtfDateBuilder(Integer.parseInt(m.group("year2")));
}
- InstantEdtfDate end = new InstantEdtfDate(d);
+ InstantEdtfDate end = endDatePartBuilder.withDateQualification(requestedDateQualification)
+ .withFlexibleDateBuild(flexibleDateBuild).build();
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.BC_AD, inputValue, new IntervalEdtfDate(start, end));
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.BC_AD, inputValue,
+ new IntervalEdtfDateBuilder(start, end).withFlexibleDateBuild(flexibleDateBuild).build());
}
- return null;
+ return DateNormalizationResult.getNoMatchResult(inputValue);
}
private boolean isBc(String abbreviation) {
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternBriefDateRangeDateExtractor.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternBriefDateRangeDateExtractor.java
deleted file mode 100644
index 4eac847508..0000000000
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternBriefDateRangeDateExtractor.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package eu.europeana.normalization.dates.extraction.dateextractors;
-
-import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
-import eu.europeana.normalization.dates.DateNormalizationResult;
-import eu.europeana.normalization.dates.YearPrecision;
-import eu.europeana.normalization.dates.edtf.EdtfDatePart;
-import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
-import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
-import java.time.Month;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Extractor that matches a date range where the end year includes only the rightmost two digits.
- *
- * The end year in this extractor has to:
- *
- *
Be higher than 12 to avoid matching a month value from other extractors.
- *
Be higher than the two rightmost digits of the start year.
- *
- *
- *
This pattern needs to be executed before the Edtf one.
- *
Most values that match this pattern also match the EDTF pattern, but would result in an invalid date.
- *
This pattern only matches values that would not be valid EDTF dates.
- */
-public class PatternBriefDateRangeDateExtractor implements DateExtractor {
-
- private final Pattern briefDateRangePattern = Pattern.compile(
- "(?\\?\\s*)?(?\\d{3,4})[\\-/](?\\d{2})(?\\s*\\?)?");
-
- @Override
- public DateNormalizationResult extract(String inputValue) {
- Matcher matcher = briefDateRangePattern.matcher(inputValue.trim());
- DateNormalizationResult dateNormalizationResult = null;
- if (matcher.matches()) {
- EdtfDatePart startDatePart = new EdtfDatePart();
- startDatePart.setYear(Integer.parseInt(matcher.group("start")));
- int endYear = Integer.parseInt(matcher.group("end"));
- if (endYear > Month.DECEMBER.getValue()) {
- int startYear = startDatePart.getYear() % YearPrecision.CENTURY.getDuration();
- if (startYear < endYear) {
- EdtfDatePart endDatePart = new EdtfDatePart();
- endDatePart.setYear(
- (startDatePart.getYear() / YearPrecision.CENTURY.getDuration()) * YearPrecision.CENTURY.getDuration() + endYear);
-
- updateUncertain(matcher, startDatePart, endDatePart);
- dateNormalizationResult = new DateNormalizationResult(DateNormalizationExtractorMatchId.BRIEF_DATE_RANGE, inputValue,
- new IntervalEdtfDate(new InstantEdtfDate(startDatePart), new InstantEdtfDate(endDatePart)));
- }
- }
- }
- return dateNormalizationResult;
- }
-
- private void updateUncertain(Matcher matcher, EdtfDatePart startDatePart, EdtfDatePart endDatePart) {
- if (matcher.group("startsWithQuestionMark") != null || matcher.group("endsWithQuestionMark") != null) {
- startDatePart.setUncertain(true);
- endDatePart.setUncertain(true);
- }
- }
-
-}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternEdtfDateExtractor.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternEdtfDateExtractor.java
deleted file mode 100644
index 1c4d0854ef..0000000000
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternEdtfDateExtractor.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package eu.europeana.normalization.dates.extraction.dateextractors;
-
-import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
-import eu.europeana.normalization.dates.DateNormalizationResult;
-import eu.europeana.normalization.dates.edtf.AbstractEdtfDate;
-import eu.europeana.normalization.dates.edtf.EdtfParser;
-import java.text.ParseException;
-
-/**
- * The pattern for EDTF dates and compatible with ISO 8601 dates.
- */
-public class PatternEdtfDateExtractor implements DateExtractor {
-
- final EdtfParser edtfParser = new EdtfParser();
-
- @Override
- public DateNormalizationResult extract(String inputValue) {
- try {
- AbstractEdtfDate edtfDate = edtfParser.parse(inputValue);
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.EDTF, inputValue, edtfDate);
- } catch (ParseException | NumberFormatException e) {
- return null;
- }
- }
-}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternFormatedFullDateDateExtractor.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternFormatedFullDateDateExtractor.java
index 33a0ad22ee..3754efe377 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternFormatedFullDateDateExtractor.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternFormatedFullDateDateExtractor.java
@@ -2,8 +2,10 @@
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
-import eu.europeana.normalization.dates.edtf.EdtfDatePart;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
+import eu.europeana.normalization.dates.edtf.InstantEdtfDateBuilder;
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
import eu.europeana.normalization.dates.extraction.MonthMultilingual;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -11,7 +13,7 @@
/**
* Patterns for date formats that are well-structured but do not follow a particular standard
*/
-public class PatternFormatedFullDateDateExtractor implements DateExtractor {
+public class PatternFormatedFullDateDateExtractor extends AbstractDateExtractor {
MonthMultilingual monthNames = new MonthMultilingual();
@@ -29,35 +31,36 @@ public class PatternFormatedFullDateDateExtractor implements DateExtractor {
// year month day hour minute second
Pattern patFormatedDate3 = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})(\\.\\d{1,3})?");
- public DateNormalizationResult extract(String inputValue) {
+ @Override
+ public DateNormalizationResult extract(String inputValue, DateQualification requestedDateQualification,
+ boolean flexibleDateBuild) throws DateExtractionException {
+ final DateQualification dateQualification = computeDateQualification(requestedDateQualification,
+ () -> DateQualification.NO_QUALIFICATION);
+
Matcher m = patFormatedDate2.matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
- d.setYear(Integer.parseInt(m.group(1)));
- d.setMonth(Integer.parseInt(m.group(2)));
- d.setDay(Integer.parseInt(m.group(3)));
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.FORMATTED_FULL_DATE, inputValue,
- new InstantEdtfDate(d));
+ final InstantEdtfDate datePart = new InstantEdtfDateBuilder(Integer.parseInt(m.group(1)))
+ .withMonth(Integer.parseInt(m.group(2)))
+ .withDay(Integer.parseInt(m.group(3)))
+ .withDateQualification(dateQualification).withFlexibleDateBuild(flexibleDateBuild).build();
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.FORMATTED_FULL_DATE, inputValue, datePart);
}
m = patFormatedDate.matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
- d.setYear(Integer.parseInt(m.group(6)));
- d.setMonth(monthNames.getMonthIndexValue(m.group(1)));
- d.setDay(Integer.parseInt(m.group(2)));
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.FORMATTED_FULL_DATE, inputValue,
- new InstantEdtfDate(d));
+ final InstantEdtfDate datePart = new InstantEdtfDateBuilder(Integer.parseInt(m.group(6)))
+ .withMonth(monthNames.getMonthIndexValue(m.group(1)))
+ .withDay(Integer.parseInt(m.group(2)))
+ .withDateQualification(dateQualification).withFlexibleDateBuild(flexibleDateBuild).build();
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.FORMATTED_FULL_DATE, inputValue, datePart);
}
m = patFormatedDate3.matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
- d.setYear(Integer.parseInt(m.group(1)));
- d.setMonth(Integer.parseInt(m.group(2)));
- d.setDay(Integer.parseInt(m.group(3)));
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.FORMATTED_FULL_DATE, inputValue,
- new InstantEdtfDate(d));
+ final InstantEdtfDate datePart = new InstantEdtfDateBuilder(Integer.parseInt(m.group(1)))
+ .withMonth(Integer.parseInt(m.group(2)))
+ .withDay(Integer.parseInt(m.group(3)))
+ .withDateQualification(dateQualification).withFlexibleDateBuild(flexibleDateBuild).build();
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.FORMATTED_FULL_DATE, inputValue, datePart);
}
- return null;
+ return DateNormalizationResult.getNoMatchResult(inputValue);
}
-
}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternLongNegativeYearDateExtractor.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternLongNegativeYearDateExtractor.java
index f844ae0a0d..7e8b9dc942 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternLongNegativeYearDateExtractor.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternLongNegativeYearDateExtractor.java
@@ -1,10 +1,16 @@
package eu.europeana.normalization.dates.extraction.dateextractors;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
+import static eu.europeana.normalization.dates.edtf.DateQualification.UNCERTAIN;
+
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
-import eu.europeana.normalization.dates.edtf.EdtfDatePart;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
+import eu.europeana.normalization.dates.edtf.InstantEdtfDateBuilder;
import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
+import eu.europeana.normalization.dates.edtf.IntervalEdtfDateBuilder;
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -12,7 +18,7 @@
* A year before 1 AD with more than 4 digits. This pattern is typically used in archaeological contexts. The year may contain
* between 5 and 9 digits. Aso includes the pattern for ranges of this kind of years.
*/
-public class PatternLongNegativeYearDateExtractor implements DateExtractor {
+public class PatternLongNegativeYearDateExtractor extends AbstractDateExtractor {
Pattern patYyyyyy = Pattern.compile("\\s*(?\\?)?(?-\\d{5,9})(?\\?)?\\s*",
Pattern.CASE_INSENSITIVE);
@@ -20,30 +26,35 @@ public class PatternLongNegativeYearDateExtractor implements DateExtractor {
"\\s*(?\\?)?(?-\\d{5,9})\\s*/\\s*(?-\\d{5,9})(?\\?)?\\s*",
Pattern.CASE_INSENSITIVE);
- public DateNormalizationResult extract(String inputValue) {
- Matcher m;
- m = patYyyyyy.matcher(inputValue);
+ @Override
+ public DateNormalizationResult extract(String inputValue, DateQualification requestedDateQualification,
+ boolean flexibleDateBuild) throws DateExtractionException {
+ final DateQualification dateQualification;
+
+ final Matcher m = patYyyyyy.matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
- d.setYear(Integer.parseInt(m.group("year")));
- if (m.group("uncertain") != null || m.group("uncertain2") != null) {
- d.setUncertain(true);
- }
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.LONG_YEAR, inputValue, new InstantEdtfDate(d));
+ dateQualification =
+ computeDateQualification(requestedDateQualification,
+ () -> (m.group("uncertain") != null || m.group("uncertain2") != null) ? UNCERTAIN : NO_QUALIFICATION);
+
+ final InstantEdtfDate datePart = new InstantEdtfDateBuilder(Integer.parseInt(m.group("year"))).withDateQualification(
+ dateQualification).withFlexibleDateBuild(flexibleDateBuild).build();
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.LONG_NEGATIVE_YEAR, inputValue, datePart);
}
- m = patYyyyyyRange.matcher(inputValue);
- if (m.matches()) {
- EdtfDatePart start = new EdtfDatePart();
- start.setYear(Integer.parseInt(m.group("year")));
- EdtfDatePart end = new EdtfDatePart();
- end.setYear(Integer.parseInt(m.group("year2")));
- IntervalEdtfDate intervalEdtfDate = new IntervalEdtfDate(new InstantEdtfDate(start), new InstantEdtfDate(end));
- if (m.group("uncertain") != null || m.group("uncertain2") != null) {
- intervalEdtfDate.setUncertain(true);
- }
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.LONG_YEAR, inputValue, intervalEdtfDate);
+ final Matcher m2 = patYyyyyyRange.matcher(inputValue);
+ if (m2.matches()) {
+ dateQualification =
+ computeDateQualification(requestedDateQualification,
+ () -> (m2.group("uncertain") != null || m2.group("uncertain2") != null) ? UNCERTAIN : NO_QUALIFICATION);
+
+ final InstantEdtfDate startDatePart = new InstantEdtfDateBuilder(Integer.parseInt(m2.group("year"))).withDateQualification(
+ dateQualification).withFlexibleDateBuild(flexibleDateBuild).build();
+ final InstantEdtfDate endDatePart = new InstantEdtfDateBuilder(Integer.parseInt(m2.group("year2"))).withDateQualification(
+ dateQualification).withFlexibleDateBuild(flexibleDateBuild).build();
+ IntervalEdtfDate intervalEdtfDate = new IntervalEdtfDateBuilder(startDatePart, endDatePart).withFlexibleDateBuild(
+ flexibleDateBuild).build();
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.LONG_NEGATIVE_YEAR, inputValue, intervalEdtfDate);
}
- return null;
+ return DateNormalizationResult.getNoMatchResult(inputValue);
}
-
}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternMonthNameDateExtractor.java b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternMonthNameDateExtractor.java
index 770a37e1be..d5e594cc6e 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternMonthNameDateExtractor.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/dates/extraction/dateextractors/PatternMonthNameDateExtractor.java
@@ -2,8 +2,10 @@
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
-import eu.europeana.normalization.dates.edtf.EdtfDatePart;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
+import eu.europeana.normalization.dates.edtf.InstantEdtfDateBuilder;
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
import eu.europeana.normalization.dates.extraction.MonthMultilingual;
import java.time.Month;
import java.util.HashMap;
@@ -13,7 +15,7 @@
/**
* A date where the month is specified by its name or an abbreviation. Supports all the official languages of the European Union
*/
-public class PatternMonthNameDateExtractor implements DateExtractor {
+public class PatternMonthNameDateExtractor extends AbstractDateExtractor {
HashMap patternDayMonthYear = new HashMap<>(12);
HashMap patternMonthDayYear = new HashMap<>(12);
@@ -49,32 +51,42 @@ public PatternMonthNameDateExtractor() {
}
@Override
- public DateNormalizationResult extract(String inputValue) {
+ public DateNormalizationResult extract(String inputValue, DateQualification requestedDateQualification,
+ boolean flexibleDateBuild) throws DateExtractionException {
+ final DateQualification dateQualification = computeDateQualification(requestedDateQualification,
+ () -> DateQualification.NO_QUALIFICATION);
+
for (Month month : Month.values()) {
Matcher m = patternDayMonthYear.get(month).matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
- d.setYear(Integer.parseInt(m.group("year")));
- d.setMonth(month.getValue());
- d.setDay(Integer.parseInt(m.group("day")));
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.MONTH_NAME, inputValue, new InstantEdtfDate(d));
+ final InstantEdtfDate datePart = new InstantEdtfDateBuilder(Integer.parseInt(m.group("year")))
+ .withMonth(month.getValue())
+ .withDay(Integer.parseInt(m.group("day")))
+ .withDateQualification(dateQualification)
+ .withFlexibleDateBuild(flexibleDateBuild)
+ .build();
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.MONTH_NAME, inputValue, datePart);
}
m = patternMonthDayYear.get(month).matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
- d.setYear(Integer.parseInt(m.group("year")));
- d.setMonth(month.getValue());
- d.setDay(Integer.parseInt(m.group("day")));
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.MONTH_NAME, inputValue, new InstantEdtfDate(d));
+ final InstantEdtfDate datePart = new InstantEdtfDateBuilder(Integer.parseInt(m.group("year")))
+ .withMonth(month.getValue())
+ .withDay(Integer.parseInt(m.group("day")))
+ .withDateQualification(dateQualification)
+ .withFlexibleDateBuild(flexibleDateBuild)
+ .build();
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.MONTH_NAME, inputValue, datePart);
}
m = patternMonthYear.get(month).matcher(inputValue);
if (m.matches()) {
- EdtfDatePart d = new EdtfDatePart();
- d.setYear(Integer.parseInt(m.group("year")));
- d.setMonth(month.getValue());
- return new DateNormalizationResult(DateNormalizationExtractorMatchId.MONTH_NAME, inputValue, new InstantEdtfDate(d));
+ final InstantEdtfDate datePart = new InstantEdtfDateBuilder(Integer.parseInt(m.group("year")))
+ .withMonth(month.getValue())
+ .withDateQualification(dateQualification)
+ .withFlexibleDateBuild(flexibleDateBuild)
+ .build();
+ return new DateNormalizationResult(DateNormalizationExtractorMatchId.MONTH_NAME, inputValue, datePart);
}
}
- return null;
+ return DateNormalizationResult.getNoMatchResult(inputValue);
}
}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/normalizers/DatesNormalizer.java b/metis-normalization/src/main/java/eu/europeana/normalization/normalizers/DatesNormalizer.java
index c6f3af7a51..6737e357b6 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/normalizers/DatesNormalizer.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/normalizers/DatesNormalizer.java
@@ -1,22 +1,22 @@
package eu.europeana.normalization.normalizers;
+import static eu.europeana.normalization.dates.DateNormalizationResultStatus.MATCHED;
+import static eu.europeana.normalization.dates.DateNormalizationResultStatus.NO_MATCH;
import static java.util.function.Predicate.not;
-import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
import eu.europeana.normalization.dates.edtf.AbstractEdtfDate;
-import eu.europeana.normalization.dates.edtf.EdtfValidator;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
-import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
+import eu.europeana.normalization.dates.extraction.dateextractors.BriefRangeDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.CenturyDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.DateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.DcmiPeriodDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.DecadeDateExtractor;
+import eu.europeana.normalization.dates.extraction.dateextractors.EdtfDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.NumericPartsDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.NumericPartsRangeDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.PatternBcAdDateExtractor;
-import eu.europeana.normalization.dates.extraction.dateextractors.PatternBriefDateRangeDateExtractor;
-import eu.europeana.normalization.dates.extraction.dateextractors.PatternEdtfDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.PatternFormatedFullDateDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.PatternLongNegativeYearDateExtractor;
import eu.europeana.normalization.dates.extraction.dateextractors.PatternMonthNameDateExtractor;
@@ -32,9 +32,7 @@
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
-import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -118,8 +116,8 @@ public DatesNormalizer() {
// Most values that match this pattern also match the EDTF pattern, but would result in an invalid date.
// This pattern only matches values that would not be valid EDTF dates.
extractorsInOrderForDateProperties = List.of(
- new PatternBriefDateRangeDateExtractor(),
- new PatternEdtfDateExtractor(),
+ new BriefRangeDateExtractor(),
+ new EdtfDateExtractor(),
new CenturyDateExtractor(),
new DecadeDateExtractor(),
new NumericPartsRangeDateExtractor(),
@@ -133,17 +131,20 @@ public DatesNormalizer() {
extractorsInOrderForGenericProperties =
extractorsInOrderForDateProperties.stream()
.filter(
- not(PatternBriefDateRangeDateExtractor.class::isInstance))
+ not(BriefRangeDateExtractor.class::isInstance))
.collect(Collectors.toList());
normalizationOperationsInOrderDateProperty = List.of(
- input -> normalizeInput(extractorsInOrderForDateProperties, input),
- input -> normalizeInput(extractorsInOrderForDateProperties, input, dateFieldSanitizer::sanitize1stTimeDateProperty),
- input -> normalizeInput(extractorsInOrderForDateProperties, input, dateFieldSanitizer::sanitize2ndTimeDateProperty));
+ input -> normalizeInput(extractorsInOrderForDateProperties, input, DateQualification.NO_QUALIFICATION),
+ input -> normalizeInput(extractorsInOrderForDateProperties, input, dateFieldSanitizer::sanitize1stTimeDateProperty,
+ SanitizeOperation::isApproximateSanitizeOperationForDateProperty),
+ input -> normalizeInput(extractorsInOrderForDateProperties, input, dateFieldSanitizer::sanitize2ndTimeDateProperty,
+ SanitizeOperation::isApproximateSanitizeOperationForDateProperty));
normalizationOperationsInOrderGenericProperty = List.of(
- input -> normalizeInput(extractorsInOrderForGenericProperties, input),
- input -> normalizeInput(extractorsInOrderForGenericProperties, input, dateFieldSanitizer::sanitizeGenericProperty));
+ input -> normalizeInputGeneric(extractorsInOrderForGenericProperties, input, DateQualification.NO_QUALIFICATION),
+ input -> normalizeInputGeneric(extractorsInOrderForGenericProperties, input,
+ dateFieldSanitizer::sanitizeGenericProperty, SanitizeOperation::isApproximateSanitizeOperationForGenericProperty));
}
private static Pair getProxySubtagQuery(Namespace.Element subtag) {
@@ -191,13 +192,8 @@ private void normalizeElement(Document document, Element element, Namespace.Elem
// Apply the normalization. If nothing can be done, we return.
final String elementText = XmlUtil.getElementText(element);
- // TODO: 11/10/2022 Check if this can be more securely structured.
- // Currently the class InstantEdtfDate is allowed to contain invalid values for day, month, year and therefore a call
- // to some of it's functions, for example getLastDay which internally triggers a call to lastDayBasedOnMonth can fail if the values are invalid.
- // Probably a better approach is to not allow the object to be created with invalid values in the first place.
final DateNormalizationResult dateNormalizationResult = normalizationFunction.apply(elementText);
- if (dateNormalizationResult.getDateNormalizationExtractorMatchId() == DateNormalizationExtractorMatchId.NO_MATCH
- || dateNormalizationResult.getDateNormalizationExtractorMatchId() == DateNormalizationExtractorMatchId.INVALID) {
+ if (dateNormalizationResult.getDateNormalizationResultStatus() == NO_MATCH) {
return;
}
@@ -229,11 +225,7 @@ private void normalizeElement(Document document, Element element, Namespace.Elem
* @return the date normalization result
*/
public DateNormalizationResult normalizeDateProperty(String input) {
- return normalizeProperty(input, normalizationOperationsInOrderDateProperty,
- dateNormalizationResult -> false, //No extra check
- SanitizeOperation::isApproximateSanitizeOperationForDateProperty,
- this::validateAndFix,
- this::noMatchIfValidAndTimeOnly);
+ return normalizeProperty(input, normalizationOperationsInOrderDateProperty);
}
/**
@@ -244,20 +236,11 @@ public DateNormalizationResult normalizeDateProperty(String input) {
* @return the date normalization result
*/
public DateNormalizationResult normalizeGenericProperty(String input) {
- return normalizeProperty(input, normalizationOperationsInOrderGenericProperty,
- dateNormalizationResult -> !dateNormalizationResult.isCompleteDate(),
- SanitizeOperation::isApproximateSanitizeOperationForGenericProperty,
- this::validate, //If invalid we don't perform a fixing to retry validation
- dateNormalizationResult -> {//NOOP
- });
+ return normalizeProperty(input, normalizationOperationsInOrderGenericProperty);
}
private DateNormalizationResult normalizeProperty(
- String input, final List> normalizationOperationsInOrder,
- Predicate extraCheckForNoMatch,
- Predicate checkIfApproximateCleanOperationId,
- Consumer validationOperation,
- Consumer postProcessingMatchId) {
+ String input, final List> normalizationOperationsInOrder) {
DateNormalizationResult dateNormalizationResult;
String sanitizedInput = sanitizeCharacters(input);
@@ -266,88 +249,70 @@ private DateNormalizationResult normalizeProperty(
dateNormalizationResult = normalizationOperationsInOrder
.stream()
.map(operation -> operation.apply(sanitizedInput))
- .filter(Objects::nonNull).findFirst().orElse(null);
+ .filter(result -> result.getDateNormalizationResultStatus() == MATCHED)
+ .findFirst()
+ .orElse(DateNormalizationResult.getNoMatchResult(input));
- //Check if we have a match
- if (Objects.isNull(dateNormalizationResult) || extraCheckForNoMatch.test(dateNormalizationResult)) {
- return DateNormalizationResult.getNoMatchResult(input);
- } else {
- //Check if we did a sanitize operation and update approximate
- if (dateNormalizationResult.getSanitizeOperation() != null) {
- dateNormalizationResult.getEdtfDate().setApproximate(
- checkIfApproximateCleanOperationId.test(dateNormalizationResult.getSanitizeOperation()));
- }
- validationOperation.accept(dateNormalizationResult);
- postProcessingMatchId.accept(dateNormalizationResult);
- return dateNormalizationResult;
- }
+ return dateNormalizationResult;
}
- private void noMatchIfValidAndTimeOnly(DateNormalizationResult dateNormalizationResult) {
- if (dateNormalizationResult.getDateNormalizationExtractorMatchId() != DateNormalizationExtractorMatchId.INVALID
- && dateNormalizationResult.getEdtfDate().isTimeOnly()) {
-
- // TODO: 21/07/2022 In the result only the match id is declared NO_MATCH but the contents are
- // still present in the object. Is that okay?
- // Answer: This is okay, but as before we don't expect to have just the time and not the date.
- // To be considered on how to structure this, if at all to be changed.
- dateNormalizationResult.setDateNormalizationExtractorMatchId(DateNormalizationExtractorMatchId.NO_MATCH);
- }
+ private DateNormalizationResult normalizeInput(List dateExtractors, String inputDate,
+ DateQualification dateQualification) {
+ return dateExtractors.stream().map(
+ dateExtractor -> dateExtractor.extractDateProperty(inputDate, dateQualification))
+ .filter(dateNormalizationResult -> dateNormalizationResult.getDateNormalizationResultStatus()
+ == MATCHED).findFirst()
+ .orElse(DateNormalizationResult.getNoMatchResult(inputDate));
}
- private DateNormalizationResult normalizeInput(List dateExtractors, String input) {
- return dateExtractors.stream().map(dateExtractor -> dateExtractor.extract(input))
- .filter(Objects::nonNull).findFirst().orElse(null);
+ private DateNormalizationResult normalizeInputGeneric(List dateExtractors, String input,
+ DateQualification dateQualification) {
+ return dateExtractors.stream().map(dateExtractor -> dateExtractor.extractGenericProperty(input, dateQualification))
+ .filter(dateNormalizationResult -> dateNormalizationResult.getDateNormalizationResultStatus()
+ == MATCHED).findFirst()
+ .orElse(DateNormalizationResult.getNoMatchResult(input));
}
private DateNormalizationResult normalizeInput(List dateExtractors, String input,
- Function sanitizeFunction) {
+ Function sanitizeFunction, Predicate checkIfApproximateCleanOperationId) {
final SanitizedDate sanitizedDate = sanitizeFunction.apply(input);
- DateNormalizationResult dateNormalizationResult = null;
+ DateNormalizationResult dateNormalizationResult = DateNormalizationResult.getNoMatchResult(input);
if (sanitizedDate != null && StringUtils.isNotEmpty(sanitizedDate.getSanitizedDateString())) {
- dateNormalizationResult = normalizeInput(dateExtractors, sanitizedDate.getSanitizedDateString());
- if (dateNormalizationResult != null) {
- dateNormalizationResult.setCleanOperation(sanitizedDate.getSanitizeOperation());
+ final DateQualification dateQualification;
+ if (checkIfApproximateCleanOperationId.test(sanitizedDate.getSanitizeOperation())) {
+ dateQualification = DateQualification.APPROXIMATE;
+ } else {
+ dateQualification = DateQualification.NO_QUALIFICATION;
}
- }
- return dateNormalizationResult;
- }
-
- private void validateAndFix(DateNormalizationResult dateNormalizationResult) {
- final AbstractEdtfDate edtfDate = dateNormalizationResult.getEdtfDate();
- if (!EdtfValidator.validate(edtfDate, false)) {
- switchAndValidate(dateNormalizationResult, edtfDate);
- }
- }
+ dateNormalizationResult = normalizeInput(dateExtractors, sanitizedDate.getSanitizedDateString(), dateQualification);
- private void validate(DateNormalizationResult dateNormalizationResult) {
- final AbstractEdtfDate edtfDate = dateNormalizationResult.getEdtfDate();
- if (!EdtfValidator.validate(edtfDate, false)) {
- dateNormalizationResult.setDateNormalizationExtractorMatchId(DateNormalizationExtractorMatchId.INVALID);
+ if (dateNormalizationResult.getDateNormalizationResultStatus() == MATCHED) {
+ //Re-create result containing sanitization operation.
+ dateNormalizationResult = new DateNormalizationResult(dateNormalizationResult, sanitizedDate.getSanitizeOperation());
+ }
}
+ return dateNormalizationResult;
}
- private void switchAndValidate(DateNormalizationResult dateNormalizationResult, AbstractEdtfDate edtfDate) {
- if (edtfDate instanceof IntervalEdtfDate) {
- ((IntervalEdtfDate) edtfDate).switchStartWithEnd();
- if (!EdtfValidator.validate(edtfDate, false)) {
- //Revert the start/end
- ((IntervalEdtfDate) edtfDate).switchStartWithEnd();
- switchDayWithMonthAndValidate(dateNormalizationResult, edtfDate);
+ private DateNormalizationResult normalizeInputGeneric(List dateExtractors, String input,
+ Function sanitizeFunction, Predicate checkIfApproximateCleanOperationId) {
+ final SanitizedDate sanitizedDate = sanitizeFunction.apply(input);
+ DateNormalizationResult dateNormalizationResult = DateNormalizationResult.getNoMatchResult(input);
+ if (sanitizedDate != null && StringUtils.isNotEmpty(sanitizedDate.getSanitizedDateString())) {
+ if (checkIfApproximateCleanOperationId.test(sanitizedDate.getSanitizeOperation())) {
+ dateNormalizationResult = normalizeInputGeneric(dateExtractors, sanitizedDate.getSanitizedDateString(),
+ DateQualification.APPROXIMATE);
+ } else {
+ dateNormalizationResult = normalizeInputGeneric(dateExtractors, sanitizedDate.getSanitizedDateString(),
+ DateQualification.NO_QUALIFICATION);
}
- } else {
- switchDayWithMonthAndValidate(dateNormalizationResult, edtfDate);
- }
- }
- private void switchDayWithMonthAndValidate(DateNormalizationResult dateNormalizationResult, AbstractEdtfDate edtfDate) {
- edtfDate.switchDayAndMonth();
- if (EdtfValidator.validate(edtfDate, false)) {
- dateNormalizationResult.setEdtfDate(edtfDate);
- } else {
- edtfDate.switchDayAndMonth();
- dateNormalizationResult.setDateNormalizationExtractorMatchId(DateNormalizationExtractorMatchId.INVALID);
+ if (dateNormalizationResult.getDateNormalizationResultStatus() == MATCHED) {
+ //Re-create result containing sanitization operation.
+ dateNormalizationResult = new DateNormalizationResult(dateNormalizationResult, sanitizedDate.getSanitizeOperation());
+ }
}
+ return dateNormalizationResult;
}
/**
@@ -400,11 +365,11 @@ private void appendTimespanEntity(Document document, AbstractEdtfDate edtfDate,
}
// Create and add skosNote elements to timespan in case of approximate or uncertain dates.
- if (edtfDate.isApproximate()) {
+ if (edtfDate.getDateQualification() == DateQualification.APPROXIMATE) {
final Element skosNote = XmlUtil.createElement(SKOS_NOTE, timeSpan, null);
skosNote.appendChild(document.createTextNode("approximate"));
}
- if (edtfDate.isUncertain()) {
+ if (edtfDate.getDateQualification() == DateQualification.UNCERTAIN) {
final Element skosNote = XmlUtil.createElement(SKOS_NOTE, timeSpan, null);
skosNote.appendChild(document.createTextNode("uncertain"));
}
diff --git a/metis-normalization/src/main/java/eu/europeana/normalization/util/XmlUtil.java b/metis-normalization/src/main/java/eu/europeana/normalization/util/XmlUtil.java
index 20128edd63..51c87f6c26 100644
--- a/metis-normalization/src/main/java/eu/europeana/normalization/util/XmlUtil.java
+++ b/metis-normalization/src/main/java/eu/europeana/normalization/util/XmlUtil.java
@@ -7,6 +7,7 @@
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
+import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -36,7 +37,7 @@
*/
public final class XmlUtil {
- private static final Logger LOGGER = LoggerFactory.getLogger(XmlUtil.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final DocumentBuilderFactory FACTORY = DocumentBuilderFactory.newInstance();
static {
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/DateLevel0Test.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/DateLevel0Test.java
deleted file mode 100644
index 031e5fad32..0000000000
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/DateLevel0Test.java
+++ /dev/null
@@ -1,301 +0,0 @@
-// TODO: 21/12/2022 Rewrite this class without the time part consideration in the results
-
-//package eu.europeana.normalization.dates;
-//
-//import static org.junit.jupiter.api.Assertions.assertEquals;
-//import static org.junit.jupiter.api.Assertions.assertThrows;
-//
-//import eu.europeana.normalization.dates.edtf.EdtfParser;
-//import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
-//import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
-//import java.text.ParseException;
-//import java.util.stream.Stream;
-//import org.junit.jupiter.api.DisplayName;
-//import org.junit.jupiter.params.ParameterizedTest;
-//import org.junit.jupiter.params.provider.Arguments;
-//import org.junit.jupiter.params.provider.MethodSource;
-//
-///**
-// * Unit tests to validate EDTF format parser Level 0
-// *
-// * @see EDTF library specification
-// */
-//public class DateLevel0Test {
-//
-// private EdtfParser parser = new EdtfParser();
-//
-// private static Stream dateCompleteRepresentation() {
-// return Stream.of(Arguments.of("1986-07-12", 1986, 7, 12, true),
-// Arguments.of("1986-05-08", 1986, 5, 8, true),
-// Arguments.of("1986-2-2", 1986, 2, 2, false), //TODO: should be this valid or not?
-// Arguments.of("1986-2-21", 1986, 2, 21, false), //TODO: should be this valid or not?
-// Arguments.of("1986/02/02", 1986, 2, 2, false)); //TODO: is this kind of date valid or not yyyy/mm/dd?
-// }
-//
-// @ParameterizedTest
-// @MethodSource("dateCompleteRepresentation")
-// @DisplayName("[year][“-”][month][“-”][day] Complete representation")
-// void testDateCompleteRepresentation(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Boolean isSuccess) throws ParseException {
-//
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedDay, actual.getEdtfDatePart().getDay());
-// assertEquals(expectedMonth, actual.getEdtfDatePart().getMonth());
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream reducedPrecisionForYearAndMonth() {
-// return Stream.of(Arguments.of("1986-07", 1986, 7, true),
-// Arguments.of("1986-11", 1986, 11, true),
-// Arguments.of("1986-5", 1986, 5, false),
-// Arguments.of("1986-13", 1986, 13, true), //TODO: check why this is not an error
-// Arguments.of("1986/1", 1986, 1, false) //TODO: do we accept slashes yyyy/mm?
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("reducedPrecisionForYearAndMonth")
-// @DisplayName("[year][“-”][month] Reduced precision for year and month")
-// void testReducedPrecisionForYearAndMonth(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-// assertEquals(expectedMonth, actual.getEdtfDatePart().getMonth());
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream reducedPrecisionForYear() {
-// return Stream.of(Arguments.of("1000", 1000, true),
-// Arguments.of("1258", 1258, true),
-// Arguments.of("1986", 1986, true),
-// Arguments.of("2022", 2022, true),
-// Arguments.of("9999", 9999, true),
-// Arguments.of("0999", 999, true),
-// Arguments.of("0001", 1, true), //TODO: if it has four digits it is interpreted as year
-// Arguments.of(" 800", 800, false), //TODO: if it doesn't is valid to test 3 digit years?
-// Arguments.of("800", 800, false)
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("reducedPrecisionForYear")
-// @DisplayName("[year] Reduced precision for year")
-// void testReducedPrecisionForYearAndMonth(String actualDate,
-// Integer expectedYear,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream dateAndTimeRepresentation() {
-// return Stream.of(Arguments.of("1986-07-12T23:59:59", 1986, 7, 12, 23, 59, 59, true),
-// Arguments.of("1986-07-12T11:22:55", 1986, 7, 12, 11, 22, 55, true),
-// Arguments.of("1986-07-12T00:00:00", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T23:59", 1986, 7, 12, 23, 59, null, true),
-// Arguments.of("1986-07-12T23", 1986, 7, 12, 23, null, null, true),
-// Arguments.of("1986-07-12T1:20:30", 1986, 7, 12, 1, 20, 30, false),
-// Arguments.of("1986-07-12 11:20:30", 1986, 7, 12, 11, 20, 30, false),
-// Arguments.of("1986-07-12t11:20:30", 1986, 7, 12, 11, 20, 30, false),
-// Arguments.of("1986-07-12T01:1:30", 1986, 7, 12, 1, 1, 30,
-// false)); //TODO: Check the if (false) cases are or not valid to test
-// }
-//
-// @ParameterizedTest
-// @MethodSource("dateAndTimeRepresentation")
-// @DisplayName("[date][“T”][time] Complete representations for calendar date and (local) time of day")
-// void testDateAndTime(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Integer expectedHour,
-// Integer expectedMinute,
-// Integer expectedSecond,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedDay, actual.getEdtfDatePart().getDay());
-// assertEquals(expectedMonth, actual.getEdtfDatePart().getMonth());
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-//// assertEquals(expectedHour, actual.getEdtfTimePart().getHour());
-//// assertEquals(expectedMinute, actual.getEdtfTimePart().getMinute());
-//// assertEquals(expectedSecond, actual.getEdtfTimePart().getSecond());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream dateAndTimeUTCRepresentation() {
-// return Stream.of(Arguments.of("1986-07-12T23:59:59Z", 1986, 7, 12, 23, 59, 59, true),
-// Arguments.of("1986-07-12T11:22:55Z", 1986, 7, 12, 11, 22, 55, true),
-// Arguments.of("1986-07-12T00:00:00Z", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T23:59Z", 1986, 7, 12, 23, 59, null, false), //TODO: Check what is the outcome of this
-// Arguments.of("1986-07-12T23Z", 1986, 7, 12, 23, null, null, false), //TODO: Check what is the outcome of this
-// Arguments.of("1986-07-12TZ", 1986, 7, 12, null, null, null, false), //TODO: Check what are the limits
-// Arguments.of("1986-07-12T1:20:30Z", 1986, 7, 12, 1, 20, 30, false),
-// Arguments.of("1986-07-12 11:20:30Z", 1986, 7, 12, 11, 20, 30, false),
-// Arguments.of("1986-07-12t11:20:30Z", 1986, 7, 12, 11, 20, 30, false),
-// Arguments.of("1986-07-12T01:1:30Z", 1986, 7, 12, 1, 1, 30, false));
-// }
-//
-// @ParameterizedTest
-// @MethodSource("dateAndTimeUTCRepresentation")
-// @DisplayName("[dateI][“T”][time][“Z”]Complete representations for calendar date and UTC time of day")
-// void testTimeInterval(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Integer expectedHour,
-// Integer expectedMinute,
-// Integer expectedSecond,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedDay, actual.getEdtfDatePart().getDay());
-// assertEquals(expectedMonth, actual.getEdtfDatePart().getMonth());
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-//// assertEquals(expectedHour, actual.getEdtfTimePart().getHour());
-//// assertEquals(expectedMinute, actual.getEdtfTimePart().getMinute());
-//// assertEquals(expectedSecond, actual.getEdtfTimePart().getSecond());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream dateAndTimeShiftRepresentation() {
-// return Stream.of(Arguments.of("1986-07-12T23:59:59-05", 1986, 7, 12, 23, 59, 59, true),
-// Arguments.of("1986-07-12T11:22:55-12", 1986, 7, 12, 11, 22, 55, true),
-// Arguments.of("1986-07-12T00:00:00+08", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T00:00:00+12", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T00:00:00-00", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T00:00:00+00", 1986, 7, 12, 0, 0, 0, true), //TODO: check why this is accepted
-// Arguments.of("1986-07-12T00:00:00+0", 1986, 7, 12, 0, 0, 0, false), //TODO: check this is why is not accepted
-// Arguments.of("1986-07-12T00:00:00-5", 1986, 7, 12, 0, 0, 0, false),
-// Arguments.of("1986-07-12T00:00:00+", 1986, 7, 12, 0, 0, 0, false),
-// Arguments.of("1986-07-12T00:00:00-", 1986, 7, 12, 0, 0, 0, false)
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("dateAndTimeShiftRepresentation")
-// @DisplayName("[dateI][“T”][time][shiftHour] Date and time with timeshift in hours (only)")
-// void testDateTimeWithShiftInHour(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Integer expectedHour,
-// Integer expectedMinute,
-// Integer expectedSecond,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedDay, actual.getEdtfDatePart().getDay());
-// assertEquals(expectedMonth, actual.getEdtfDatePart().getMonth());
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-//// assertEquals(expectedHour, actual.getEdtfTimePart().getHour());
-//// assertEquals(expectedMinute, actual.getEdtfTimePart().getMinute());
-//// assertEquals(expectedSecond, actual.getEdtfTimePart().getSecond());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream dateAndTimeShiftHourRepresentation() {
-// return Stream.of(Arguments.of("1986-07-12T23:59:59-05:20", 1986, 7, 12, 23, 59, 59, true),
-// Arguments.of("1986-07-12T11:22:55-12:30", 1986, 7, 12, 11, 22, 55, true),
-// Arguments.of("1986-07-12T00:00:00+08:15", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T00:00:00+12:30", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T00:00:00-00:30", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T00:00:00+00:30", 1986, 7, 12, 0, 0, 0, true),
-// Arguments.of("1986-07-12T00:00:00+0:", 1986, 7, 12, 0, 0, 0, false),
-// Arguments.of("1986-07-12T00:00:00-5:30", 1986, 7, 12, 0, 0, 0, false),
-// Arguments.of("1986-07-12T00:00:00+5:15", 1986, 7, 12, 0, 0, 0, false),
-// Arguments.of("1986-07-12T00:00:00-05:", 1986, 7, 12, 0, 0, 0, true) //TODO: check why this is accepted
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("dateAndTimeShiftHourRepresentation")
-// @DisplayName("[dateI][“T”][time][shiftHourMinute] Date and time with timeshift in hours and minutes")
-// void testDateTimeWithShiftInHourAndMinutes(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Integer expectedHour,
-// Integer expectedMinute,
-// Integer expectedSecond,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedDay, actual.getEdtfDatePart().getDay());
-// assertEquals(expectedMonth, actual.getEdtfDatePart().getMonth());
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-//// assertEquals(expectedHour, actual.getEdtfTimePart().getHour());
-//// assertEquals(expectedMinute, actual.getEdtfTimePart().getMinute());
-//// assertEquals(expectedSecond, actual.getEdtfTimePart().getSecond());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream dateIntervalRepresentation() {
-// return Stream.of(Arguments.of("1986/1998", 1986, null, null, 1998, null, null, true),
-// Arguments.of("1986-07/1998-11", 1986, 7, null, 1998, 11, null, true),
-// Arguments.of("1986-07 / 1998-07", 1986, 7, null, 1998, 7, null, false), //TODO: check if the space between is valid
-// Arguments.of("1986-02-12/1998-07-09", 1986, 2, 12, 1998, 7, 9, true),
-// Arguments.of("1986-07-09/2005", 1986, 7, 9, 2005, null, null, true),
-// Arguments.of("1986/2005-02-22", 1986, null, null, 2005, 2, 22, true),
-// Arguments.of("0986/0998", 986, null, null, 998, null, null, true),
-// Arguments.of("986/998", 986, null, null, 998, null, null, false),
-// Arguments.of("1286/1218", 1286, null, null, 1218, null, null, true), //TODO: check if ranges d1>d2 or d2>d1 are valid
-// Arguments.of("1986-1998", 1986, null, null, 1998, null, null, false),
-// Arguments.of("1986-13/1998-01", 1986, 13, null, 1998, 1, null, true), //TODO: check why this is valid?
-// Arguments.of("1986-00/1998-01", 1986, null, null, 1998, 1, null, true) //TODO: check why this is valid?
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("dateIntervalRepresentation")
-// @DisplayName("EDTF Level 0 adopts representations of a time interval")
-// void testDateInterval(String actualDate,
-// Integer expectedStartYear,
-// Integer expectedStartMonth,
-// Integer expectedStartDay,
-// Integer expectedEndYear,
-// Integer expectedEndMonth,
-// Integer expectedEndDay,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// IntervalEdtfDate actual = (IntervalEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedStartDay, actual.getStart().getEdtfDatePart().getDay());
-// assertEquals(expectedStartMonth, actual.getStart().getEdtfDatePart().getMonth());
-// assertEquals(expectedStartYear, actual.getStart().getEdtfDatePart().getYear());
-// assertEquals(expectedEndDay, actual.getEnd().getEdtfDatePart().getDay());
-// assertEquals(expectedEndMonth, actual.getEnd().getEdtfDatePart().getMonth());
-// assertEquals(expectedEndYear, actual.getEnd().getEdtfDatePart().getYear());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//}
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/DateLevel1Test.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/DateLevel1Test.java
deleted file mode 100644
index e18eebc956..0000000000
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/DateLevel1Test.java
+++ /dev/null
@@ -1,336 +0,0 @@
-// TODO: 21/12/2022 Rewrite this class without the time part consideration in the results
-
-//package eu.europeana.normalization.dates;
-//
-//import static org.junit.jupiter.api.Assertions.assertEquals;
-//import static org.junit.jupiter.api.Assertions.assertThrows;
-//import static org.junit.jupiter.api.Assertions.assertTrue;
-//
-//import eu.europeana.normalization.dates.edtf.EdtfParser;
-//import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
-//import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
-//import java.text.ParseException;
-//import java.util.stream.Stream;
-//import org.junit.jupiter.api.Disabled;
-//import org.junit.jupiter.api.DisplayName;
-//import org.junit.jupiter.params.ParameterizedTest;
-//import org.junit.jupiter.params.provider.Arguments;
-//import org.junit.jupiter.params.provider.MethodSource;
-//
-///**
-// * Unit tests to validate EDTF format parser Level 1
-// *
-// * @see EDTF library specification
-// */
-//public class DateLevel1Test {
-//
-// private EdtfParser parser = new EdtfParser();
-//
-// private static Stream letterPrefixedCalendarYear() {
-// return Stream.of(Arguments.of("Y170000002", 170000002, true),
-// Arguments.of("Y-170000002", -170000002, true),
-// Arguments.of("Y1700000002", 1700000002, true),
-// //Arguments.of("Y17000000002", 17000000002L, true), //TODO: Long OF Integer?
-// Arguments.of("Y0", 0, true),
-// Arguments.of("Y1", 1, true),
-// Arguments.of("Y-1", -1, true)
-// //Arguments.of("Y", 0, false) //TODO: Is this correct to have?
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("letterPrefixedCalendarYear")
-// @DisplayName("Letter-prefixed calendar year")
-// void testLetterPrefixedCalendarYear(String actualDate, Integer expectedYear, Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream seasons() {
-// return Stream.of(
-// Arguments.of("2022-21", "Spring, 2022", true), //TODO: Seasons is not supported as Level1
-// Arguments.of("2022-22", "Summer, 2022", true),
-// Arguments.of("2022-23", "Autumn, 2022", true),
-// Arguments.of("2022-24", "Winter, 2022", true),
-// Arguments.of("2022-25", null, false)
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("seasons")
-// @Disabled
-// @DisplayName("The values 21, 22, 23, 24 may be used used to signify ' Spring', 'Summer', 'Autumn', 'Winter', respectively")
-// void testSeasons(String actualDate, String expectedDate, Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedDate, actual.toString());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream qualificationOfADate() {
-// return Stream.of(
-// Arguments.of("1986?", 1986, null, null, true, false, true),
-// Arguments.of("1986~", 1986, null, null, false, true, true),
-// Arguments.of("1986-07?", 1986, 7, null, true, false, true),
-// Arguments.of("1986-07~", 1986, 7, null, false, true, true),
-// Arguments.of("1986-07-12%", 1986, 7, 12, true, true, true)
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("qualificationOfADate")
-// @DisplayName("The characters '?', '~' and '%' are used to mean \"uncertain\", \"approximate\", and \"uncertain\" as well as \"approximate\"")
-// void testQualificationOfADate(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Boolean expectedUncertainty,
-// Boolean expectedApproximate,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-// assertEquals(expectedMonth, actual.getEdtfDatePart().getMonth());
-// assertEquals(expectedDay, actual.getEdtfDatePart().getDay());
-// assertEquals(expectedUncertainty, actual.isUncertain());
-// assertEquals(expectedApproximate, actual.isApproximate());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream unspecifiedDigitsFromTheRight() {
-// return Stream.of(
-// //TODO: check if all this patterns of L1 should be accepted. now they give exception
-// Arguments.of("201X", 2010, null, null, false),
-// Arguments.of("20XX", 2000, null, null, false),
-// Arguments.of("1986-XX", 1986, null, null, false),
-// Arguments.of("1986-07-XX", 1986, 7, null, false),
-// Arguments.of("1986-XX-XX", 1986, 7, 12, false)
-// );
-// }
-//
-// @ParameterizedTest
-// @MethodSource("unspecifiedDigitsFromTheRight")
-// @DisplayName("The character 'X' may be used in place of one or more rightmost digits to indicate that the value of that digit is unspecified")
-// void testUnspecifiedDigitsFromTheRight(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-// assertEquals(expectedMonth, actual.getEdtfDatePart().getMonth());
-// assertEquals(expectedDay, actual.getEdtfDatePart().getDay());
-//
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream negativeCalendarYear() {
-// return Stream.of(Arguments.of("-1986", -1986, true),
-// Arguments.of("-2086", -2086, true),
-// Arguments.of("-9999", -9999, true),
-// Arguments.of("-0986", -986, true),
-// Arguments.of("-10986", -10986, false));
-// }
-//
-// @ParameterizedTest
-// @MethodSource("negativeCalendarYear")
-// @DisplayName("Negative Calendar Year")
-// void testNegativeCalendarYear(String actualDate,
-// Integer expectedYear,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// InstantEdtfDate actual = (InstantEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedYear, actual.getEdtfDatePart().getYear());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream openEndTimeInterval() {
-// return Stream.of(
-// Arguments.of("1998-11-12/..", 1998, 11, 12, false, false, true),
-// Arguments.of("1998-11/..", 1998, 11, null, false, false, true),
-// Arguments.of("1998/..", 1998, null, null, false, false, true),
-// Arguments.of("1998-11-12~/..", 1998, 11, 12, true, false, true),
-// Arguments.of("1998-11~/..", 1998, 11, null, true, false, true),
-// Arguments.of("1998~/..", 1998, null, null, true, false, true),
-// Arguments.of("1998-11-12?/..", 1998, 11, 12, false, true, true),
-// Arguments.of("1998-11?/..", 1998, 11, null, false, true, true),
-// Arguments.of("1998?/..", 1998, null, null, false, true, true),
-// Arguments.of("1998-11-12%/..", 1998, 11, 12, true, true, true),
-// Arguments.of("1998-11%/..", 1998, 11, null, true, true, true),
-// Arguments.of("1998%/..", 1998, null, null, true, true, true),
-// Arguments.of("1998-11-12 / ..", 1998, 11, 12, false, false, false),
-// Arguments.of("1998-11-12 /..", 1998, 11, 12, false, false, false),
-// Arguments.of("1998-11-12/ ..", 1998, 11, 12, false, false, false));
-// }
-//
-// @ParameterizedTest
-// @MethodSource("openEndTimeInterval")
-// @DisplayName("Open end time interval")
-// void testOpenEndTimeInterval(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Boolean expectedIsApproximate,
-// Boolean expectedIsUncertain,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// IntervalEdtfDate actual = (IntervalEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedYear, actual.getStart().getEdtfDatePart().getYear());
-// assertEquals(expectedMonth, actual.getStart().getEdtfDatePart().getMonth());
-// assertEquals(expectedDay, actual.getStart().getEdtfDatePart().getDay());
-// assertEquals(expectedIsApproximate, actual.getStart().isApproximate());
-// assertEquals(expectedIsUncertain, actual.getStart().isUncertain());
-// assertTrue(actual.getEnd().getEdtfDatePart().isUnspecified());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream openStartTimeInterval() {
-// return Stream.of(
-// Arguments.of("../1998-11-12", 1998, 11, 12, false, false, true),
-// Arguments.of("../1998-11", 1998, 11, null, false, false, true),
-// Arguments.of("../1998", 1998, null, null, false, false, true),
-// Arguments.of("../1998-11-12~", 1998, 11, 12, true, false, true),
-// Arguments.of("../1998-11~", 1998, 11, null, true, false, true),
-// Arguments.of("../1998~", 1998, null, null, true, false, true),
-// Arguments.of("../1998-11-12?", 1998, 11, 12, false, true, true),
-// Arguments.of("../1998-11?", 1998, 11, null, false, true, true),
-// Arguments.of("../1998?", 1998, null, null, false, true, true),
-// Arguments.of("../1998-11-12%", 1998, 11, 12, true, true, true),
-// Arguments.of("../1998-11%", 1998, 11, null, true, true, true),
-// Arguments.of("../1998%", 1998, null, null, true, true, true),
-// Arguments.of(".. / 1998-11-12", 1998, 11, 12, false, false, false),
-// Arguments.of("../ 1998-11-12", 1998, 11, 12, false, false, false),
-// Arguments.of(".. /1998-11-12", 1998, 11, 12, false, false, false));
-// }
-//
-// @ParameterizedTest
-// @MethodSource("openStartTimeInterval")
-// @DisplayName("Open start time interval")
-// void testOpenStartTimeInterval(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Boolean expectedIsApproximate,
-// Boolean expectedIsUncertain,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// IntervalEdtfDate actual = (IntervalEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedYear, actual.getEnd().getEdtfDatePart().getYear());
-// assertEquals(expectedMonth, actual.getEnd().getEdtfDatePart().getMonth());
-// assertEquals(expectedDay, actual.getEnd().getEdtfDatePart().getDay());
-// assertEquals(expectedIsApproximate, actual.getEnd().isApproximate());
-// assertEquals(expectedIsUncertain, actual.getEnd().isUncertain());
-// assertTrue(actual.getStart().getEdtfDatePart().isUnspecified());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream timeIntervalWithUnknownEnd() {
-// return Stream.of(
-// Arguments.of("1998-11-12/", 1998, 11, 12, false, false, true),
-// Arguments.of("1998-11/", 1998, 11, null, false, false, true),
-// Arguments.of("1998/", 1998, null, null, false, false, true),
-// Arguments.of("1998-11-12~/", 1998, 11, 12, true, false, true),
-// Arguments.of("1998-11~/", 1998, 11, null, true, false, true),
-// Arguments.of("1998~/", 1998, null, null, true, false, true),
-// Arguments.of("1998-11-12?/", 1998, 11, 12, false, true, true),
-// Arguments.of("1998-11?/", 1998, 11, null, false, true, true),
-// Arguments.of("1998?/", 1998, null, null, false, true, true),
-// Arguments.of("1998-11-12%/", 1998, 11, 12, true, true, true),
-// Arguments.of("1998-11%/", 1998, 11, null, true, true, true),
-// Arguments.of("1998%/", 1998, null, null, true, true, true),
-// Arguments.of("1998-11-12 / ", 1998, 11, 12, false, false, false),
-// Arguments.of("1998-11-12 /", 1998, 11, 12, false, false, false),
-// Arguments.of("1998-11-12/ ", 1998, 11, 12, false, false, false));
-// }
-//
-// @ParameterizedTest
-// @MethodSource("timeIntervalWithUnknownEnd")
-// @DisplayName("Time interval with unknown end")
-// void testTimeIntervalWithUnknownEnd(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Boolean expectedIsApproximate,
-// Boolean expectedIsUncertain,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// IntervalEdtfDate actual = (IntervalEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedYear, actual.getStart().getEdtfDatePart().getYear());
-// assertEquals(expectedMonth, actual.getStart().getEdtfDatePart().getMonth());
-// assertEquals(expectedDay, actual.getStart().getEdtfDatePart().getDay());
-// assertEquals(expectedIsApproximate, actual.getStart().isApproximate());
-// assertEquals(expectedIsUncertain, actual.getStart().isUncertain());
-// assertTrue(actual.getEnd().getEdtfDatePart().isUnknown());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//
-// private static Stream timeIntervalWithUnknownStart() {
-// return Stream.of(
-// Arguments.of("/1998-11-12", 1998, 11, 12, false, false, true),
-// Arguments.of("/1998-11", 1998, 11, null, false, false, true),
-// Arguments.of("/1998", 1998, null, null, false, false, true),
-// Arguments.of("/1998-11-12~", 1998, 11, 12, true, false, true),
-// Arguments.of("/1998-11~", 1998, 11, null, true, false, true),
-// Arguments.of("/1998~", 1998, null, null, true, false, true),
-// Arguments.of("/1998-11-12?", 1998, 11, 12, false, true, true),
-// Arguments.of("/1998-11?", 1998, 11, null, false, true, true),
-// Arguments.of("/1998?", 1998, null, null, false, true, true),
-// Arguments.of("/1998-11-12%", 1998, 11, 12, true, true, true),
-// Arguments.of("/1998-11%", 1998, 11, null, true, true, true),
-// Arguments.of("/1998%", 1998, null, null, true, true, true),
-// Arguments.of(" / 1998-11-12", 1998, 11, 12, false, false, false),
-// Arguments.of("/ 1998-11-12", 1998, 11, 12, false, false, false),
-// Arguments.of(" /1998-11-12", 1998, 11, 12, false, false, false));
-// }
-//
-// @ParameterizedTest
-// @MethodSource("timeIntervalWithUnknownStart")
-// @DisplayName("Time interval with unknown start")
-// void testTimeIntervalWithUnknownStart(String actualDate,
-// Integer expectedYear,
-// Integer expectedMonth,
-// Integer expectedDay,
-// Boolean expectedIsApproximate,
-// Boolean expectedIsUncertain,
-// Boolean isSuccess) throws ParseException {
-// if (isSuccess) {
-// IntervalEdtfDate actual = (IntervalEdtfDate) parser.parse(actualDate);
-//
-// assertEquals(expectedYear, actual.getEnd().getEdtfDatePart().getYear());
-// assertEquals(expectedMonth, actual.getEnd().getEdtfDatePart().getMonth());
-// assertEquals(expectedDay, actual.getEnd().getEdtfDatePart().getDay());
-// assertEquals(expectedIsApproximate, actual.getEnd().isApproximate());
-// assertEquals(expectedIsUncertain, actual.getEnd().isUncertain());
-// assertTrue(actual.getStart().getEdtfDatePart().isUnknown());
-// } else {
-// assertThrows(ParseException.class, () -> parser.parse(actualDate));
-// }
-// }
-//}
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/EdtfParseAndSerializeTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/EdtfParseAndSerializeTest.java
deleted file mode 100644
index adf6d60313..0000000000
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/EdtfParseAndSerializeTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package eu.europeana.normalization.dates.edtf;
-
-import java.text.ParseException;
-import org.junit.jupiter.api.Test;
-
-public class EdtfParseAndSerializeTest {
-
- EdtfParser parser = new EdtfParser();
-
- @Test
- void parseDate() throws ParseException {
- String dateStr = "2004-01-01";
- InstantEdtfDate parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().getYear() == 2004);
- assert (parse.getEdtfDatePart().getMonth() == 1);
- assert (parse.getEdtfDatePart().getDay() == 1);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "2004-01";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().getYear() == 2004);
- assert (parse.getEdtfDatePart().getMonth() == 1);
- assert (parse.getEdtfDatePart().getDay() == null);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "2004";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().getYear() == 2004);
- assert (parse.getEdtfDatePart().getMonth() == null);
- assert (parse.getEdtfDatePart().getDay() == null);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "2004-01?";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().isUncertain());
- assert (parse.getEdtfDatePart().getYear() == 2004);
- assert (parse.getEdtfDatePart().getMonth() == 1);
- assert (parse.getEdtfDatePart().getDay() == null);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "2004~";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().isApproximate());
- assert (parse.getEdtfDatePart().getYear() == 2004);
- assert (parse.getEdtfDatePart().getMonth() == null);
- assert (parse.getEdtfDatePart().getDay() == null);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "2004-01-01%";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().isUncertain());
- assert (parse.getEdtfDatePart().isApproximate());
- assert (parse.getEdtfDatePart().getYear() == 2004);
- assert (parse.getEdtfDatePart().getMonth() == 1);
- assert (parse.getEdtfDatePart().getDay() == 1);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "Y-200000";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().getYear() == -200000);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "Y200000";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().getYear() == 200000);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "..";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().isUnspecified());
- assert (parse.getEdtfDatePart().getYear() == null);
- assert (parse.getEdtfDatePart().getMonth() == null);
- assert (parse.getEdtfDatePart().getDay() == null);
- assert (parse.toString().equals(dateStr));
- }
-
- @Test
- void parseTime() throws ParseException {
- String dateStr = "2004-01-01T23:05:02";
- String resultString = "2004-01-01";
- InstantEdtfDate parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.getEdtfDatePart().getYear() == 2004);
- assert (parse.getEdtfDatePart().getMonth() == 1);
- assert (parse.getEdtfDatePart().getDay() == 1);
- assert (parse.toString().equals(resultString));
-
- dateStr = "2004-01-01T23:05";
- resultString = "2004-01-01";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.toString().equals(resultString));
-
- dateStr = "2004-01-01T23";
- resultString = "2004-01-01";
- parse = (InstantEdtfDate) parser.parse(dateStr);
- assert (parse.toString().equals(resultString));
- }
-
- @Test
- void parseInterval() throws ParseException {
- String dateStr = "2004-01-01/2004-01-02";
- IntervalEdtfDate parse = (IntervalEdtfDate) parser.parse(dateStr);
- assert (parse.getStart().getEdtfDatePart().getYear() == 2004);
- assert (parse.getStart().getEdtfDatePart().getMonth() == 1);
- assert (parse.getStart().getEdtfDatePart().getDay() == 1);
- assert (parse.getEnd().getEdtfDatePart().getYear() == 2004);
- assert (parse.getEnd().getEdtfDatePart().getMonth() == 1);
- assert (parse.getEnd().getEdtfDatePart().getDay() == 2);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "2004-01-01/2005";
- parse = (IntervalEdtfDate) parser.parse(dateStr);
- assert (parse.getStart().getEdtfDatePart().getYear() == 2004);
- assert (parse.getStart().getEdtfDatePart().getMonth() == 1);
- assert (parse.getStart().getEdtfDatePart().getDay() == 1);
- assert (parse.getEnd().getEdtfDatePart().getYear() == 2005);
- assert (parse.getEnd().getEdtfDatePart().getMonth() == null);
- assert (parse.getEnd().getEdtfDatePart().getDay() == null);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "2004/2005";
- parse = (IntervalEdtfDate) parser.parse(dateStr);
- assert (parse.getStart().getEdtfDatePart().getYear() == 2004);
- assert (parse.getStart().getEdtfDatePart().getMonth() == null);
- assert (parse.getStart().getEdtfDatePart().getDay() == null);
- assert (parse.getEnd().getEdtfDatePart().getYear() == 2005);
- assert (parse.getEnd().getEdtfDatePart().getMonth() == null);
- assert (parse.getEnd().getEdtfDatePart().getDay() == null);
- assert (parse.toString().equals(dateStr));
-
- dateStr = "2004?/2005~";
- parse = (IntervalEdtfDate) parser.parse(dateStr);
- assert (parse.getStart().getEdtfDatePart().getYear() == 2004);
- assert (parse.getStart().getEdtfDatePart().getMonth() == null);
- assert (parse.getStart().getEdtfDatePart().getDay() == null);
- assert (parse.getStart().getEdtfDatePart().isUncertain());
- assert (parse.getEnd().getEdtfDatePart().getYear() == 2005);
- assert (parse.getEnd().getEdtfDatePart().getMonth() == null);
- assert (parse.getEnd().getEdtfDatePart().getDay() == null);
- assert (parse.getEnd().getEdtfDatePart().isApproximate());
- assert (parse.toString().equals(dateStr));
- }
-}
\ No newline at end of file
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/InstantEdtfDateTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/InstantEdtfDateTest.java
new file mode 100644
index 0000000000..76feb1e5cc
--- /dev/null
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/InstantEdtfDateTest.java
@@ -0,0 +1,45 @@
+package eu.europeana.normalization.dates.edtf;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.of;
+
+import eu.europeana.normalization.dates.YearPrecision;
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class InstantEdtfDateTest {
+
+ @ParameterizedTest
+ @MethodSource
+ void getFirstDay(InstantEdtfDate input, String expectedCentury, String expectedFirstDayFirstDayCentury,
+ String expectedFirstDayLastDayCentury,
+ String expectedFirstDay, String expectedLastDay) {
+ InstantEdtfDate firstDay = input.getFirstDay();
+ InstantEdtfDate lastDay = input.getLastDay();
+ assertEquals(expectedFirstDay, firstDay.toString());
+ assertEquals(expectedLastDay, lastDay.toString());
+ assertEquals(expectedCentury, input.getCentury().toString());
+ assertEquals(expectedFirstDayFirstDayCentury, firstDay.getCentury().toString());
+ assertEquals(expectedFirstDayLastDayCentury, lastDay.getCentury().toString());
+ }
+
+ private static Stream getFirstDay() throws DateExtractionException {
+ return Stream.of(
+ of(new InstantEdtfDateBuilder(2011).build(), "21", "21", "21", "2011-01-01", "2011-12-31"),
+ of(new InstantEdtfDateBuilder(1782).build(), "18", "18", "18", "1782-01-01", "1782-12-31"),
+ of(new InstantEdtfDateBuilder(1804).build(), "19", "19", "19", "1804-01-01", "1804-12-31"),
+ of(new InstantEdtfDateBuilder(19).withYearPrecision(YearPrecision.CENTURY).build(),
+ "19", "20", "20", "1901-01-01", "2000-12-31"),
+ of(new InstantEdtfDateBuilder(198).withYearPrecision(YearPrecision.DECADE).build(),
+ "20", "20", "20", "1980-01-01", "1989-12-31"),
+ of(new InstantEdtfDateBuilder(190).withYearPrecision(YearPrecision.DECADE).build(),
+ "19", "19", "20", "1900-01-01", "1909-12-31"),
+ of(new InstantEdtfDateBuilder(2018).withMonth(10).withDay(11).build(), "21", "21", "21", "2018-10-11", "2018-10-11"),
+ of(new InstantEdtfDateBuilder(1989).withMonth(11).build(), "20", "20", "20", "1989-11-01", "1989-11-30")
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/Iso8601ParserTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/Iso8601ParserTest.java
new file mode 100644
index 0000000000..4a78f41d4f
--- /dev/null
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/edtf/Iso8601ParserTest.java
@@ -0,0 +1,121 @@
+package eu.europeana.normalization.dates.edtf;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.of;
+
+import eu.europeana.normalization.dates.extraction.DateExtractionException;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class Iso8601ParserTest {
+
+ private final Iso8601Parser iso8601Parser = new Iso8601Parser();
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("[year][“-”][month][“-”][day] Complete representation")
+ void completeDateRepresentation(String input, String expected) throws DateExtractionException {
+ assertParse(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("[year][“-”][month] Reduced precision for year and month")
+ void reducedPrecisionForYearAndMonth(String input, String expected) throws DateExtractionException {
+ assertParse(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("[year] Reduced precision for year")
+ void reducedPrecisionForYear(String input, String expected) throws DateExtractionException {
+ assertParse(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void dateAndTimeRepresentation(String input, String expected) throws DateExtractionException {
+ assertParse(input, expected);
+ }
+
+ private void assertParse(String input, String expected) throws DateExtractionException {
+ if (expected == null) {
+ assertThrows(DateExtractionException.class, () -> iso8601Parser.parseDatePart(input));
+ } else {
+ assertEquals(expected, iso8601Parser.temporalAccessorToString(iso8601Parser.parseDatePart(input)));
+ }
+ }
+
+ private static Stream completeDateRepresentation() {
+ return Stream.of(
+ of("1989-11-01", "1989-11-01"),
+ of("0989-11-01", "0989-11-01"),
+ of("0989-11-01", "0989-11-01"),
+ //Digits missing on year
+ of("198-11-01", null),
+ //Digits missing on month or day
+ of("1989-11-1", null),
+ of("1989-1-01", null),
+ //Anything other than hyphen "-" is not valid
+ of("1989/11/01", null)
+ );
+ }
+
+ private static Stream reducedPrecisionForYearAndMonth() {
+ return Stream.of(
+ of("1989-11", "1989-11"),
+ of("0989-11", "0989-11"),
+ //Digits missing on year
+ of("198-11", null),
+ //Digits missing on month
+ of("1989-1", null),
+ //Anything other than hyphen "-" is not valid
+ of("1989/11", null)
+ );
+ }
+
+ private static Stream reducedPrecisionForYear() {
+ return Stream.of(
+ of("1989", "1989"),
+ of("0989", "0989"),
+ //Digits missing on year
+ of("198", null)
+ );
+ }
+
+ private static Stream dateAndTimeRepresentation() {
+ return Stream.of(
+ //Complete representations for calendar date and (local) time of day
+ of("1989-11-01T23:59:59", "1989-11-01"),
+ of("1989-11-01T23:59", "1989-11-01"),
+ of("1989-11-01T23", "1989-11-01"),
+ of("1989-11-01T", "1989-11-01"),
+ of("1989-11-01T23:59:5", "1989-11-01"),
+ of("1989-11-01T23:5:59", "1989-11-01"),
+ of("1989-11-01t23:59:59", null),
+ of("1989-11-01 23:59:59", null),
+
+ //Complete representations for calendar date and UTC time of day
+ of("1989-11-01T23:59:59Z", "1989-11-01"),
+ of("1989-11-01t23:59:59Z", null),
+ of("1989-11-01 23:59:59Z", null),
+
+ //Date and time with time shift in hours (only)
+ of("1989-11-01T23:59:59-04", "1989-11-01"),
+ of("1989-11-01T23:59:59+04", "1989-11-01"),
+ of("1989-11-01t23:59:59-04", null),
+ of("1989-11-01 23:59:59-04", null),
+
+ //Date and time with time shift in hours and minutes
+ of("1989-11-01T23:59:59-04:44", "1989-11-01"),
+ of("1989-11-01T23:59:59+04:44", "1989-11-01"),
+ of("1989-11-01t23:59:59-04:44", null),
+ of("1989-11-01 23:59:59-04:44", null)
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/EdtfDatePartNormalizerTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/EdtfDatePartNormalizerTest.java
deleted file mode 100644
index 2b2b4b799d..0000000000
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/EdtfDatePartNormalizerTest.java
+++ /dev/null
@@ -1,218 +0,0 @@
-package eu.europeana.normalization.dates.extraction;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
-import eu.europeana.normalization.dates.DateNormalizationResult;
-import eu.europeana.normalization.normalizers.DatesNormalizer;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-import org.junit.jupiter.api.Test;
-
-class EdtfDatePartNormalizerTest {
-
- final HashMap datePropertyTestCases = new HashMap<>();
- final HashMap genericPropertyTestCases = new HashMap<>();
-
- public EdtfDatePartNormalizerTest() {
- //DATE PROPERTY
- //DCMI
- datePropertyTestCases.put("name=Prehistoric Period; end=-5300", "../-5300");
- datePropertyTestCases.put("Byzantine Period; start=0395; end=0641", "0395/0641");
- datePropertyTestCases.put("Modern era; start=1975;", "1975/..");
-
- //Centuries numeric
- datePropertyTestCases.put("18..", "18XX");
- datePropertyTestCases.put("19??", "19XX");
- datePropertyTestCases.put("192?", null);// ambiguous
- datePropertyTestCases.put("[171-]", null); // ambiguous
- datePropertyTestCases.put("19th century", "18XX");
- datePropertyTestCases.put("2nd century", "01XX");
- datePropertyTestCases.put("[10th century]", "09XX"); // not supported
- datePropertyTestCases.put("12th century BC", null); // not supported
-
- //Centuries roman
- datePropertyTestCases.put("XIV", "13XX");
- datePropertyTestCases.put("MDCLXX", null);
- datePropertyTestCases.put("MDCVII", null);
- datePropertyTestCases.put("S. XVI-XX", "15XX/19XX");
- datePropertyTestCases.put("S.VIII-XV", "07XX/14XX");
- datePropertyTestCases.put("S. XVI-XVIII", "15XX/17XX");
- datePropertyTestCases.put("S. XVIII-", null); // open-ended period
- datePropertyTestCases.put("[XVI-XIX]", "15XX/18XX");
- datePropertyTestCases.put("SVV", null);
-
- //Unknown/Unspecified start or end of range
- datePropertyTestCases.put("1907/?", "1907/..");
- datePropertyTestCases.put("?/1907", "../1907");
- datePropertyTestCases.put("1907/", "1907/..");
- datePropertyTestCases.put("/1907", "../1907");
-
- //Numeric range '/'
- datePropertyTestCases.put("1872-06-01/1872-06-30", "1872-06-01/1872-06-30");
- datePropertyTestCases.put(" 1820/1820", "1820/1820");
- datePropertyTestCases.put("1918 / 1919", "1918/1919");
- datePropertyTestCases.put("1205/1215 [Herstellung]", "1205/1215");
- datePropertyTestCases.put("2014/15", "2014/2015");
- datePropertyTestCases.put(" 1757/1757", "1757/1757");
- datePropertyTestCases.put("ca 1757/1757", "1757~/1757~");
- datePropertyTestCases.put("2000 vC - 2002 nC", "-2000/2002");
- datePropertyTestCases.put("0114 aC - 0113 aC", "-0114/-0113");
- datePropertyTestCases.put("0390 AD - 0425 AD", "0390/0425");
- datePropertyTestCases.put("337 BC - 283 BC", "-0337/-0283");
- datePropertyTestCases.put("100 vC - 150 nC", "-0100/0150");
- datePropertyTestCases.put("400 BC - 400 AD", "-0400/0400");
- datePropertyTestCases.put("235 AD – 236 AD", "0235/0236");
- datePropertyTestCases.put("168 B.C.-135 A.D.", "-0168/0135");
- datePropertyTestCases.put("20/09/18XX", "18XX-09-20");
- datePropertyTestCases.put("1889/98? (Herstellung)", "1889?/1898?");
- datePropertyTestCases.put("?/1807", "../1807");
- //Incorrect day values
- datePropertyTestCases.put("1947-19-50/1950-19-53", null);
- datePropertyTestCases.put("15/21-8-1918", null);
- datePropertyTestCases.put("1.1848/49[?]", null);
-
- //Numeric range ' - '(spaces around hyphen)
- datePropertyTestCases.put("1851-01-01 - 1851-12-31", "1851-01-01/1851-12-31");
- datePropertyTestCases.put("1650? - 1700?", "1650?/1700?");
- datePropertyTestCases.put("1871 - 191-", null);
-
- //Numeric range '-'
- datePropertyTestCases.put("1918-20", "1918/1920");
- datePropertyTestCases.put("[1942-1943]", "1942/1943");
- datePropertyTestCases.put("(1942-1943)", "1942/1943");
- datePropertyTestCases.put("192?-1958", null);
- datePropertyTestCases.put("[ca. 1920-1930]", "1920~/1930~");
- datePropertyTestCases.put("1937--1938", null);
- datePropertyTestCases.put("[ca. 193-]", null);// ambiguous
- datePropertyTestCases.put("1990-", null); // open ended period not supported
-
- //Numeric range '|'
- datePropertyTestCases.put("1910/05/31 | 1910/05/01", "1910-05-01/1910-05-31");
-
- //Numeric range ' '(space)
- datePropertyTestCases.put("1916-09-26 1916-09-28", "1916-09-26/1916-09-28");
- datePropertyTestCases.put("29-10-2009 29-10-2009", "2009-10-29/2009-10-29");
- // this may not be 100% correct, maybe it is not a range but two dates
- datePropertyTestCases.put("1939 [1942?]", "1939/1942?");
- // this may not be a 100% correct normalisation, maybe it is not a range but two dates
- datePropertyTestCases.put("1651 [ca. 1656]", "1651~/1656~");
-
- //Numeric year
- datePropertyTestCases.put("(17--?)", "17XX?");
- datePropertyTestCases.put("[19--?]", "19XX?");
-
- //Numeric date with dot "."
- datePropertyTestCases.put("21.1.1921", "1921-01-21");
- datePropertyTestCases.put("12.10.1690", "1690-10-12");
- datePropertyTestCases.put("26.4.1828", "1828-04-26");
- datePropertyTestCases.put("28.05.1969", "1969-05-28");
- datePropertyTestCases.put("11.11.1947", "1947-11-11");
- datePropertyTestCases.put("23.02.[18--]", "18XX-02-23");
- datePropertyTestCases.put("28. 1. 1240", null);
-
- //Numeric date with dash "-"
- datePropertyTestCases.put("1941-22-06", "1941-06-22");
- datePropertyTestCases.put("1937-10-??", "1937-10");
- datePropertyTestCases.put("199--09-28", null);
- datePropertyTestCases.put("01?-1905", null);
- datePropertyTestCases.put("02?-1915", null);
-
- //Numeric date with space " "
- datePropertyTestCases.put("1905 09 01", "1905-09-01");
- datePropertyTestCases.put("0 2 1980", "1980-02");
-
- //More than 4 digits year
- datePropertyTestCases.put("-701950/-251950", "Y-701950/Y-251950");
- datePropertyTestCases.put("-123456/-12345", "Y-123456/Y-12345");
- datePropertyTestCases.put("18720601/18720630", null);
- datePropertyTestCases.put("19471950/19501953", null);
-
- datePropertyTestCases.put("-2100/-1550", "-2100/-1550");
- // TODO: 21/12/2022 Check the below, expected null but returns 1952-02-25 instead
- // datePropertyTestCases.put("1952-02-25T00:00:00Z-1952-02-25T23:59:59Z", null);
- datePropertyTestCases.put("2013-09-07 09:31:51 UTC", "2013-09-07");
- datePropertyTestCases.put("1997-07-18T00:00:00 [Create]", "1997-07-18");
- datePropertyTestCases.put("1924 ca.", null);
- datePropertyTestCases.put("[1712?]", "1712?");
- datePropertyTestCases.put("circa 1712", "1712~");
- datePropertyTestCases.put("[ca. 1946]", "1946~");
- datePropertyTestCases.put("1651?]", "1651?");
- datePropertyTestCases.put("19--?]", "19XX?");
- datePropertyTestCases.put(". 1885", null);
- datePropertyTestCases.put("- 1885", null);
- datePropertyTestCases.put("1749 (Herstellung (Werk))", "1749");
- datePropertyTestCases.put("1939; 1954; 1955; 1978; 1939-1945", null); // multiple dates no suported
- datePropertyTestCases.put("[17__]", null);// this pattern is not supported (this pattern was never tested
- datePropertyTestCases.put("19--]", "19XX");
- datePropertyTestCases.put("19xx", "19XX");
- datePropertyTestCases.put("Sat Jan 01 01:00:00 CET 1701", "1701-01-01");
- datePropertyTestCases.put("2013-03-21 18:45:36 UTC", "2013-03-21");
- datePropertyTestCases.put("15.02.1985 (identification)", "1985-02-15");
- datePropertyTestCases.put("-500000", "Y-500000");
- datePropertyTestCases.put("091090", null);
- datePropertyTestCases.put("-0043-12-07", "-0043-12-07");
- datePropertyTestCases.put("imp. 1901", null);
- datePropertyTestCases.put("u.1707-1739", null);// what does 'u.' mean?
- datePropertyTestCases.put("22.07.1971 (identification)", "1971-07-22");
-
- //Ambiguous pattern
- datePropertyTestCases.put("187-?]", null);
-
- datePropertyTestCases.put("18. September 1914", "1914-09-18");
- datePropertyTestCases.put("19960216-19960619", null);
- datePropertyTestCases.put("-0549-01-01T00:00:00Z", "-0549-01-01");
- datePropertyTestCases.put("1942-1943 c.", null);
- datePropertyTestCases.put("(1942)", "1942");
- datePropertyTestCases.put("-3.6982", null);
- datePropertyTestCases.put("[ca. 16??]", "16XX~");
- datePropertyTestCases.put("ISO9126", null);
- datePropertyTestCases.put("1985-10-xx", "1985-10");
- datePropertyTestCases.put("14:27", null);
- datePropertyTestCases.put("c.6 Nov 1902", "1902-11-06~");
- datePropertyTestCases.put("-1234", "-1234");
- datePropertyTestCases.put("09.1972 (gathering)", "1972-09");
-
- //GENERIC PROPERTY
- genericPropertyTestCases.put("XIV", null);
- genericPropertyTestCases.put("1905 09 01", "1905-09-01");
- genericPropertyTestCases.put("1851-01-01 - 1851-12-31", "1851-01-01/1851-12-31");
- genericPropertyTestCases.put("18..", null);
- genericPropertyTestCases.put("2013-09-07 09:31:51 UTC", "2013-09-07");
- genericPropertyTestCases.put("1918 / 1919", "1918/1919");
- genericPropertyTestCases.put("1205/1215 [Herstellung]", null);
- genericPropertyTestCases.put("1997-07", null);
- genericPropertyTestCases.put("19??", null);
- genericPropertyTestCases.put("1871 - 191-", null);
- }
-
- @Test
- void extractorsTest() {
- DatesNormalizer normaliser = new DatesNormalizer();
- verifyNormalizations(datePropertyTestCases, normaliser::normalizeDateProperty);
- verifyNormalizations(genericPropertyTestCases, normaliser::normalizeGenericProperty);
- }
-
- private void verifyNormalizations(Map testCases, Function normaliserFunction) {
- DateNormalizationResult dateNormalizationResult;
- for (String testCase : testCases.keySet()) {
- dateNormalizationResult = normaliserFunction.apply(testCase);
- if (dateNormalizationResult.getDateNormalizationExtractorMatchId() == DateNormalizationExtractorMatchId.NO_MATCH
- || dateNormalizationResult.getDateNormalizationExtractorMatchId() == DateNormalizationExtractorMatchId.INVALID) {
- assertNull(testCases.get(testCase), "Test case '" + testCase
- + "' was a no-match but should be normalised to '" + testCases.get(testCase) + "'");
- } else {
- String edtfStr = dateNormalizationResult.getEdtfDate().toString();
- assertEquals(testCases.get(testCase), edtfStr, "Test case '" + testCase + "'");
- if (dateNormalizationResult.getDateNormalizationExtractorMatchId() == DateNormalizationExtractorMatchId.DCMI_PERIOD) {
- if (dateNormalizationResult.getEdtfDate().getLabel() != null) {
- assertTrue(testCase.contains(dateNormalizationResult.getEdtfDate().getLabel()));
- }
- }
- }
- }
- }
-}
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/RomanToNumberTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/RomanToNumberTest.java
index c1d0d364ed..ed71007ee8 100644
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/RomanToNumberTest.java
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/RomanToNumberTest.java
@@ -1,9 +1,10 @@
package eu.europeana.normalization.dates.extraction;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.params.provider.Arguments.of;
import java.util.stream.Stream;
-import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -14,69 +15,70 @@
class RomanToNumberTest {
private static Stream numberData() {
- return Stream.of(Arguments.of("I", 1, true),
- Arguments.of("II", 2, true),
- Arguments.of("III", 3, true),
+ return Stream.of(
+ of("I", 1, true),
+ of("II", 2, true),
+ of("III", 3, true),
//Arguments.of("IIII", 4, false), //TODO: this is not valid
- Arguments.of("IV", 4, true),
- Arguments.of("V", 5, true),
- Arguments.of("VI", 6, true),
- Arguments.of("VII", 7, true),
+ of("IV", 4, true),
+ of("V", 5, true),
+ of("VI", 6, true),
+ of("VII", 7, true),
//Arguments.of("VIII", 8, true), //TODO: this is not valid
- Arguments.of("IX", 9, true),
- Arguments.of("X", 10, true),
+ of("IX", 9, true),
+ of("X", 10, true),
//Arguments.of("VV", 10, false), //TODO: this is not valid
- Arguments.of("XI", 11, true),
- Arguments.of("XII", 12, true),
- Arguments.of("XIII", 13, true),
- Arguments.of("XIV", 14, true),
+ of("XI", 11, true),
+ of("XII", 12, true),
+ of("XIII", 13, true),
+ of("XIV", 14, true),
//Arguments.of("XIIII", 14, false), //TODO: this is not valid
- Arguments.of("XV", 15, true),
- Arguments.of("XVI", 16, true),
- Arguments.of("XVII", 17, true),
- Arguments.of("XVIII", 18, true),
- Arguments.of("XIX", 19, true),
- Arguments.of("XX", 20, true),
- Arguments.of("XXI", 21, true),
+ of("XV", 15, true),
+ of("XVI", 16, true),
+ of("XVII", 17, true),
+ of("XVIII", 18, true),
+ of("XIX", 19, true),
+ of("XX", 20, true),
+ of("XXI", 21, true),
//Arguments.of("XVVI", 21, true), //TODO: this is not valid
- Arguments.of("XXII", 22, true),
- Arguments.of("XXIII", 23, true),
- Arguments.of("XXIV", 24, true),
- Arguments.of("XXV", 25, true),
- Arguments.of("XXVI", 26, true),
- Arguments.of("XXVII", 27, true),
- Arguments.of("XXVIII", 28, true),
- Arguments.of("XXIX", 29, true),
- Arguments.of("XXX", 30, true),
- Arguments.of("XXXI", 31, true),
- Arguments.of("XXXIV", 34, true),
- Arguments.of("XXXV", 35, true),
+ of("XXII", 22, true),
+ of("XXIII", 23, true),
+ of("XXIV", 24, true),
+ of("XXV", 25, true),
+ of("XXVI", 26, true),
+ of("XXVII", 27, true),
+ of("XXVIII", 28, true),
+ of("XXIX", 29, true),
+ of("XXX", 30, true),
+ of("XXXI", 31, true),
+ of("XXXIV", 34, true),
+ of("XXXV", 35, true),
//Arguments.of("VXL", 35, false), //TODO: this is not valid
- Arguments.of("XL", 40, true),
- Arguments.of("XLI", 41, true),
+ of("XL", 40, true),
+ of("XLI", 41, true),
//Arguments.of("XXXXI", 41, false), //TODO: this is not valid
- Arguments.of("XLII", 42, true),
- Arguments.of("XLIII", 43, true),
- Arguments.of("XLIV", 44, true),
- Arguments.of("XLV", 45, true),
- Arguments.of("XLVI", 46, true),
- Arguments.of("XLVII", 47, true),
- Arguments.of("XLVIII", 48, true),
+ of("XLII", 42, true),
+ of("XLIII", 43, true),
+ of("XLIV", 44, true),
+ of("XLV", 45, true),
+ of("XLVI", 46, true),
+ of("XLVII", 47, true),
+ of("XLVIII", 48, true),
//Arguments.of("XLIVV", 49, false), //TODO: this is not valid
- Arguments.of("XLIX", 49, true),
- Arguments.of("L", 50, true),
- Arguments.of("LI", 51, true),
- Arguments.of("LII", 52, true),
- Arguments.of("MCMLXXVI", 1976, true),
- Arguments.of("MCMXCVIII", 1998, true),
- Arguments.of("MMXXII", 2022, true)
+ of("XLIX", 49, true),
+ of("L", 50, true),
+ of("LI", 51, true),
+ of("LII", 52, true),
+ of("MCMLXXVI", 1976, true),
+ of("MCMXCVIII", 1998, true),
+ of("MMXXII", 2022, true)
);
}
@ParameterizedTest
@MethodSource("numberData")
void romanToDecimal(String romanNumber, Integer expectedNumber, Boolean isSuccess) {
- if(isSuccess) {
+ if (isSuccess) {
assertEquals(expectedNumber, RomanToNumber.romanToDecimal(romanNumber));
} else {
assertNotEquals(expectedNumber, RomanToNumber.romanToDecimal(romanNumber));
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/BriefRangeDateExtractorTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/BriefRangeDateExtractorTest.java
new file mode 100644
index 0000000000..4d0334b4be
--- /dev/null
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/BriefRangeDateExtractorTest.java
@@ -0,0 +1,88 @@
+package eu.europeana.normalization.dates.extraction.dateextractors;
+
+import static eu.europeana.normalization.dates.edtf.DateBoundaryType.OPEN;
+import static eu.europeana.normalization.dates.edtf.DateBoundaryType.UNKNOWN;
+import static eu.europeana.normalization.dates.edtf.DateQualification.APPROXIMATE;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
+import static eu.europeana.normalization.dates.edtf.DateQualification.UNCERTAIN;
+import static eu.europeana.normalization.dates.edtf.DateQualification.UNCERTAIN_APPROXIMATE;
+import static eu.europeana.normalization.dates.edtf.IntervalEdtfDate.DATE_INTERVAL_SEPARATOR;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.of;
+
+import eu.europeana.normalization.dates.DateNormalizationResult;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
+import eu.europeana.normalization.dates.edtf.AbstractEdtfDate;
+import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
+import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class BriefRangeDateExtractorTest {
+
+ private final BriefRangeDateExtractor briefRangeDateExtractor = new BriefRangeDateExtractor();
+
+ private void assertExtract(String input, String expected) {
+ final DateNormalizationResult dateNormalizationResult = briefRangeDateExtractor.extractDateProperty(input, NO_QUALIFICATION);
+ if (expected == null) {
+ assertEquals(DateNormalizationResultStatus.NO_MATCH, dateNormalizationResult.getDateNormalizationResultStatus());
+ } else {
+ AbstractEdtfDate edtfDate = dateNormalizationResult.getEdtfDate();
+ if (edtfDate instanceof IntervalEdtfDate) {
+ String startPart = expected.substring(0, expected.indexOf(DATE_INTERVAL_SEPARATOR));
+ String endPart = expected.substring(expected.indexOf(DATE_INTERVAL_SEPARATOR) + 1);
+ InstantEdtfDate start = ((IntervalEdtfDate) edtfDate).getStart();
+ InstantEdtfDate end = ((IntervalEdtfDate) edtfDate).getEnd();
+ assertEdtfDate(startPart, start);
+ assertEdtfDate(endPart, end);
+ } else {
+ assertEdtfDate(expected, (InstantEdtfDate) dateNormalizationResult.getEdtfDate());
+ }
+ assertEquals(expected, edtfDate.toString());
+ }
+ }
+
+ private static void assertEdtfDate(String expected, InstantEdtfDate instantEdtfDate) {
+ assertEquals(expected.contains("?"), instantEdtfDate.getDateQualification() == UNCERTAIN);
+ assertEquals(expected.contains("~"), instantEdtfDate.getDateQualification() == APPROXIMATE);
+ assertEquals(expected.contains("%"), instantEdtfDate.getDateQualification() == UNCERTAIN_APPROXIMATE);
+ assertEquals(expected.equals(OPEN.getSerializedRepresentation()),
+ instantEdtfDate.getDateBoundaryType() == OPEN || instantEdtfDate.getDateBoundaryType() == UNKNOWN);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void extractBrief(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+ private static Stream extractBrief() {
+ return Stream.of(
+ of("1989/90", "1989/1990"),
+ of("1989/90?", "1989?/1990?"),
+ of("1989-90", "1989/1990"),
+ of("1989-90?", "1989?/1990?"),
+ of("1900-13", "1900/1913"),
+
+ //End date lower rightmost two digits than start year
+ of("1989/89", null),
+ of("1989/88", null),
+ of("1989-89", null),
+ of("1989-88", null),
+
+ //More than two digits on end year not allowed
+ of("1989/990", null),
+ of("1989-990", null),
+
+ //End year cannot be lower or equal than 12
+ of("1900/01", null),
+ of("1900-12", null),
+
+ //Less than three digits on start year
+ of("89-90", null)
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/CenturyDateExtractorTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/CenturyDateExtractorTest.java
index d72caf9218..1a8df29669 100644
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/CenturyDateExtractorTest.java
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/CenturyDateExtractorTest.java
@@ -3,28 +3,31 @@
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.CENTURY_NUMERIC;
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.CENTURY_RANGE_ROMAN;
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.CENTURY_ROMAN;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.params.provider.Arguments.of;
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
class CenturyDateExtractorTest {
-
private static final CenturyDateExtractor CENTURY_DATE_EXTRACTOR = new CenturyDateExtractor();
- void extract(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
- final DateNormalizationResult dateNormalizationResult = CENTURY_DATE_EXTRACTOR.extract(input);
+ void assertExtract(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
+ final DateNormalizationResult dateNormalizationResult = CENTURY_DATE_EXTRACTOR.extractDateProperty(input, NO_QUALIFICATION);
if (expected == null) {
- assertNull(dateNormalizationResult);
+ assertEquals(DateNormalizationResultStatus.NO_MATCH, dateNormalizationResult.getDateNormalizationResultStatus());
} else {
final String actual = dateNormalizationResult.getEdtfDate().toString();
assertEquals(expected, actual);
- assertEquals(actual.contains("?"), dateNormalizationResult.getEdtfDate().isUncertain());
+ assertEquals(actual.contains("?"),
+ dateNormalizationResult.getEdtfDate().getDateQualification() == DateQualification.UNCERTAIN);
assertEquals(dateNormalizationExtractorMatchId, dateNormalizationResult.getDateNormalizationExtractorMatchId());
}
}
@@ -32,44 +35,44 @@ void extract(String input, String expected, DateNormalizationExtractorMatchId da
@ParameterizedTest
@MethodSource("extractNumericData")
void extractNumeric(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
- extract(input, expected, dateNormalizationExtractorMatchId);
+ assertExtract(input, expected, dateNormalizationExtractorMatchId);
}
@ParameterizedTest
@MethodSource("extractRomanData")
void extractRoman(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
- extract(input, expected, dateNormalizationExtractorMatchId);
+ assertExtract(input, expected, dateNormalizationExtractorMatchId);
}
private static Stream extractNumericData() {
return Stream.of(
//PATTERN_YYYY
- Arguments.of("18..", "18XX", CENTURY_NUMERIC),
- Arguments.of(" 18.. ", "18XX", CENTURY_NUMERIC),
- Arguments.of("?18..", "18XX?", CENTURY_NUMERIC),
- Arguments.of("18..?", "18XX?", CENTURY_NUMERIC),
- Arguments.of("?18..?", "18XX?", CENTURY_NUMERIC),
- Arguments.of("192?", null, null, null), //Too many digits
- Arguments.of("1..", null, null, null), //Too few digits
+ of("18..", "18XX", CENTURY_NUMERIC),
+ of(" 18.. ", "18XX", CENTURY_NUMERIC),
+ of("?18..", "18XX?", CENTURY_NUMERIC),
+ of("18..?", "18XX?", CENTURY_NUMERIC),
+ of("?18..?", "18XX?", CENTURY_NUMERIC),
+ of("192?", null, null, null), //Too many digits
+ of("1..", null, null, null), //Too few digits
//PATTERN_ENGLISH
- Arguments.of("1st century", "00XX", CENTURY_NUMERIC),
- Arguments.of("2nd century", "01XX", CENTURY_NUMERIC),
- Arguments.of("3rd century", "02XX", CENTURY_NUMERIC),
- Arguments.of("11th century", "10XX", CENTURY_NUMERIC),
- Arguments.of(" 11th century ", "10XX", CENTURY_NUMERIC),
- Arguments.of("?11th century", "10XX?", CENTURY_NUMERIC),
- Arguments.of("11th century?", "10XX?", CENTURY_NUMERIC),
- Arguments.of("?11th century?", "10XX?", CENTURY_NUMERIC),
- Arguments.of("12th century BC", null, null, null), // not supported
- Arguments.of("[10th century]", null, null, null), // not supported
- Arguments.of("11thcentury", null, null, null), //Incorrect spacing numeric
- Arguments.of("11st century", null, null, null), //Incorrect suffix
- Arguments.of("12rd century", null, null, null), //Incorrect suffix
- Arguments.of("13st century", null, null, null), //Incorrect suffix
- Arguments.of("21th century", null, null, null), //Incorrect suffix
- Arguments.of("0st century", null, null, null), //Out of range
- Arguments.of("22nd century", null, null, null) //Out of range
+ of("1st century", "00XX", CENTURY_NUMERIC),
+ of("2nd century", "01XX", CENTURY_NUMERIC),
+ of("3rd century", "02XX", CENTURY_NUMERIC),
+ of("11th century", "10XX", CENTURY_NUMERIC),
+ of(" 11th century ", "10XX", CENTURY_NUMERIC),
+ of("?11th century", "10XX?", CENTURY_NUMERIC),
+ of("11th century?", "10XX?", CENTURY_NUMERIC),
+ of("?11th century?", "10XX?", CENTURY_NUMERIC),
+ of("12th century BC", null, null, null), // not supported
+ of("[10th century]", null, null, null), // not supported
+ of("11thcentury", null, null, null), //Incorrect spacing numeric
+ of("11st century", null, null, null), //Incorrect suffix
+ of("12rd century", null, null, null), //Incorrect suffix
+ of("13st century", null, null, null), //Incorrect suffix
+ of("21th century", null, null, null), //Incorrect suffix
+ of("0st century", null, null, null), //Out of range
+ of("22nd century", null, null, null) //Out of range
);
}
@@ -77,136 +80,136 @@ private static Stream extractRomanData() {
return Stream.of(
//PATTERN_ROMAN
//Uppercase
- Arguments.of("I", "00XX", CENTURY_ROMAN),
- Arguments.of("IV", "03XX", CENTURY_ROMAN),
- Arguments.of("V", "04XX", CENTURY_ROMAN),
- Arguments.of("VI", "05XX", CENTURY_ROMAN),
- Arguments.of("IX", "08XX", CENTURY_ROMAN),
- Arguments.of("X", "09XX", CENTURY_ROMAN),
- Arguments.of("XI", "10XX", CENTURY_ROMAN),
- Arguments.of("XIV", "13XX", CENTURY_ROMAN),
- Arguments.of("XV", "14XX", CENTURY_ROMAN),
- Arguments.of("XVI", "15XX", CENTURY_ROMAN),
- Arguments.of("XIX", "18XX", CENTURY_ROMAN),
- Arguments.of("XX", "19XX", CENTURY_ROMAN),
- Arguments.of("XXI", "20XX", CENTURY_ROMAN),
+ of("I", "00XX", CENTURY_ROMAN),
+ of("IV", "03XX", CENTURY_ROMAN),
+ of("V", "04XX", CENTURY_ROMAN),
+ of("VI", "05XX", CENTURY_ROMAN),
+ of("IX", "08XX", CENTURY_ROMAN),
+ of("X", "09XX", CENTURY_ROMAN),
+ of("XI", "10XX", CENTURY_ROMAN),
+ of("XIV", "13XX", CENTURY_ROMAN),
+ of("XV", "14XX", CENTURY_ROMAN),
+ of("XVI", "15XX", CENTURY_ROMAN),
+ of("XIX", "18XX", CENTURY_ROMAN),
+ of("XX", "19XX", CENTURY_ROMAN),
+ of("XXI", "20XX", CENTURY_ROMAN),
//Lower case
- Arguments.of("i", "00XX", CENTURY_ROMAN),
- Arguments.of("iv", "03XX", CENTURY_ROMAN),
- Arguments.of("v", "04XX", CENTURY_ROMAN),
- Arguments.of("vi", "05XX", CENTURY_ROMAN),
- Arguments.of("ix", "08XX", CENTURY_ROMAN),
- Arguments.of("x", "09XX", CENTURY_ROMAN),
- Arguments.of("xi", "10XX", CENTURY_ROMAN),
- Arguments.of("xiv", "13XX", CENTURY_ROMAN),
- Arguments.of("xv", "14XX", CENTURY_ROMAN),
- Arguments.of("xvi", "15XX", CENTURY_ROMAN),
- Arguments.of("xix", "18XX", CENTURY_ROMAN),
- Arguments.of("xx", "19XX", CENTURY_ROMAN),
- Arguments.of("xxi", "20XX", CENTURY_ROMAN),
+ of("i", "00XX", CENTURY_ROMAN),
+ of("iv", "03XX", CENTURY_ROMAN),
+ of("v", "04XX", CENTURY_ROMAN),
+ of("vi", "05XX", CENTURY_ROMAN),
+ of("ix", "08XX", CENTURY_ROMAN),
+ of("x", "09XX", CENTURY_ROMAN),
+ of("xi", "10XX", CENTURY_ROMAN),
+ of("xiv", "13XX", CENTURY_ROMAN),
+ of("xv", "14XX", CENTURY_ROMAN),
+ of("xvi", "15XX", CENTURY_ROMAN),
+ of("xix", "18XX", CENTURY_ROMAN),
+ of("xx", "19XX", CENTURY_ROMAN),
+ of("xxi", "20XX", CENTURY_ROMAN),
//Prefixes
- Arguments.of("s I", "00XX", CENTURY_ROMAN),
- Arguments.of("s. I", "00XX", CENTURY_ROMAN),
- Arguments.of("S I", "00XX", CENTURY_ROMAN),
- Arguments.of("S.I", "00XX", CENTURY_ROMAN),
- Arguments.of("sec.I", "00XX", CENTURY_ROMAN),
- Arguments.of("SEC.I", "00XX", CENTURY_ROMAN),
- Arguments.of("sec. I", "00XX", CENTURY_ROMAN),
- Arguments.of("SEC. I", "00XX", CENTURY_ROMAN),
- Arguments.of("saec.I", "00XX", CENTURY_ROMAN),
- Arguments.of("SAEC.I", "00XX", CENTURY_ROMAN),
- Arguments.of("saec. I", "00XX", CENTURY_ROMAN),
- Arguments.of("SAEC. I", "00XX", CENTURY_ROMAN),
+ of("s I", "00XX", CENTURY_ROMAN),
+ of("s. I", "00XX", CENTURY_ROMAN),
+ of("S I", "00XX", CENTURY_ROMAN),
+ of("S.I", "00XX", CENTURY_ROMAN),
+ of("sec.I", "00XX", CENTURY_ROMAN),
+ of("SEC.I", "00XX", CENTURY_ROMAN),
+ of("sec. I", "00XX", CENTURY_ROMAN),
+ of("SEC. I", "00XX", CENTURY_ROMAN),
+ of("saec.I", "00XX", CENTURY_ROMAN),
+ of("SAEC.I", "00XX", CENTURY_ROMAN),
+ of("saec. I", "00XX", CENTURY_ROMAN),
+ of("SAEC. I", "00XX", CENTURY_ROMAN),
//Other possibilities and uncertain
- Arguments.of("Ii", "01XX", CENTURY_ROMAN),
- Arguments.of(" s I ", "00XX", CENTURY_ROMAN),
- Arguments.of("?s. I", "00XX?", CENTURY_ROMAN),
- Arguments.of("sec. I?", "00XX?", CENTURY_ROMAN),
- Arguments.of("?saec. I?", "00XX?", CENTURY_ROMAN),
- Arguments.of(" I ", "00XX", CENTURY_ROMAN),
- Arguments.of("?I", "00XX?", CENTURY_ROMAN),
- Arguments.of("I?", "00XX?", CENTURY_ROMAN),
- Arguments.of("?I?", "00XX?", CENTURY_ROMAN),
+ of("Ii", "01XX", CENTURY_ROMAN),
+ of(" s I ", "00XX", CENTURY_ROMAN),
+ of("?s. I", "00XX?", CENTURY_ROMAN),
+ of("sec. I?", "00XX?", CENTURY_ROMAN),
+ of("?saec. I?", "00XX?", CENTURY_ROMAN),
+ of(" I ", "00XX", CENTURY_ROMAN),
+ of("?I", "00XX?", CENTURY_ROMAN),
+ of("I?", "00XX?", CENTURY_ROMAN),
+ of("?I?", "00XX?", CENTURY_ROMAN),
//Non matches
- Arguments.of("saecI", null, null), //Without a dot a space is required
- Arguments.of("secI", null, null), //Without a dot a space is required
- Arguments.of("MDCLXX", null, null, null), // Not supported range
- Arguments.of("IXX", null, null, null), // Invalid roman
+ of("saecI", null, null), //Without a dot a space is required
+ of("secI", null, null), //Without a dot a space is required
+ of("MDCLXX", null, null, null), // Not supported range
+ of("IXX", null, null, null), // Invalid roman
//PATTERN_ROMAN_RANGE
//Uppercase
- Arguments.of("I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
- Arguments.of("II-III", "01XX/02XX", CENTURY_RANGE_ROMAN),
- Arguments.of("III-IV", "02XX/03XX", CENTURY_RANGE_ROMAN),
- Arguments.of("IV-V", "03XX/04XX", CENTURY_RANGE_ROMAN),
- Arguments.of("V-VI", "04XX/05XX", CENTURY_RANGE_ROMAN),
- Arguments.of("VI-VII", "05XX/06XX", CENTURY_RANGE_ROMAN),
- Arguments.of("VII-VIII", "06XX/07XX", CENTURY_RANGE_ROMAN),
- Arguments.of("VIII-IX", "07XX/08XX", CENTURY_RANGE_ROMAN),
- Arguments.of("IX-X", "08XX/09XX", CENTURY_RANGE_ROMAN),
- Arguments.of("X-XI", "09XX/10XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XI-XII", "10XX/11XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XII-XIII", "11XX/12XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XIII-XIV", "12XX/13XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XIV-XV", "13XX/14XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XV-XVI", "14XX/15XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XVI-XVII", "15XX/16XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XVII-XVIII", "16XX/17XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XVIII-XIX", "17XX/18XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XIX-XX", "18XX/19XX", CENTURY_RANGE_ROMAN),
- Arguments.of("XX-XXI", "19XX/20XX", CENTURY_RANGE_ROMAN),
+ of("I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
+ of("II-III", "01XX/02XX", CENTURY_RANGE_ROMAN),
+ of("III-IV", "02XX/03XX", CENTURY_RANGE_ROMAN),
+ of("IV-V", "03XX/04XX", CENTURY_RANGE_ROMAN),
+ of("V-VI", "04XX/05XX", CENTURY_RANGE_ROMAN),
+ of("VI-VII", "05XX/06XX", CENTURY_RANGE_ROMAN),
+ of("VII-VIII", "06XX/07XX", CENTURY_RANGE_ROMAN),
+ of("VIII-IX", "07XX/08XX", CENTURY_RANGE_ROMAN),
+ of("IX-X", "08XX/09XX", CENTURY_RANGE_ROMAN),
+ of("X-XI", "09XX/10XX", CENTURY_RANGE_ROMAN),
+ of("XI-XII", "10XX/11XX", CENTURY_RANGE_ROMAN),
+ of("XII-XIII", "11XX/12XX", CENTURY_RANGE_ROMAN),
+ of("XIII-XIV", "12XX/13XX", CENTURY_RANGE_ROMAN),
+ of("XIV-XV", "13XX/14XX", CENTURY_RANGE_ROMAN),
+ of("XV-XVI", "14XX/15XX", CENTURY_RANGE_ROMAN),
+ of("XVI-XVII", "15XX/16XX", CENTURY_RANGE_ROMAN),
+ of("XVII-XVIII", "16XX/17XX", CENTURY_RANGE_ROMAN),
+ of("XVIII-XIX", "17XX/18XX", CENTURY_RANGE_ROMAN),
+ of("XIX-XX", "18XX/19XX", CENTURY_RANGE_ROMAN),
+ of("XX-XXI", "19XX/20XX", CENTURY_RANGE_ROMAN),
//Lowercase
- Arguments.of("i-ii", "00XX/01XX", CENTURY_RANGE_ROMAN),
- Arguments.of("ii-iii", "01XX/02XX", CENTURY_RANGE_ROMAN),
- Arguments.of("iii-iv", "02XX/03XX", CENTURY_RANGE_ROMAN),
- Arguments.of("iv-v", "03XX/04XX", CENTURY_RANGE_ROMAN),
- Arguments.of("v-vi", "04XX/05XX", CENTURY_RANGE_ROMAN),
- Arguments.of("vi-vii", "05XX/06XX", CENTURY_RANGE_ROMAN),
- Arguments.of("vii-viii", "06XX/07XX", CENTURY_RANGE_ROMAN),
- Arguments.of("viii-ix", "07XX/08XX", CENTURY_RANGE_ROMAN),
- Arguments.of("ix-x", "08XX/09XX", CENTURY_RANGE_ROMAN),
- Arguments.of("x-xi", "09XX/10XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xi-xii", "10XX/11XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xii-xiii", "11XX/12XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xiii-xiv", "12XX/13XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xiv-xv", "13XX/14XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xv-xvi", "14XX/15XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xvi-xvii", "15XX/16XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xvii-xviii", "16XX/17XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xviii-xix", "17XX/18XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xix-xx", "18XX/19XX", CENTURY_RANGE_ROMAN),
- Arguments.of("xx-xxi", "19XX/20XX", CENTURY_RANGE_ROMAN),
+ of("i-ii", "00XX/01XX", CENTURY_RANGE_ROMAN),
+ of("ii-iii", "01XX/02XX", CENTURY_RANGE_ROMAN),
+ of("iii-iv", "02XX/03XX", CENTURY_RANGE_ROMAN),
+ of("iv-v", "03XX/04XX", CENTURY_RANGE_ROMAN),
+ of("v-vi", "04XX/05XX", CENTURY_RANGE_ROMAN),
+ of("vi-vii", "05XX/06XX", CENTURY_RANGE_ROMAN),
+ of("vii-viii", "06XX/07XX", CENTURY_RANGE_ROMAN),
+ of("viii-ix", "07XX/08XX", CENTURY_RANGE_ROMAN),
+ of("ix-x", "08XX/09XX", CENTURY_RANGE_ROMAN),
+ of("x-xi", "09XX/10XX", CENTURY_RANGE_ROMAN),
+ of("xi-xii", "10XX/11XX", CENTURY_RANGE_ROMAN),
+ of("xii-xiii", "11XX/12XX", CENTURY_RANGE_ROMAN),
+ of("xiii-xiv", "12XX/13XX", CENTURY_RANGE_ROMAN),
+ of("xiv-xv", "13XX/14XX", CENTURY_RANGE_ROMAN),
+ of("xv-xvi", "14XX/15XX", CENTURY_RANGE_ROMAN),
+ of("xvi-xvii", "15XX/16XX", CENTURY_RANGE_ROMAN),
+ of("xvii-xviii", "16XX/17XX", CENTURY_RANGE_ROMAN),
+ of("xviii-xix", "17XX/18XX", CENTURY_RANGE_ROMAN),
+ of("xix-xx", "18XX/19XX", CENTURY_RANGE_ROMAN),
+ of("xx-xxi", "19XX/20XX", CENTURY_RANGE_ROMAN),
//Prefixes
- Arguments.of("s I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
- Arguments.of("S I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
- Arguments.of("s. I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
- Arguments.of("S. I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
- Arguments.of("sec.IV-VII", "03XX/06XX", CENTURY_RANGE_ROMAN),
- Arguments.of("SEC.IV-VII", "03XX/06XX", CENTURY_RANGE_ROMAN),
- Arguments.of("sec. IV-VII", "03XX/06XX", CENTURY_RANGE_ROMAN),
- Arguments.of("SEC. IV-VII", "03XX/06XX", CENTURY_RANGE_ROMAN),
- Arguments.of("saec.VII-XVIII", "06XX/17XX", CENTURY_RANGE_ROMAN),
- Arguments.of("SAEC.VII-XVIII", "06XX/17XX", CENTURY_RANGE_ROMAN),
- Arguments.of("saec. XVI-XVIII", "15XX/17XX", CENTURY_RANGE_ROMAN),
- Arguments.of("SAEC. XVI-XVIII", "15XX/17XX", CENTURY_RANGE_ROMAN),
+ of("s I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
+ of("S I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
+ of("s. I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
+ of("S. I-II", "00XX/01XX", CENTURY_RANGE_ROMAN),
+ of("sec.IV-VII", "03XX/06XX", CENTURY_RANGE_ROMAN),
+ of("SEC.IV-VII", "03XX/06XX", CENTURY_RANGE_ROMAN),
+ of("sec. IV-VII", "03XX/06XX", CENTURY_RANGE_ROMAN),
+ of("SEC. IV-VII", "03XX/06XX", CENTURY_RANGE_ROMAN),
+ of("saec.VII-XVIII", "06XX/17XX", CENTURY_RANGE_ROMAN),
+ of("SAEC.VII-XVIII", "06XX/17XX", CENTURY_RANGE_ROMAN),
+ of("saec. XVI-XVIII", "15XX/17XX", CENTURY_RANGE_ROMAN),
+ of("SAEC. XVI-XVIII", "15XX/17XX", CENTURY_RANGE_ROMAN),
//Other possibilities and uncertain
- Arguments.of("s I-iI", "00XX/01XX", CENTURY_RANGE_ROMAN),
- Arguments.of(" s I-II ", "00XX/01XX", CENTURY_RANGE_ROMAN),
- Arguments.of("?saec.X-XVIII", "09XX?/17XX?", CENTURY_RANGE_ROMAN),
- Arguments.of("X-XVIII?", "09XX?/17XX?", CENTURY_RANGE_ROMAN),
- Arguments.of("?saec.X-XVIII?", "09XX?/17XX?", CENTURY_RANGE_ROMAN),
+ of("s I-iI", "00XX/01XX", CENTURY_RANGE_ROMAN),
+ of(" s I-II ", "00XX/01XX", CENTURY_RANGE_ROMAN),
+ of("?saec.X-XVIII", "09XX?/17XX?", CENTURY_RANGE_ROMAN),
+ of("X-XVIII?", "09XX?/17XX?", CENTURY_RANGE_ROMAN),
+ of("?saec.X-XVIII?", "09XX?/17XX?", CENTURY_RANGE_ROMAN),
//Non matches
- Arguments.of("S. XIIII-XIIIV", null, null), //Invalid roman
- Arguments.of("S. XVIII-", null, null, null), //Open-ended incorrect
- Arguments.of("sII-V", null, null), //Without a dot a space is required
- Arguments.of("secVI-XVII", null, null), //Without a dot a space is required
- Arguments.of("saecX-XVIII?", null, null) //Without a dot a space is required
+ of("S. XIIII-XIIIV", null, null), //Invalid roman
+ of("S. XVIII-", null, null, null), //Open-ended incorrect
+ of("sII-V", null, null), //Without a dot a space is required
+ of("secVI-XVII", null, null), //Without a dot a space is required
+ of("saecX-XVIII?", null, null) //Without a dot a space is required
);
}
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/DcmiPeriodDateExtractorTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/DcmiPeriodDateExtractorTest.java
index 47899fc798..74670ea253 100644
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/DcmiPeriodDateExtractorTest.java
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/DcmiPeriodDateExtractorTest.java
@@ -1,12 +1,12 @@
package eu.europeana.normalization.dates.extraction.dateextractors;
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.DCMI_PERIOD;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.of;
import eu.europeana.normalization.dates.DateNormalizationResult;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
@@ -24,16 +24,15 @@ class DcmiPeriodDateExtractorTest {
@DisplayName("Extract DCMI Period")
void extract(String actualDcmiPeriod, String expectedLabel, String expectedStartDate, String expectedEndDate) {
DcmiPeriodDateExtractor periodDateExtractor = new DcmiPeriodDateExtractor();
- DateNormalizationResult result = periodDateExtractor.extract(actualDcmiPeriod);
+ DateNormalizationResult dateNormalizationResult = periodDateExtractor.extractDateProperty(actualDcmiPeriod, NO_QUALIFICATION);
if (expectedStartDate == null || expectedEndDate == null) {
- assertNull(result);
+ assertEquals(DateNormalizationResultStatus.NO_MATCH, dateNormalizationResult.getDateNormalizationResultStatus());
} else {
- IntervalEdtfDate interval = (IntervalEdtfDate) result.getEdtfDate();
+ IntervalEdtfDate interval = (IntervalEdtfDate) dateNormalizationResult.getEdtfDate();
assertEquals(expectedLabel, interval.getLabel());
assertEquals(expectedStartDate, interval.getStart() != null ? interval.getStart().toString() : null);
assertEquals(expectedEndDate, interval.getEnd() != null ? interval.getEnd().toString() : null);
- assertEquals(DCMI_PERIOD, result.getDateNormalizationExtractorMatchId());
- assertTrue(result.isCompleteDate());
+ assertEquals(DCMI_PERIOD, dateNormalizationResult.getDateNormalizationExtractorMatchId());
}
}
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/DecadeDateExtractorTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/DecadeDateExtractorTest.java
index 5a0942bad2..fbdd350963 100644
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/DecadeDateExtractorTest.java
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/DecadeDateExtractorTest.java
@@ -1,12 +1,14 @@
package eu.europeana.normalization.dates.extraction.dateextractors;
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.DECADE;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.params.provider.Arguments.of;
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -19,13 +21,14 @@ class DecadeDateExtractorTest {
@ParameterizedTest
@MethodSource
void extract(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
- final DateNormalizationResult dateNormalizationResult = DECADE_DATE_EXTRACTOR.extract(input);
+ final DateNormalizationResult dateNormalizationResult = DECADE_DATE_EXTRACTOR.extractDateProperty(input, NO_QUALIFICATION);
if (expected == null) {
- assertNull(dateNormalizationResult);
+ assertEquals(DateNormalizationResultStatus.NO_MATCH, dateNormalizationResult.getDateNormalizationResultStatus());
} else {
final String actual = dateNormalizationResult.getEdtfDate().toString();
assertEquals(expected, actual);
- assertEquals(actual.contains("?"), dateNormalizationResult.getEdtfDate().isUncertain());
+ assertEquals(actual.contains("?"),
+ dateNormalizationResult.getEdtfDate().getDateQualification() == DateQualification.UNCERTAIN);
assertEquals(dateNormalizationExtractorMatchId, dateNormalizationResult.getDateNormalizationExtractorMatchId());
}
}
@@ -46,7 +49,8 @@ private static Stream extract() {
of("?180u?", "180X?", DECADE),
of("?180??", "180X?", DECADE),
- of("222u", "222X", DECADE),
+ //Future dates not allowed
+ of("222u", null, null),
//This is an ambiguous case because hyphen can be used as a separator
of("180-?", null, null),
//Ambiguous, possible open end
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/EdtfDateExtractorTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/EdtfDateExtractorTest.java
new file mode 100644
index 0000000000..b0fedbf806
--- /dev/null
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/EdtfDateExtractorTest.java
@@ -0,0 +1,319 @@
+package eu.europeana.normalization.dates.extraction.dateextractors;
+
+import static eu.europeana.normalization.dates.edtf.DateBoundaryType.OPEN;
+import static eu.europeana.normalization.dates.edtf.DateBoundaryType.UNKNOWN;
+import static eu.europeana.normalization.dates.edtf.DateQualification.APPROXIMATE;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
+import static eu.europeana.normalization.dates.edtf.DateQualification.UNCERTAIN;
+import static eu.europeana.normalization.dates.edtf.DateQualification.UNCERTAIN_APPROXIMATE;
+import static eu.europeana.normalization.dates.edtf.IntervalEdtfDate.DATE_INTERVAL_SEPARATOR;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.of;
+
+import eu.europeana.normalization.dates.DateNormalizationResult;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
+import eu.europeana.normalization.dates.edtf.AbstractEdtfDate;
+import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
+import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class EdtfDateExtractorTest {
+
+ private final EdtfDateExtractor edtfDateExtractor = new EdtfDateExtractor();
+
+ // TODO: 01/03/2023 Possible reuse of the test code here for all extractors
+ private void assertExtract(String input, String expected) {
+ final DateNormalizationResult dateNormalizationResult = edtfDateExtractor.extractDateProperty(input, NO_QUALIFICATION);
+ if (expected == null) {
+ assertEquals(DateNormalizationResultStatus.NO_MATCH, dateNormalizationResult.getDateNormalizationResultStatus());
+ } else {
+ AbstractEdtfDate edtfDate = dateNormalizationResult.getEdtfDate();
+ if (edtfDate instanceof IntervalEdtfDate) {
+ String startPart = expected.substring(0, expected.indexOf(DATE_INTERVAL_SEPARATOR));
+ String endPart = expected.substring(expected.indexOf(DATE_INTERVAL_SEPARATOR) + 1);
+ InstantEdtfDate start = ((IntervalEdtfDate) edtfDate).getStart();
+ InstantEdtfDate end = ((IntervalEdtfDate) edtfDate).getEnd();
+ assertEdtfDate(startPart, start);
+ assertEdtfDate(endPart, end);
+ } else {
+ assertEdtfDate(expected, (InstantEdtfDate) dateNormalizationResult.getEdtfDate());
+ }
+ assertEquals(expected, edtfDate.toString());
+ }
+ }
+
+ private static void assertEdtfDate(String expected, InstantEdtfDate instantEdtfDate) {
+ assertEquals(expected.contains("?"), instantEdtfDate.getDateQualification() == UNCERTAIN);
+ assertEquals(expected.contains("~"), instantEdtfDate.getDateQualification() == APPROXIMATE);
+ assertEquals(expected.contains("%"), instantEdtfDate.getDateQualification() == UNCERTAIN_APPROXIMATE);
+ assertEquals(expected.equals(OPEN.getSerializedRepresentation()),
+ instantEdtfDate.getDateBoundaryType() == OPEN || instantEdtfDate.getDateBoundaryType() == UNKNOWN);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("[year][“-”][month][“-”][day] Complete representation")
+ void completeDateRepresentationLevel0(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("[year][“-”][month] Reduced precision for year and month")
+ void reducedPrecisionForYearAndMonthLevel0(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("[year] Reduced precision for year")
+ void reducedPrecisionForYearLevel0(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void dateIntervalRepresentationLevel0(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("Letter-prefixed calendar year")
+ void letterPrefixedCalendarYearLevel1(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("The characters '?', '~' and '%' are used to mean \"uncertain\", \"approximate\", and \"uncertain\" as well as \"approximate\", respectively")
+ void dateQualificationLevel1(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("Negative Calendar Year")
+ void negativeCalendarYearLevel1(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("Open time interval")
+ void openTimeIntervalLevel1(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+
+ @ParameterizedTest
+ @MethodSource
+ @DisplayName("Unknown time interval")
+ void unknownTimeIntervalLevel1(String input, String expected) {
+ assertExtract(input, expected);
+ }
+
+ private static Stream completeDateRepresentationLevel0() {
+ return Stream.of(
+ of("1989-11-01", "1989-11-01"),
+ of("0989-11-01", "0989-11-01"),
+ of("0989-11-01", "0989-11-01"),
+ //Digits missing on year
+ of("198-11-01", null),
+ //Digits missing on month or day
+ of("1989-11-1", null),
+ of("1989-1-01", null),
+ //Anything other than hyphen "-" is not valid
+ of("1989/11/01", null),
+
+ //Complete representations for calendar date and (local) time of day
+ of("1989-11-01T23:59:59", "1989-11-01"),
+ of("1989-11-01T23:59", "1989-11-01"),
+ of("1989-11-01T23", "1989-11-01"),
+ of("1989-11-01T", "1989-11-01"),
+ of("1989-11-01T23:59:5", "1989-11-01"),
+ of("1989-11-01T23:5:59", "1989-11-01"),
+ of("1989-11-01t23:59:59", null),
+ of("1989-11-01 23:59:59", null),
+
+ //Complete representations for calendar date and UTC time of day
+ of("1989-11-01T23:59:59Z", "1989-11-01"),
+ of("1989-11-01t23:59:59Z", null),
+ of("1989-11-01 23:59:59Z", null),
+
+ //Date and time with time shift in hours (only)
+ of("1989-11-01T23:59:59-04", "1989-11-01"),
+ of("1989-11-01T23:59:59+04", "1989-11-01"),
+ of("1989-11-01t23:59:59-04", null),
+ of("1989-11-01 23:59:59-04", null),
+
+ //Date and time with time shift in hours and minutes
+ of("1989-11-01T23:59:59-04:44", "1989-11-01"),
+ of("1989-11-01T23:59:59+04:44", "1989-11-01"),
+ of("1989-11-01t23:59:59-04:44", null),
+ of("1989-11-01 23:59:59-04:44", null)
+ );
+ }
+
+ private static Stream reducedPrecisionForYearAndMonthLevel0() {
+ return Stream.of(
+ of("1989-11", "1989-11"),
+ of("0989-11", "0989-11"),
+ //Digits missing on year
+ of("198-11", null),
+ //Digits missing on month
+ of("1989-1", null),
+ //Anything other than hyphen "-" is not valid
+ of("1989/11", null)
+ );
+ }
+
+ private static Stream reducedPrecisionForYearLevel0() {
+ return Stream.of(
+ of("1989", "1989"),
+ of("0989", "0989"),
+ //Digits missing on year
+ of("198", null)
+ );
+ }
+
+ private static Stream dateIntervalRepresentationLevel0() {
+ return Stream.of(
+ of("1989/1990", "1989/1990"),
+ of("1989-11/1990-11", "1989-11/1990-11"),
+ of("1989-11-01/1990-11-01", "1989-11-01/1990-11-01"),
+ of("1989-11-01/1990-11", "1989-11-01/1990-11"),
+ of("1989-11-01/1990", "1989-11-01/1990"),
+ of("1989/1990-11", "1989/1990-11"),
+ of("1989/1990-11-01", "1989/1990-11-01"),
+ of("1989-00/1990-00", null),
+ of("1989-00-00/1990-00-00", null),
+ //Spaces not valid
+ of("1989 / 1990", null),
+ //Dash not valid
+ of("1989-1990", null),
+ //Missing digits
+ of("989-1990", null),
+ of("1989-990", null)
+ );
+ }
+
+ private static Stream letterPrefixedCalendarYearLevel1() {
+ return Stream.of(
+ //Future dates are not valid
+ of("Y170000002", null),
+ of("Y-170000002", "Y-170000002"),
+ //Overflow, max is +-999999999
+ of("Y1700000002", null),
+ of("Y-1700000002", null),
+ //Too low values
+ of("Y0", null),
+ of("Y1", null),
+ of("Y-1", null),
+ of("Y", null)
+ );
+ }
+
+ private static Stream dateQualificationLevel1() {
+ return Stream.of(
+ of("1989?", "1989?"),
+ of("1989~", "1989~"),
+ of("1989-11?", "1989-11?"),
+ of("1989-11~", "1989-11~"),
+ of("1989-11-01%", "1989-11-01%")
+ );
+ }
+
+ private static Stream negativeCalendarYearLevel1() {
+ return Stream.of(
+ of("-1989", "-1989"),
+ of("-9999", "-9999"),
+ of("-0989", "-0989"),
+ of("-11989", null)
+ );
+ }
+
+ private static Stream openTimeIntervalLevel1() {
+ return Stream.of(
+ //Open start
+ of("../1989-11-01", "../1989-11-01"),
+ of("../1989-11", "../1989-11"),
+ of("../1989", "../1989"),
+ of("../1989-11-01~", "../1989-11-01~"),
+ of("../1989-11~", "../1989-11~"),
+ of("../1989~", "../1989~"),
+ of("../1989-11-01?", "../1989-11-01?"),
+ of("../1989-11?", "../1989-11?"),
+ of("../1989?", "../1989?"),
+ of("../1989-11-01%", "../1989-11-01%"),
+ of("../1989-11%", "../1989-11%"),
+ of("../1989%", "../1989%"),
+ of(".. / 1989-11-01", null),
+ of("../ 1989-11-01", null),
+ of(".. /1989-11-01", null),
+
+ //Open end
+ of("1989-11-01/..", "1989-11-01/.."),
+ of("1989-11/..", "1989-11/.."),
+ of("1989/..", "1989/.."),
+ of("1989-11-01~/..", "1989-11-01~/.."),
+ of("1989-11~/..", "1989-11~/.."),
+ of("1989~/..", "1989~/.."),
+ of("1989-11-01?/..", "1989-11-01?/.."),
+ of("1989-11?/..", "1989-11?/.."),
+ of("1989?/..", "1989?/.."),
+ of("1989-11-01%/..", "1989-11-01%/.."),
+ of("1989-11%/..", "1989-11%/.."),
+ of("1989%/..", "1989%/.."),
+ of("1989-11-01 / ..", null),
+ of("1989-11-01 /..", null),
+ of("1989-11-01/ ..", null),
+ of("../..", null)
+ );
+ }
+
+
+ private static Stream unknownTimeIntervalLevel1() {
+ return Stream.of(
+ //Unknown start
+ of("/1989-11-01", "../1989-11-01"),
+ of("/1989-11", "../1989-11"),
+ of("/1989", "../1989"),
+ of("/1989-11-01~", "../1989-11-01~"),
+ of("/1989-11~", "../1989-11~"),
+ of("/1989~", "../1989~"),
+ of("/1989-11-01?", "../1989-11-01?"),
+ of("/1989-11?", "../1989-11?"),
+ of("/1989?", "../1989?"),
+ of("/1989-11-01%", "../1989-11-01%"),
+ of("/1989-11%", "../1989-11%"),
+ of("/1989%", "../1989%"),
+ of(" / 1989-11-01", null),
+ of("/ 1989-11-01", null),
+ of(" /1989-11-01", null),
+
+ //Unknown end
+ of("1989-11-01/", "1989-11-01/.."),
+ of("1989-11/", "1989-11/.."),
+ of("1989/", "1989/.."),
+ of("1989-11-01~/", "1989-11-01~/.."),
+ of("1989-11~/", "1989-11~/.."),
+ of("1989~/", "1989~/.."),
+ of("1989-11-01?/", "1989-11-01?/.."),
+ of("1989-11?/", "1989-11?/.."),
+ of("1989?/", "1989?/.."),
+ of("1989-11-01%/", "1989-11-01%/.."),
+ of("1989-11%/", "1989-11%/.."),
+ of("1989%/", "1989%/.."),
+ of("1989-11-01 / ", null),
+ of("1989-11-01 /", null),
+ of("1989-11-01/ ", null),
+ of("/", null)
+ );
+ }
+}
\ No newline at end of file
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsDateExtractorTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsDateExtractorTest.java
index 082b91495a..0aaa161ea8 100644
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsDateExtractorTest.java
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsDateExtractorTest.java
@@ -3,12 +3,14 @@
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.NUMERIC_ALL_VARIANTS;
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.NUMERIC_ALL_VARIANTS_XX;
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.YYYY_MM_DD_SPACES;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.params.provider.Arguments.of;
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -21,41 +23,43 @@ class NumericPartsDateExtractorTest {
@ParameterizedTest
@MethodSource
void extractYMD(String input, String expected) {
- extract(input, expected, NUMERIC_ALL_VARIANTS);
+ assertExtract(input, expected, NUMERIC_ALL_VARIANTS);
}
@ParameterizedTest
@MethodSource
void extractDMY(String input, String expected) {
- extract(input, expected, NUMERIC_ALL_VARIANTS);
+ assertExtract(input, expected, NUMERIC_ALL_VARIANTS);
}
@ParameterizedTest
@MethodSource
void extractYMD_XX(String input, String expected) {
- extract(input, expected, NUMERIC_ALL_VARIANTS_XX);
+ assertExtract(input, expected, NUMERIC_ALL_VARIANTS_XX);
}
@ParameterizedTest
@MethodSource
void extractDMY_XX(String input, String expected) {
- extract(input, expected, NUMERIC_ALL_VARIANTS_XX);
+ assertExtract(input, expected, NUMERIC_ALL_VARIANTS_XX);
}
@ParameterizedTest
@MethodSource
void extractDateSpaces(String input, String expected) {
- extract(input, expected, YYYY_MM_DD_SPACES);
+ assertExtract(input, expected, YYYY_MM_DD_SPACES);
}
- void extract(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
- final DateNormalizationResult dateNormalizationResult = NUMERIC_PARTS_DATE_EXTRACTOR.extract(input);
+ void assertExtract(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
+ final DateNormalizationResult dateNormalizationResult = NUMERIC_PARTS_DATE_EXTRACTOR.extractDateProperty(input,
+ NO_QUALIFICATION);
if (expected == null) {
- assertNull(dateNormalizationResult);
+ assertEquals(DateNormalizationResultStatus.NO_MATCH, dateNormalizationResult.getDateNormalizationResultStatus());
} else {
final String actual = dateNormalizationResult.getEdtfDate().toString();
assertEquals(expected, actual);
- assertEquals(actual.contains("?"), dateNormalizationResult.getEdtfDate().isUncertain());
+ assertEquals(actual.contains("?"),
+ dateNormalizationResult.getEdtfDate().getDateQualification() == DateQualification.UNCERTAIN);
assertEquals(dateNormalizationExtractorMatchId, dateNormalizationResult.getDateNormalizationExtractorMatchId());
}
}
@@ -106,8 +110,7 @@ private static Stream extractYMD() {
//YEAR-MONTH-DAY
of("1989-11-01", "1989-11-01"),
- // TODO: 26/09/2022 Make sure this is checked later on, on validation code
- of("1989-13-32", "1989-13-32"),
+ of("1989-13-32", null),
//Some missing digits are allowed
of("989-1-1", "0989-01-01"),
of("?1989-11-01", "1989-11-01?"),
@@ -129,10 +132,10 @@ private static Stream extractYMD() {
//Combination of separators
of("?989/1-1", "0989-01-01?"),
of("?989-1/1", "0989-01-01?"),
- of("9989-99/99", "9989-99-99"),
- of("9989/99-99", "9989-99-99"),
- of("?989-99/99", "0989-99-99?"),
- of("?989-99/99?", "0989-99-99?"),
+ of("9989-99/99", null),
+ of("9989/99-99", null),
+ of("?989-99/99", null),
+ of("?989-99/99?", null),
//Too few digits on year
of("89-01-01", null, null),
@@ -166,8 +169,7 @@ private static Stream extractDMY() {
//DAY-MONTH-YEAR
of("01-11-1989", "1989-11-01"),
- // TODO: 26/09/2022 Make sure this is checked later on, on validation code
- of("32-13-1989", "1989-13-32"),
+ of("32-13-1989", null),
//Some missing digits are allowed
of("1-1-989", "0989-01-01"),
of("?01-11-1989", "1989-11-01?"),
@@ -189,9 +191,9 @@ private static Stream extractDMY() {
//Combination of separators
of("?1-1/989", "0989-01-01?"),
of("?1/1-989", "0989-01-01?"),
- of("99/99-9989", "9989-99-99"),
- of("99-99/9989", "9989-99-99"),
- of("?99/99-989", "0989-99-99?"),
+ of("99/99-9989", null),
+ of("99-99/9989", null),
+ of("?99/99-989", null),
//Too few digits on year
of("01-01-89", null, null),
@@ -303,7 +305,7 @@ private static Stream extractYMD_XX() {
of("19XX/XX/99", "19XX"),
of("19XX/--/--", "19XX"),
of("19XX.--.--", "19XX"),
- of("19XX-11-99?", "19XX-11-99?"),
+ of("19XX-11-99?", null),
of("19UU-XX-99?", "19XX?"),
of("19UU-??-99?", "19XX?"),
of("19UU/--/99?", "19XX?"),
@@ -395,7 +397,7 @@ private static Stream extractDMY_XX() {
of("99/XX/19XX", "19XX"),
of("--/--/19XX", "19XX"),
of("--.--.19XX", "19XX"),
- of("?99-11-19XX", "19XX-11-99?"),
+ of("?99-11-19XX", null),
of("?99-XX-19UU", "19XX?"),
of("?99-??-19UU", "19XX?"),
of("?99/--/19UU", "19XX?"),
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsRangeDateExtractorTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsRangeDateExtractorTest.java
index 8e8728cc89..65fb376f27 100644
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsRangeDateExtractorTest.java
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericPartsRangeDateExtractorTest.java
@@ -3,11 +3,13 @@
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.NUMERIC_RANGE_ALL_VARIANTS;
import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.NUMERIC_RANGE_ALL_VARIANTS_XX;
+import static eu.europeana.normalization.dates.edtf.DateQualification.NO_QUALIFICATION;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
import eu.europeana.normalization.dates.DateNormalizationResult;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
+import eu.europeana.normalization.dates.edtf.DateQualification;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -40,14 +42,16 @@ void extractDMY_XX(String input, String expected) {
}
void extract(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId) {
- final DateNormalizationResult dateNormalizationResult = NUMERIC_PARTS_RANGE_DATE_EXTRACTOR.extract(input);
+ final DateNormalizationResult dateNormalizationResult = NUMERIC_PARTS_RANGE_DATE_EXTRACTOR.extractDateProperty(input,
+ NO_QUALIFICATION);
if (expected == null) {
- assertNull(dateNormalizationResult);
+ assertEquals(DateNormalizationResultStatus.NO_MATCH, dateNormalizationResult.getDateNormalizationResultStatus());
} else {
final String actual = dateNormalizationResult.getEdtfDate().toString();
assertEquals(expected, actual);
- assertEquals(actual.contains("?"), dateNormalizationResult.getEdtfDate().isUncertain());
- assertEquals(actual.contains(".."), dateNormalizationResult.getEdtfDate().isUnspecified());
+ assertEquals(actual.contains("?"),
+ dateNormalizationResult.getEdtfDate().getDateQualification() == DateQualification.UNCERTAIN);
+ assertEquals(actual.contains(".."), dateNormalizationResult.getEdtfDate().isOpen());
assertEquals(dateNormalizationExtractorMatchId, dateNormalizationResult.getDateNormalizationExtractorMatchId());
}
}
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericRangeYMDXXArgumentsProvider.java b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericRangeYMDXXArgumentsProvider.java
index 05605930b1..71a0f51f8a 100644
--- a/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericRangeYMDXXArgumentsProvider.java
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/dates/extraction/dateextractors/NumericRangeYMDXXArgumentsProvider.java
@@ -711,7 +711,7 @@ private static Stream yearMonth_DashArguments() {
of("198X/XX-199X/XX", "198X/199X"),
of("198X/UU-199X/UU", "198X/199X"),
//This is a special numeric case, because the "-" separator does not support "-" unspecified, so it is caught from a later pattern
- of("1989/11--", "1989/11XX"),
+ of("1989/11--", "11XX/1989"),
//Unspecified
of("1989/XX-?", "1989/.."),
of("1989/XX--", null, null),
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/normalizers/CleanMarkupTagsNormalizerTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/normalizers/CleanMarkupTagsNormalizerTest.java
index e7bdb51c15..3055f0c12d 100644
--- a/metis-normalization/src/test/java/eu/europeana/normalization/normalizers/CleanMarkupTagsNormalizerTest.java
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/normalizers/CleanMarkupTagsNormalizerTest.java
@@ -22,8 +22,6 @@ void testHtmlMarkup() {
CleanMarkupTagsNormalizer cleaner = new CleanMarkupTagsNormalizer(CleanMarkupTagsMode.HTML_ONLY);
List cleaned = cleaner.normalizeValue(html).stream()
.map(NormalizedValueWithConfidence::getNormalizedValue).collect(Collectors.toList());
- System.out.println(html);
- System.out.println(cleaned);
assertEquals(1, cleaned.size());
assertTrue(cleaned.get(0).contains("ire this"));
assertTrue(cleaned.get(0).contains("guy"));
@@ -38,7 +36,6 @@ void testAllMarkup() {
CleanMarkupTagsNormalizer cleaner = new CleanMarkupTagsNormalizer(CleanMarkupTagsMode.ALL_MARKUP);
List cleaned = cleaner.normalizeValue(html).stream()
.map(NormalizedValueWithConfidence::getNormalizedValue).collect(Collectors.toList());
- System.out.println(cleaned);
assertEquals(1, cleaned.size());
assertTrue(cleaned.get(0).contains("ire this guy"));
assertFalse(cleaned.get(0).contains("this is ugly html"));
diff --git a/metis-normalization/src/test/java/eu/europeana/normalization/normalizers/DatesNormalizerTest.java b/metis-normalization/src/test/java/eu/europeana/normalization/normalizers/DatesNormalizerTest.java
new file mode 100644
index 0000000000..70e2f679d4
--- /dev/null
+++ b/metis-normalization/src/test/java/eu/europeana/normalization/normalizers/DatesNormalizerTest.java
@@ -0,0 +1,269 @@
+package eu.europeana.normalization.normalizers;
+
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.BC_AD;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.BRIEF_DATE_RANGE;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.CENTURY_NUMERIC;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.CENTURY_RANGE_ROMAN;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.CENTURY_ROMAN;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.DCMI_PERIOD;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.EDTF;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.FORMATTED_FULL_DATE;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.MONTH_NAME;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.NUMERIC_ALL_VARIANTS;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.NUMERIC_ALL_VARIANTS_XX;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.NUMERIC_RANGE_ALL_VARIANTS;
+import static eu.europeana.normalization.dates.DateNormalizationExtractorMatchId.YYYY_MM_DD_SPACES;
+import static eu.europeana.normalization.dates.edtf.IntervalEdtfDate.DATE_INTERVAL_SEPARATOR;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.of;
+
+import eu.europeana.normalization.dates.DateNormalizationExtractorMatchId;
+import eu.europeana.normalization.dates.DateNormalizationResult;
+import eu.europeana.normalization.dates.DateNormalizationResultStatus;
+import eu.europeana.normalization.dates.edtf.AbstractEdtfDate;
+import eu.europeana.normalization.dates.edtf.DateBoundaryType;
+import eu.europeana.normalization.dates.edtf.DateQualification;
+import eu.europeana.normalization.dates.edtf.InstantEdtfDate;
+import eu.europeana.normalization.dates.edtf.IntervalEdtfDate;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class DatesNormalizerTest {
+
+ private final static DatesNormalizer NORMALIZER = new DatesNormalizer();
+
+ void assertExtract(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId,
+ String label) {
+ final DateNormalizationResult dateNormalizationResult = NORMALIZER.normalizeDateProperty(input);
+ if (expected != null) {
+ assertEquals(dateNormalizationExtractorMatchId, dateNormalizationResult.getDateNormalizationExtractorMatchId());
+ assertEquals(label, dateNormalizationResult.getEdtfDate().getLabel());
+ AbstractEdtfDate edtfDate = dateNormalizationResult.getEdtfDate();
+ if (edtfDate instanceof IntervalEdtfDate) {
+ String startPart = expected.substring(0, expected.indexOf(DATE_INTERVAL_SEPARATOR));
+ String endPart = expected.substring(expected.indexOf(DATE_INTERVAL_SEPARATOR) + 1);
+ InstantEdtfDate start = ((IntervalEdtfDate) edtfDate).getStart();
+ InstantEdtfDate end = ((IntervalEdtfDate) edtfDate).getEnd();
+ assertEdtfDate(startPart, start);
+ assertEdtfDate(endPart, end);
+ } else {
+ assertEdtfDate(expected, (InstantEdtfDate) dateNormalizationResult.getEdtfDate());
+ }
+ assertEquals(expected, edtfDate.toString());
+ } else {
+ assertEquals(DateNormalizationResultStatus.NO_MATCH, dateNormalizationResult.getDateNormalizationResultStatus());
+ }
+
+ }
+
+ private static void assertEdtfDate(String expected, InstantEdtfDate instantEdtfDate) {
+ assertEquals(expected.contains("?"), instantEdtfDate.getDateQualification() == DateQualification.UNCERTAIN);
+ assertEquals(expected.contains("~"), instantEdtfDate.getDateQualification() == DateQualification.APPROXIMATE);
+ assertEquals(expected.contains("%"), instantEdtfDate.getDateQualification() == DateQualification.UNCERTAIN_APPROXIMATE);
+ assertEquals(expected.equals(DateBoundaryType.OPEN.getSerializedRepresentation()),
+ instantEdtfDate.getDateBoundaryType() == DateBoundaryType.OPEN
+ || instantEdtfDate.getDateBoundaryType() == DateBoundaryType.UNKNOWN);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void extractDateProperties(String input, String expected, DateNormalizationExtractorMatchId dateNormalizationExtractorMatchId,
+ String label) {
+ assertExtract(input, expected, dateNormalizationExtractorMatchId, label);
+ }
+
+ private static Stream extractDateProperties() {
+ Stream argumentsWithoutLabel = Stream.of(
+ extractDatePropertiesWithoutLabel()
+ ).flatMap(Function.identity()).map(arguments ->
+ {
+ Object[] argumentsWithLabel = Arrays.copyOf(arguments.get(), arguments.get().length + 1);
+ argumentsWithLabel[argumentsWithLabel.length - 1] = null;
+ return of(argumentsWithLabel);
+ });
+ return Stream.concat(extractDatePropertiesWithLabel(), argumentsWithoutLabel);
+ }
+
+ private static Stream extractDatePropertiesWithLabel() {
+ return Stream.of(
+ //DCMI
+ of("name=Prehistoric Period; end=-5300", "../-5300", DCMI_PERIOD, "Prehistoric Period"),
+ of("Byzantine Period; start=0395; end=0641", "0395/0641", DCMI_PERIOD, null),
+ of("Modern era; start=1975;", "1975/..", DCMI_PERIOD, null)
+ );
+ }
+
+ private static Stream extractDatePropertiesWithoutLabel() {
+ return Stream.of(
+ //Brief dates. Those are similar to EDFT but should match first.
+ of("2014/15", "2014/2015", BRIEF_DATE_RANGE),
+ of("1889/98? (Herstellung)", "1889?/1898?", BRIEF_DATE_RANGE),
+ of("1918-20", "1918/1920", BRIEF_DATE_RANGE),
+
+ //Centuries numeric
+ of("18..", "18XX", CENTURY_NUMERIC),
+ of("19??", "19XX", NUMERIC_ALL_VARIANTS_XX),
+ of("192?", null, null),// ambiguous
+ of("[171-]", null, null), // ambiguous
+ of("19th century", "18XX", CENTURY_NUMERIC),
+ of("2nd century", "01XX", CENTURY_NUMERIC),
+ of("[10th century]", "09XX", CENTURY_NUMERIC), // not supported
+ of("12th century BC", null, null), // not supported
+
+ //Centuries roman
+ of("XIV", "13XX", CENTURY_ROMAN),
+ of("MDCLXX", null, null),
+ of("MDCVII", null, null),
+ of("S. XVI-XX", "15XX/19XX", CENTURY_RANGE_ROMAN),
+ of("S.VIII-XV", "07XX/14XX", CENTURY_RANGE_ROMAN),
+ of("S. XVI-XVIII", "15XX/17XX", CENTURY_RANGE_ROMAN),
+ of("S. XVIII-", null, null), // open-ended period
+ of("[XVI-XIX]", "15XX/18XX", CENTURY_RANGE_ROMAN),
+ of("SVV", null, null),
+
+ //Unknown/Unspecified start or end of range
+ of("1907/?", "1907/..", NUMERIC_RANGE_ALL_VARIANTS),
+ of("?/1907", "../1907", NUMERIC_RANGE_ALL_VARIANTS),
+ of("1907/", "1907/..", EDTF),
+ of("/1907", "../1907", EDTF),
+
+ //Numeric range '/'
+ of("1872-06-01/1872-06-30", "1872-06-01/1872-06-30", EDTF),
+ of(" 1820/1820", "1820/1820", NUMERIC_RANGE_ALL_VARIANTS),
+ of("1918 / 1919", "1918/1919", NUMERIC_RANGE_ALL_VARIANTS),
+ of("1205/1215 [Herstellung]", "1205/1215", EDTF),
+ of(" 1757/1757", "1757/1757", NUMERIC_RANGE_ALL_VARIANTS),
+ of("ca 1757/1757", "1757~/1757~", EDTF),
+ of("2000 vC - 2002 nC", "-2000/2002", BC_AD),
+ of("0114 aC - 0113 aC", "-0114/-0113", BC_AD),
+ of("0390 AD - 0425 AD", "0390/0425", BC_AD),
+ of("337 BC - 283 BC", "-0337/-0283", BC_AD),
+ of("100 vC - 150 nC", "-0100/0150", BC_AD),
+ of("400 BC - 400 AD", "-0400/0400", BC_AD),
+ of("235 AD – 236 AD", "0235/0236", BC_AD),
+ of("168 B.C.-135 A.D.", "-0168/0135", BC_AD),
+ of("20/09/18XX", "18XX-09-20", NUMERIC_ALL_VARIANTS_XX),
+ of("?/1807", "../1807", NUMERIC_RANGE_ALL_VARIANTS),
+ //Incorrect day values
+ of("1947-19-50/1950-19-53", null, null),
+ of("15/21-8-1918", null, null),
+ of("1.1848/49[?]", null, null),
+
+ //Numeric range ' - '(spaces around hyphen)
+ of("1851-01-01 - 1851-12-31", "1851-01-01/1851-12-31", NUMERIC_RANGE_ALL_VARIANTS),
+ of("1650? - 1700?", "1650?/1700?", NUMERIC_RANGE_ALL_VARIANTS),
+ of("1871 - 191-", null, null),
+
+ //Numeric range '-'
+ of("[1942-1943]", "1942/1943", NUMERIC_RANGE_ALL_VARIANTS),
+ of("(1942-1943)", "1942/1943", NUMERIC_RANGE_ALL_VARIANTS),
+ of("192?-1958", null, null),
+ of("[ca. 1920-1930]", "1920~/1930~", NUMERIC_RANGE_ALL_VARIANTS),
+ of("1937--1938", null, null),
+ of("[ca. 193-]", null, null),// ambiguous
+ of("1990-", null, null), // open-ended period not supported
+
+ //Numeric range '|'
+ of("1910/05/31 | 1910/05/01", "1910-05-01/1910-05-31", NUMERIC_RANGE_ALL_VARIANTS),
+
+ //Numeric range ' '(space)
+ of("1916-09-26 1916-09-28", "1916-09-26/1916-09-28", NUMERIC_RANGE_ALL_VARIANTS),
+ of("29-10-2009 29-10-2009", "2009-10-29/2009-10-29", NUMERIC_RANGE_ALL_VARIANTS),
+ // this may not be 100% correct, maybe it is not a range but two dates
+ of("1939 [1942?]", "1939/1942?", NUMERIC_RANGE_ALL_VARIANTS),
+ // this may not be a 100% correct normalisation, maybe it is not a range but two dates
+ of("1651 [ca. 1656]", "1651~/1656~", NUMERIC_RANGE_ALL_VARIANTS),
+
+ //Numeric year
+ of("(17--?)", "17XX?", NUMERIC_ALL_VARIANTS_XX),
+ of("[19--?]", "19XX?", NUMERIC_ALL_VARIANTS_XX),
+
+ //Numeric date with dot "."
+ of("21.1.1921", "1921-01-21", NUMERIC_ALL_VARIANTS),
+ of("12.10.1690", "1690-10-12", NUMERIC_ALL_VARIANTS),
+ of("26.4.1828", "1828-04-26", NUMERIC_ALL_VARIANTS),
+ of("28.05.1969", "1969-05-28", NUMERIC_ALL_VARIANTS),
+ of("11.11.1947", "1947-11-11", NUMERIC_ALL_VARIANTS),
+ of("23.02.[18--]", "18XX-02-23", NUMERIC_ALL_VARIANTS_XX),
+ of("28. 1. 1240", null, null),
+
+ //Numeric date with dash "-"
+ of("1941-22-06", "1941-06-22", NUMERIC_ALL_VARIANTS),
+ of("1937-10-??", "1937-10", NUMERIC_ALL_VARIANTS_XX),
+ of("199--09-28", null, null),
+ of("01?-1905", null, null),
+ of("02?-1915", null, null),
+
+ //Numeric date with space " "
+ of("1905 09 01", "1905-09-01", YYYY_MM_DD_SPACES),
+ of("0 2 1980", "1980-02", YYYY_MM_DD_SPACES),
+
+ //More than 4 digits year
+ of("18720601/18720630", null, null),
+ of("19471950/19501953", null, null),
+
+ of("-2100/-1550", "-2100/-1550", EDTF),
+ // TODO: 21/12/2022 Check the below, expected null but returns 1952-02-25 instead
+ // of("1952-02-25T00:00:00Z-1952-02-25T23:59:59Z", null),
+ of("2013-09-07 09:31:51 UTC", "2013-09-07", FORMATTED_FULL_DATE),
+ of("1997-07-18T00:00:00 [Create]", "1997-07-18", EDTF),
+ of("1924 ca.", null, null),
+ of("[1712?]", "1712?", EDTF),
+ of("circa 1712", "1712~", EDTF),
+ of("[ca. 1946]", "1946~", EDTF),
+ of("1651?]", "1651?", EDTF),
+ of("19--?]", "19XX?", NUMERIC_ALL_VARIANTS_XX),
+ of(". 1885", null, null),
+ of("- 1885", null, null),
+ of("1749 (Herstellung (Werk))", "1749", EDTF),
+ of("1939; 1954; 1955; 1978; 1939-1945", null, null), // multiple dates no suported
+ of("[17__]", null, null),// this pattern is not supported (this pattern was never tested
+ of("19--]", "19XX", NUMERIC_ALL_VARIANTS_XX),
+ of("19xx", "19XX", NUMERIC_ALL_VARIANTS_XX),
+ of("Sat Jan 01 01:00:00 CET 1701", "1701-01-01", FORMATTED_FULL_DATE),
+ of("2013-03-21 18:45:36 UTC", "2013-03-21", FORMATTED_FULL_DATE),
+ of("15.02.1985 (identification)", "1985-02-15", NUMERIC_ALL_VARIANTS),
+ of("091090", null, null),
+ of("-0043-12-07", "-0043-12-07", EDTF),
+ of("imp. 1901", null, null),
+ of("u.1707-1739", null, null),// what does 'u.' mean?
+ of("22.07.1971 (identification)", "1971-07-22", NUMERIC_ALL_VARIANTS),
+
+ //Ambiguous pattern
+ of("187-?]", null, null),
+
+ of("18. September 1914", "1914-09-18", MONTH_NAME),
+ of("19960216-19960619", null, null),
+ of("-0549-01-01T00:00:00Z", "-0549-01-01", EDTF),
+ of("1942-1943 c.", null, null),
+ of("(1942)", "1942", EDTF),
+ of("-3.6982", null, null),
+ of("[ca. 16??]", "16XX~", NUMERIC_ALL_VARIANTS_XX),
+ of("ISO9126", null, null),
+ of("1985-10-xx", "1985-10", NUMERIC_ALL_VARIANTS_XX),
+ of("14:27", null, null),
+ of("c.6 Nov 1902", "1902-11-06~", MONTH_NAME),
+ of("-1234", "-1234", EDTF),
+ of("09.1972 (gathering)", "1972-09", NUMERIC_ALL_VARIANTS)
+ );
+
+ }
+
+ // TODO: 10/03/2023 Don't forget to add specific to generic properties normalization
+ // //GENERIC PROPERTY
+ // genericPropertyTestCases.put("XIV", null);
+ // genericPropertyTestCases.put("1905 09 01", "1905-09-01");
+ // genericPropertyTestCases.put("1851-01-01 - 1851-12-31", "1851-01-01/1851-12-31");
+ // genericPropertyTestCases.put("18..", null);
+ // genericPropertyTestCases.put("2013-09-07 09:31:51 UTC", "2013-09-07");
+ // genericPropertyTestCases.put("1918 / 1919", "1918/1919");
+ // genericPropertyTestCases.put("1205/1215 [Herstellung]", null);
+ // genericPropertyTestCases.put("1997-07", null);
+ // genericPropertyTestCases.put("19??", null);
+ // genericPropertyTestCases.put("1871 - 191-", null);
+
+}
\ No newline at end of file
diff --git a/metis-normalization/src/test/resources/edm-record-internal.xml b/metis-normalization/src/test/resources/edm-record-internal.xml
index 080c82de1e..d42e1cf062 100644
--- a/metis-normalization/src/test/resources/edm-record-internal.xml
+++ b/metis-normalization/src/test/resources/edm-record-internal.xml
@@ -2,7 +2,6 @@
- January, 1765
+ 190XPhysical location: UCD Library, UCD Library Special Collections,
UCD Letters, UCD L 1
diff --git a/metis-pattern-analysis/pom.xml b/metis-pattern-analysis/pom.xml
index 7b78760f22..da1296e9af 100644
--- a/metis-pattern-analysis/pom.xml
+++ b/metis-pattern-analysis/pom.xml
@@ -3,7 +3,7 @@
metis-frameworkeu.europeana.metis
- 9
+ 104.0.0metis-pattern-analysis
diff --git a/metis-repository/.gitignore b/metis-repository/.gitignore
deleted file mode 100644
index 37a5d1bb1e..0000000000
--- a/metis-repository/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/src/main/resources/application.properties
\ No newline at end of file
diff --git a/metis-repository/README.md b/metis-repository/README.md
deleted file mode 100644
index 0ee067b825..0000000000
--- a/metis-repository/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-#Metis Repository
-This module contains a simple record repository that
-can be deployed and used for testing harvesting. Currently
-it only supports OAI-PMH harvesting. Note that it doesn't
-completely implement the OAI standard; deviations are
-as follows:
-1. It confines itself to the requests that are actually
-executed by Metis. That means it only supports
-the `ListIdentifiers` and `GetRecord` requests.
-2. Pagination is not guaranteed to work when there are
-changes to the data. Specifically, if the data is changed
-while a user is harvesting, discrepancies (omissions of
-duplications of records) can occur. This is because
-pagination is executed directly on the database and the
-state of a previous request is not preserved.
\ No newline at end of file
diff --git a/metis-repository/metis-repository-rest/.gitignore b/metis-repository/metis-repository-rest/.gitignore
new file mode 100644
index 0000000000..0484be480b
--- /dev/null
+++ b/metis-repository/metis-repository-rest/.gitignore
@@ -0,0 +1,3 @@
+/src/main/resources/application.properties
+!/src/test/resources/repository-test.zip
+!/src/test/resources/repository-test-error.zip
\ No newline at end of file
diff --git a/metis-repository/metis-repository-rest/Dockerfile b/metis-repository/metis-repository-rest/Dockerfile
new file mode 100644
index 0000000000..73139364d7
--- /dev/null
+++ b/metis-repository/metis-repository-rest/Dockerfile
@@ -0,0 +1,4 @@
+FROM openjdk:11-jre-slim
+COPY target/*.jar app.jar
+EXPOSE 8080
+ENTRYPOINT ["java", "-jar", "/app.jar"]
diff --git a/metis-repository/metis-repository-rest/README.md b/metis-repository/metis-repository-rest/README.md
new file mode 100644
index 0000000000..27fcdcd917
--- /dev/null
+++ b/metis-repository/metis-repository-rest/README.md
@@ -0,0 +1,16 @@
+#Metis Repository
+This module contains a simple record repository that
+can be deployed and used for testing harvesting. Currently
+it only supports OAI-PMH harvesting. Note that it doesn't
+completely implement the OAI standard; deviations are
+as follows:
+
+1. It confines itself to the requests that are actually
+ executed by Metis. That means it only supports
+ the `ListIdentifiers` and `GetRecord` requests.
+2. Pagination is not guaranteed to work when there are
+ changes to the data. Specifically, if the data is changed
+ while a user is harvesting, discrepancies (omissions of
+ duplications of records) can occur. This is because
+ pagination is executed directly on the database and the
+ state of a previous request is not preserved.
\ No newline at end of file
diff --git a/metis-repository/metis-repository-rest/docker-compose.yml b/metis-repository/metis-repository-rest/docker-compose.yml
new file mode 100644
index 0000000000..f447b5b77f
--- /dev/null
+++ b/metis-repository/metis-repository-rest/docker-compose.yml
@@ -0,0 +1,26 @@
+version: '3.8'
+services:
+ mongo:
+ image: mongo:4.2.9
+ container_name: metis-repository-mongo
+ environment:
+ MONGO_INITDB_DATABASE: metis-repository
+ MONGO_INITDB_ROOT_USERNAME: guest
+ MONGO_INITDB_ROOT_PASSWORD: guest
+ ports:
+ - '27017:27017'
+ metis-repository-local:
+ image: europeana/metis-repository:develop
+ container_name: metis-repository-local
+ build:
+ context: ./
+ dockerfile: Dockerfile
+ ports:
+ - '8080:8080'
+ environment:
+ MONGO_HOSTS: metis-repository-mongo
+ volumes:
+ - /data/metis-configuration/metis-framework/metis-repository/metis-repository-rest/k8s/overlays/local/components/properties/application.properties:/application.properties
+ - /data/metis-configuration/k8s/common-components/log4j2-xml/log4j2.xml:/data/logging/log4j2.xml
+ depends_on:
+ - mongo
diff --git a/metis-repository/metis-repository-rest/pom.xml b/metis-repository/metis-repository-rest/pom.xml
new file mode 100644
index 0000000000..5fd8f1ef4c
--- /dev/null
+++ b/metis-repository/metis-repository-rest/pom.xml
@@ -0,0 +1,168 @@
+
+
+
+ metis-repository
+ eu.europeana.metis
+ 10
+
+ 4.0.0
+ metis-repository-rest
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+
+
+ org.springframework
+ spring-core
+
+
+ org.springframework
+ spring-webmvc
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ ${version.springdoc.openapi}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${version.jackson}
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ ${version.jackson}
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ ${version.jackson}
+
+
+ eu.europeana.metis
+ metis-common-utils
+ ${project.version}
+
+
+ eu.europeana.metis
+ metis-common-mongo
+ ${project.version}
+
+
+ eu.europeana.metis
+ metis-harvesting
+ ${project.version}
+
+
+ de.flapdoodle.embed
+ de.flapdoodle.embed.mongo
+ ${version.embedded.mongo}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${version.junit}
+ test
+
+
+ org.mockito
+ mockito-core
+
+
+ com.jayway.jsonpath
+ json-path
+ test
+
+
+ org.springframework
+ spring-test
+
+
+ io.swagger
+ swagger-annotations
+ ${version.swagger.annotations}
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+
+
+ co.elastic.apm
+ apm-agent-attach
+ ${version.elastic.apm}
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${version.spring.boot}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${version.spring.boot}
+
+
+
+ repackage
+
+
+
+
+
+
+
diff --git a/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/Application.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/Application.java
new file mode 100644
index 0000000000..d5b4965c04
--- /dev/null
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/Application.java
@@ -0,0 +1,21 @@
+package eu.europeana.metis.repository.rest;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * The Spring boot application entry point
+ */
+@SpringBootApplication
+public class Application {
+
+ /**
+ * The main spring boot method
+ *
+ * @param args application arguments
+ */
+ public static void main(String[] args){
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/ApplicationConfiguration.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/ApplicationConfiguration.java
new file mode 100644
index 0000000000..423169264a
--- /dev/null
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/ApplicationConfiguration.java
@@ -0,0 +1,116 @@
+package eu.europeana.metis.repository.rest.config;
+
+import com.mongodb.client.MongoClient;
+import eu.europeana.corelib.web.socks.SocksProxy;
+import eu.europeana.metis.mongo.connection.MongoClientProvider;
+import eu.europeana.metis.repository.rest.dao.RecordDao;
+import eu.europeana.metis.utils.CustomTruststoreAppender;
+import eu.europeana.metis.utils.CustomTruststoreAppender.TrustStoreConfigurationException;
+import eu.europeana.metis.utils.apm.ElasticAPMConfiguration;
+import javax.annotation.PreDestroy;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.web.multipart.support.StandardServletMultipartResolver;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * The web application making available the repository functionality. This provides all the
+ * configuration and is the starting point for all injections and beans. It also performs the
+ * required setup.
+ */
+@Configuration
+@EnableWebMvc
+@Import({ElasticAPMConfiguration.class})
+@ComponentScan(basePackages = {"eu.europeana.metis.repository.rest.controller",
+ "eu.europeana.metis.repository.rest.view"})
+public class ApplicationConfiguration implements WebMvcConfigurer {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationConfiguration.class);
+
+ private final ConfigurationPropertiesHolder propertiesHolder;
+
+ private final MongoClient mongoClient;
+
+ /**
+ * Constructor.
+ *
+ * @param propertiesHolder The properties.
+ * @throws TrustStoreConfigurationException If something goes wrong initializing the application
+ */
+ @Autowired
+ public ApplicationConfiguration(ConfigurationPropertiesHolder propertiesHolder) throws TrustStoreConfigurationException {
+ this.mongoClient = ApplicationConfiguration.initializeApplication(propertiesHolder);
+ this.propertiesHolder = propertiesHolder;
+ }
+
+ /**
+ * This method performs the initializing tasks for the application.
+ *
+ * @param propertiesHolder The properties.
+ * @return The Mongo client that can be used to access the mongo database.
+ * @throws TrustStoreConfigurationException In case a problem occurred with the truststore.
+ */
+ static MongoClient initializeApplication(ConfigurationPropertiesHolder propertiesHolder)
+ throws TrustStoreConfigurationException {
+
+ // Set the SOCKS proxy
+ if (propertiesHolder.isSocksProxyEnabled()) {
+ new SocksProxy(propertiesHolder.getSocksProxyHost(), propertiesHolder.getSocksProxyPort(),
+ propertiesHolder.getSocksProxyUsername(), propertiesHolder.getSocksProxyPassword()).init();
+ }
+
+ // Set the truststore.
+ LOGGER.info("Append default truststore with custom truststore");
+ if (StringUtils.isNotEmpty(propertiesHolder.getTruststorePath())
+ && StringUtils.isNotEmpty(propertiesHolder.getTruststorePassword())) {
+ CustomTruststoreAppender.appendCustomTrustoreToDefault(propertiesHolder.getTruststorePath(),
+ propertiesHolder.getTruststorePassword());
+ }
+
+ // Initialize the Mongo connection
+ LOGGER.info("Creating Mongo connection");
+ return new MongoClientProvider<>(propertiesHolder.getMongoProperties()).createMongoClient();
+ }
+
+ @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
+ public StandardServletMultipartResolver getMultipartResolver() {
+ return new StandardServletMultipartResolver();
+ }
+
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/swagger-ui/**")
+ .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
+ .resourceChain(false);
+ }
+
+ @Override
+ public void addViewControllers(ViewControllerRegistry registry) {
+ registry.addRedirectViewController("/", "/swagger-ui/index.html");
+ }
+
+ @Bean
+ public RecordDao getRecordDao() {
+ return new RecordDao(mongoClient, propertiesHolder.getMongoRecordDb());
+ }
+
+ /**
+ * Closes any connections previous acquired.
+ */
+ @PreDestroy
+ public void close() {
+ if (mongoClient != null) {
+ mongoClient.close();
+ }
+ }
+}
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/config/ApplicationPropertiesHolder.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/ConfigurationPropertiesHolder.java
similarity index 69%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/config/ApplicationPropertiesHolder.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/ConfigurationPropertiesHolder.java
index be98a8ed97..4ff3b06249 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/config/ApplicationPropertiesHolder.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/ConfigurationPropertiesHolder.java
@@ -1,15 +1,15 @@
-package eu.europeana.metis.repository.config;
+package eu.europeana.metis.repository.rest.config;
+import eu.europeana.metis.mongo.connection.MongoProperties;
+import eu.europeana.metis.mongo.connection.MongoProperties.ReadPreferenceValue;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* Holder for the property values from Spring injection/property loading.
*/
@Component
-@PropertySource({"classpath:application.properties"})
-public class ApplicationPropertiesHolder {
+public class ConfigurationPropertiesHolder {
// Socks proxy
@Value("${socks.proxy.enabled}")
@@ -27,7 +27,7 @@ public class ApplicationPropertiesHolder {
@Value("${mongo.hosts}")
private String[] mongoHosts;
@Value("${mongo.port}")
- private int mongoPort;
+ private int[] mongoPorts;
@Value("${mongo.authentication.db}")
private String mongoAuthenticationDb;
@Value("${mongo.username}")
@@ -35,13 +35,13 @@ public class ApplicationPropertiesHolder {
@Value("${mongo.password}")
private String mongoPassword;
@Value("${mongo.enable.ssl}")
- private boolean mongoEnableSsl;
+ private boolean mongoEnableSSL;
@Value("${mongo.application.name}")
private String mongoApplicationName;
@Value("${mongo.record.db}")
private String mongoRecordDb;
- // truststore
+ // Truststore
@Value("${truststore.path}")
private String truststorePath;
@Value("${truststore.password}")
@@ -67,34 +67,6 @@ public String getSocksProxyPassword() {
return socksProxyPassword;
}
- public String[] getMongoHosts() {
- return mongoHosts.clone();
- }
-
- public int getMongoPort() {
- return mongoPort;
- }
-
- public String getMongoAuthenticationDb() {
- return mongoAuthenticationDb;
- }
-
- public String getMongoUsername() {
- return mongoUsername;
- }
-
- public String getMongoPassword() {
- return mongoPassword;
- }
-
- public boolean isMongoEnableSsl() {
- return mongoEnableSsl;
- }
-
- public String getMongoApplicationName() {
- return mongoApplicationName;
- }
-
public String getMongoRecordDb() {
return mongoRecordDb;
}
@@ -106,4 +78,12 @@ public String getTruststorePath() {
public String getTruststorePassword() {
return truststorePassword;
}
+
+ public MongoProperties getMongoProperties() {
+ final MongoProperties mongoProperties = new MongoProperties<>(
+ IllegalArgumentException::new);
+ mongoProperties.setAllProperties(mongoHosts, mongoPorts, mongoAuthenticationDb, mongoUsername,
+ mongoPassword, mongoEnableSSL, ReadPreferenceValue.PRIMARY_PREFERRED, mongoApplicationName);
+ return mongoProperties;
+ }
}
diff --git a/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/SwaggerConfig.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/SwaggerConfig.java
new file mode 100644
index 0000000000..a821a37adf
--- /dev/null
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/config/SwaggerConfig.java
@@ -0,0 +1,31 @@
+package eu.europeana.metis.repository.rest.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Config for Swagger documentation
+ */
+@Configuration
+public class SwaggerConfig {
+
+ /**
+ * The open api documentation docket
+ *
+ * @return the docket configuration
+ */
+ @Bean
+ public OpenAPI openAPI(){
+ return new OpenAPI()
+ .info(new Info()
+ .title("Metis Repository REST API")
+ .description("Metis Repository REST API for Europeana")
+ .version("v1")
+ .license(new License()
+ .name("EUPL Licence v1.2")
+ .url("https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12")));
+ }
+}
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/HttpHarvestController.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/HttpHarvestController.java
similarity index 69%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/rest/HttpHarvestController.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/HttpHarvestController.java
index eb56b76e1e..a124e279a0 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/HttpHarvestController.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/HttpHarvestController.java
@@ -1,7 +1,7 @@
-package eu.europeana.metis.repository.rest;
+package eu.europeana.metis.repository.rest.controller;
-import eu.europeana.metis.repository.dao.Record;
-import eu.europeana.metis.repository.dao.RecordDao;
+import eu.europeana.metis.repository.rest.dao.Record;
+import eu.europeana.metis.repository.rest.dao.RecordDao;
import eu.europeana.metis.utils.RestEndpoints;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -17,8 +17,6 @@
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -39,12 +37,11 @@
public class HttpHarvestController {
public static final String CONTROLLER_TAG_NAME = "HttpHarvestController";
- private static final Logger LOGGER = LoggerFactory.getLogger(HttpHarvestController.class);
private RecordDao recordDao;
@Autowired
- void setRecordDao(RecordDao recordDao) {
+ public void setRecordDao(RecordDao recordDao) {
this.recordDao = recordDao;
}
@@ -60,28 +57,25 @@ void setRecordDao(RecordDao recordDao) {
@ApiOperation(value = "The dataset is exported as a zip file for harvesting by Metis. Records "
+ "that are marked as deleted will be excluded from the resulting zip file.")
@ApiResponses(value = {@ApiResponse(code = 404, message = "No records for this dataset."),
- @ApiResponse(code = 500, message = "Error obtaining the records.")})
+ @ApiResponse(code = 500, message = "Error obtaining the records.")})
public ResponseEntity getDatasetRecords(
- @ApiParam(value = "Dataset ID (new or existing)", required = true) @PathVariable("dataset") String datasetId) {
+ @ApiParam(value = "Dataset ID (new or existing)", required = true) @PathVariable("dataset") String datasetId) {
// Create zip file in memory (and keep track on whether there are any records).
final AtomicBoolean recordsFound = new AtomicBoolean(false);
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (final ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
- final Stream recordList = recordDao.getAllRecordsFromDataset(datasetId);
- recordList.forEach(record -> {
- if (!record.isDeleted()) {
- addRecordToZipFile(record, zipOutputStream);
+ final Stream allRecordsFromDataset = recordDao.getAllRecordsFromDataset(datasetId);
+ allRecordsFromDataset.forEach(datasetRecord -> {
+ if (!datasetRecord.isDeleted()) {
+ addRecordToZipFile(datasetRecord, zipOutputStream);
recordsFound.set(true);
}
});
zipOutputStream.finish();
zipOutputStream.flush();
} catch (RuntimeException | IOException e) {
-
- // Report any problems (also for individual records) as 500 code.
- LOGGER.warn("There was problems while zipping the records.", e);
- throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "There was problems while zipping the records.", e);
}
// If there are no records found, we return a 404 code.
@@ -91,18 +85,18 @@ public ResponseEntity getDatasetRecords(
// Return bytes as zip file.
return ResponseEntity.ok()
- .header("Content-Disposition", "attachment; filename=\"" + datasetId + ".zip\"")
- .body(byteArrayOutputStream.toByteArray());
+ .header("Content-Disposition", "attachment; filename=\"" + datasetId + ".zip\"")
+ .body(byteArrayOutputStream.toByteArray());
}
- private static void addRecordToZipFile(Record record, ZipOutputStream zipOutputStream) {
+ private static void addRecordToZipFile(Record oaiRecord, ZipOutputStream zipOutputStream) {
try {
- zipOutputStream.putNextEntry(new ZipEntry(record.getRecordId() + ".xml"));
- zipOutputStream.write(record.getEdmRecord().getBytes(StandardCharsets.UTF_8));
+ zipOutputStream.putNextEntry(new ZipEntry(oaiRecord.getRecordId() + ".xml"));
+ zipOutputStream.write(oaiRecord.getEdmRecord().getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
} catch (IOException e) {
throw new IllegalStateException(
- "There was a problem while preparing the records to be zipped.", e);
+ "There was a problem while preparing the records to be zipped.", e);
}
}
}
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/OaiPmhController.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/OaiPmhController.java
similarity index 70%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/rest/OaiPmhController.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/OaiPmhController.java
index e9bec91854..8aaba0a677 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/OaiPmhController.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/OaiPmhController.java
@@ -1,8 +1,8 @@
-package eu.europeana.metis.repository.rest;
+package eu.europeana.metis.repository.rest.controller;
import com.lyncode.xml.exceptions.XmlWriteException;
-import eu.europeana.metis.repository.dao.Record;
-import eu.europeana.metis.repository.dao.RecordDao;
+import eu.europeana.metis.repository.rest.dao.Record;
+import eu.europeana.metis.repository.rest.dao.RecordDao;
import eu.europeana.metis.utils.RestEndpoints;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -28,8 +28,6 @@
import org.dspace.xoai.services.impl.SimpleResumptionTokenFormat;
import org.dspace.xoai.xml.XmlWriter;
import org.dspace.xoai.xml.XmlWriter.WriterContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -49,12 +47,11 @@
public class OaiPmhController {
public static final String CONTROLLER_TAG_NAME = "OaiPmhController";
- private static final Logger LOGGER = LoggerFactory.getLogger(OaiPmhController.class);
private RecordDao recordDao;
@Autowired
- void setRecordDao(RecordDao recordDao) {
+ public void setRecordDao(RecordDao recordDao) {
this.recordDao = recordDao;
}
@@ -64,18 +61,18 @@ void setRecordDao(RecordDao recordDao) {
@ResponseBody
@ApiOperation(value = "OAI endpoint (supporting only the ListIdentifiers and GetRecord verbs)")
@ApiResponses(value = {@ApiResponse(code = 400, message = "Illegal OAI request"),
- @ApiResponse(code = 404, message = "Unknown dataset or record ID"),
- @ApiResponse(code = 500, message = "Error processing the request")})
+ @ApiResponse(code = 404, message = "Unknown dataset or record ID"),
+ @ApiResponse(code = 500, message = "Error processing the request")})
public String oaiPmh(
- @ApiParam(value = "The verb (ListIdentifiers or GetRecords)", required = true) @QueryParam("verb") String verb,
- @ApiParam(value = "The set (required for ListIdentifiers)") @QueryParam("set") String set,
- @ApiParam(value = "The metadataPrefix (only 'edm' is supported.)", required = true) @QueryParam("metadataPrefix") String metadataPrefix,
- @ApiParam(value = "The record identifier (required for GetRecord)") @QueryParam("identifier") String identifier) {
+ @ApiParam(value = "The verb (ListIdentifiers or GetRecords)", required = true) @QueryParam("verb") String verb,
+ @ApiParam(value = "The set (required for ListIdentifiers)") @QueryParam("set") String set,
+ @ApiParam(value = "The metadataPrefix (only 'edm' is supported.)", required = true) @QueryParam("metadataPrefix") String metadataPrefix,
+ @ApiParam(value = "The record identifier (required for GetRecord)") @QueryParam("identifier") String identifier) {
// Check the metadata prefix
if (!"edm".equals(metadataPrefix)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
- "Unsupported metadataPrefix value: " + metadataPrefix);
+ "Unsupported metadataPrefix value: " + metadataPrefix);
}
// Check the verb and delegate
@@ -90,7 +87,8 @@ public String oaiPmh(
// Compile the result
final OAIPMH result = new OAIPMH().withVerb(verbResult).withResponseDate(new Date())
- .withRequest(new Request(RestEndpoints.REPOSITORY_OAI_ENDPOINT).withVerbType(verbResult.getType()));
+ .withRequest(
+ new Request(RestEndpoints.REPOSITORY_OAI_ENDPOINT).withVerbType(verbResult.getType()));
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final WriterContext context = new WriterContext(Granularity.Day, new SimpleResumptionTokenFormat());
@@ -102,9 +100,8 @@ public String oaiPmh(
}
return outputStream.toString();
} catch (XMLStreamException | XmlWriteException e) {
- LOGGER.warn("A problem occurred while serializing the response.", e);
- throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
- "A problem occurred while serializing the response.", e);
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "A problem occurred while serializing the response.",
+ e);
}
}
@@ -114,7 +111,7 @@ private ListIdentifiers listIdentifiers(String setSpec) {
}
final ListIdentifiers result = new ListIdentifiers();
recordDao.getAllRecordsFromDataset(setSpec)
- .forEach(record -> result.getHeaders().add(createHeader(record)));
+ .forEach(datasetRecord -> result.getHeaders().add(createHeader(datasetRecord)));
if (result.getHeaders().isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No records found for this dataset.");
}
@@ -125,20 +122,20 @@ private GetRecord getRecord(String identifier) {
if (StringUtils.isBlank(identifier)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Please provide an identifier");
}
- final Record record = recordDao.getRecord(identifier);
- if (record == null) {
+ final Record oaiRecord = recordDao.getRecord(identifier);
+ if (oaiRecord == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
- "No record found for this identifier.");
+ "No record found for this identifier.");
}
final org.dspace.xoai.model.oaipmh.Record resultRecord = new org.dspace.xoai.model.oaipmh.Record()
- .withHeader(createHeader(record)).withMetadata(new Metadata(record.getEdmRecord()));
+ .withHeader(createHeader(oaiRecord)).withMetadata(new Metadata(oaiRecord.getEdmRecord()));
return new GetRecord(resultRecord);
}
- private static Header createHeader(Record record) {
- final Header result = new Header().withDatestamp(Date.from(record.getDateStamp()))
- .withSetSpec(record.getDatasetId()).withIdentifier(record.getRecordId());
- if (record.isDeleted()) {
+ private static Header createHeader(Record oaiRecord) {
+ final Header result = new Header().withDatestamp(Date.from(oaiRecord.getDateStamp()))
+ .withSetSpec(oaiRecord.getDatasetId()).withIdentifier(oaiRecord.getRecordId());
+ if (oaiRecord.isDeleted()) {
result.withStatus(Status.DELETED);
}
return result;
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/RecordController.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/RecordController.java
similarity index 83%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/rest/RecordController.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/RecordController.java
index e44cbc8656..3150dc6fc6 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/RecordController.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/controller/RecordController.java
@@ -1,9 +1,11 @@
-package eu.europeana.metis.repository.rest;
+package eu.europeana.metis.repository.rest.controller;
import eu.europeana.metis.harvesting.HarvesterException;
import eu.europeana.metis.harvesting.http.HttpHarvesterImpl;
-import eu.europeana.metis.repository.dao.Record;
-import eu.europeana.metis.repository.dao.RecordDao;
+import eu.europeana.metis.repository.rest.dao.Record;
+import eu.europeana.metis.repository.rest.dao.RecordDao;
+import eu.europeana.metis.repository.rest.view.InsertionResult;
+import eu.europeana.metis.repository.rest.view.RecordView;
import eu.europeana.metis.utils.CompressedFileExtension;
import eu.europeana.metis.utils.RestEndpoints;
import io.swagger.annotations.Api;
@@ -46,7 +48,7 @@
*/
@RestController
@Tags(@Tag(name = RecordController.CONTROLLER_TAG_NAME,
- description = "Controller providing access to record management functionality."))
+ description = "Controller providing access to record management functionality."))
@Api(tags = RecordController.CONTROLLER_TAG_NAME)
public class RecordController {
@@ -59,7 +61,7 @@ public class RecordController {
private RecordDao recordDao;
@Autowired
- void setRecordDao(RecordDao recordDao){
+ public void setRecordDao(RecordDao recordDao) {
this.recordDao = recordDao;
}
@@ -86,7 +88,8 @@ void setRecordDao(RecordDao recordDao){
public InsertionResult saveRecord(
@ApiParam(value = "Record ID (new or existing)", required = true) @PathVariable("recordId") String recordId,
@ApiParam(value = "Dataset ID (new or existing)", required = true) @RequestParam("datasetId") String datasetId,
- @ApiParam(value = "Date stamp (in ISO format)") @RequestParam(name = "dateStamp", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant dateStamp,
+ @ApiParam(value = "Date stamp (in ISO format)") @RequestParam(name = "dateStamp", required = false)
+ @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant dateStamp,
@ApiParam(value = "Whether the record is to be marked as deleted", required = true) @RequestParam("markAsDeleted") boolean markAsDeleted,
@ApiParam(value = "The actual (EDM/RDF) record", required = true) @RequestBody String edmRecord) {
verifyDatasetId(datasetId);
@@ -98,7 +101,7 @@ public InsertionResult saveRecord(
/**
* Save multiple records into the database
- *
+ *
* TODO The swagger console does not pick up the @ApiParam settings.
*
* @param datasetId - The id of the dataset which the record belongs to
@@ -116,14 +119,15 @@ public InsertionResult saveRecord(
+ "dataset ID. If a record ID already exists, the record is overwritten. Note that record "
+ "IDs are normalized to contain only the characters a-z, A-Z, 0-9 and `_`.")
@ApiResponses(value = {@ApiResponse(code = 404, message = "Illegal dataset or record ID"),
- @ApiResponse(code = 500, message = "Error processing the file archive")})
+ @ApiResponse(code = 500, message = "Error processing the file archive")})
public InsertionResult saveRecords(
- @ApiParam(value = "Dataset ID (new or existing)", required = true) @RequestParam("datasetId") String datasetId,
- @ApiParam(value = "Date stamp (in ISO format)") @RequestParam(name = "dateStamp", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant dateStamp,
- @ApiParam(value = "The (EDM/RDF) records", required = true) @RequestPart MultipartFile recordsZipFile) {
+ @ApiParam(value = "Dataset ID (new or existing)", required = true) @RequestParam("datasetId") String datasetId,
+ @ApiParam(value = "Date stamp (in ISO format)") @RequestParam(name = "dateStamp", required = false)
+ @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant dateStamp,
+ @ApiParam(value = "The (EDM/RDF) records", required = true) @RequestPart MultipartFile recordsZipFile) {
verifyDatasetId(datasetId);
final InsertionResult result = new InsertionResult(datasetId,
- Objects.requireNonNullElseGet(dateStamp, Instant::now));
+ Objects.requireNonNullElseGet(dateStamp, Instant::now));
try (final InputStream inputStream = recordsZipFile.getInputStream()) {
new HttpHarvesterImpl().harvestRecords(inputStream, CompressedFileExtension.ZIP, entry -> {
final byte[] content = entry.getEntryContent().readAllBytes();
@@ -142,7 +146,7 @@ public InsertionResult saveRecords(
/**
* Update record header (metadata information) of the record given by the record ID.
*
- * @param recordId - A unique record id
+ * @param recordId - A unique record id
* @param datasetId - The id of the dataset which the record belongs to
* @param dateStamp - Last time the record was updated. It can also be the date of creation
* @return a summary of the performed actions.
@@ -157,14 +161,14 @@ public InsertionResult saveRecords(
public InsertionResult updateRecordHeader(
@ApiParam(value = "Record ID (existing)", required = true) @PathVariable("recordId") String recordId,
@ApiParam(value = "Dataset ID (new or existing)", required = true) @RequestParam("datasetId") String datasetId,
- @ApiParam(value = "Date stamp (in ISO format)") @RequestParam(name = "dateStamp", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant dateStamp,
+ @ApiParam(value = "Date stamp (in ISO format)") @RequestParam(name = "dateStamp", required = false)
+ @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant dateStamp,
@ApiParam(value = "Whether the record is to be marked as deleted", required = true) @RequestParam("markAsDeleted") boolean markAsDeleted) {
- final Record record = recordDao.getRecord(recordId);
- if (record == null) {
- throw new ResponseStatusException(HttpStatus.NOT_FOUND,
- "No record found for this identifier.");
+ final Record oaiRecord = recordDao.getRecord(recordId);
+ if (oaiRecord == null) {
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No record found for this identifier.");
}
- return saveRecord(recordId, datasetId, dateStamp, markAsDeleted, record.getEdmRecord());
+ return saveRecord(recordId, datasetId, dateStamp, markAsDeleted, oaiRecord.getEdmRecord());
}
private void saveRecord(String providedRecordId, String edmRecord, InsertionResult result,
@@ -195,13 +199,12 @@ private void saveRecord(String providedRecordId, String edmRecord, InsertionResu
@ApiResponse(code = 500, message = "Error processing the request")})
public RecordView getRecord(
@ApiParam(value = "Record ID", required = true) @PathVariable("recordId") String recordId) {
- final Record record = recordDao.getRecord(recordId);
- if (record == null) {
- throw new ResponseStatusException(HttpStatus.NOT_FOUND,
- "No record found for this identifier.");
+ final Record oaiRecord = recordDao.getRecord(recordId);
+ if (oaiRecord == null) {
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No record found for this identifier.");
}
- return new RecordView(record.getRecordId(), record.getDatasetId(), record.getDateStamp(),
- record.isDeleted(), record.getEdmRecord());
+ return new RecordView(oaiRecord.getRecordId(), oaiRecord.getDatasetId(), oaiRecord.getDateStamp(),
+ oaiRecord.isDeleted(), oaiRecord.getEdmRecord());
}
@DeleteMapping(value = RestEndpoints.REPOSITORY_RECORDS_RECORD_ID)
@@ -214,8 +217,7 @@ public RecordView getRecord(
public void deleteRecord(
@ApiParam(value = "Record ID", required = true) @PathVariable("recordId") String recordId) {
if (!recordDao.deleteRecord(recordId)) {
- throw new ResponseStatusException(HttpStatus.NOT_FOUND,
- "No record found for this identifier.");
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No record found for this identifier.");
}
}
@@ -230,6 +232,6 @@ private static String normalizeRecordId(String suggestedRecordId) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid record ID.");
}
return UNSUPPORTED_CHARACTERS_PATTERN.matcher(suggestedRecordId)
- .replaceAll(REPLACEMENT_CHARACTER);
+ .replaceAll(REPLACEMENT_CHARACTER);
}
}
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/dao/Record.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/dao/Record.java
similarity index 97%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/dao/Record.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/dao/Record.java
index 6aab0af349..0b1f1cf14f 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/dao/Record.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/dao/Record.java
@@ -1,4 +1,4 @@
-package eu.europeana.metis.repository.dao;
+package eu.europeana.metis.repository.rest.dao;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import dev.morphia.annotations.Entity;
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/dao/RecordDao.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/dao/RecordDao.java
similarity index 98%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/dao/RecordDao.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/dao/RecordDao.java
index 560ede8b81..572970600a 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/dao/RecordDao.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/dao/RecordDao.java
@@ -1,4 +1,4 @@
-package eu.europeana.metis.repository.dao;
+package eu.europeana.metis.repository.rest.dao;
import static eu.europeana.metis.utils.CommonStringValues.CRLF_PATTERN;
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/InsertionResult.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/InsertionResult.java
similarity index 94%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/rest/InsertionResult.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/InsertionResult.java
index ac669505b2..bd840a3023 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/InsertionResult.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/InsertionResult.java
@@ -1,4 +1,4 @@
-package eu.europeana.metis.repository.rest;
+package eu.europeana.metis.repository.rest.view;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.time.Instant;
@@ -6,7 +6,7 @@
import java.util.HashSet;
import java.util.Set;
-class InsertionResult {
+public class InsertionResult {
private final String datasetId;
@JsonSerialize(using = InstantSerializer.class)
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/InstantSerializer.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/InstantSerializer.java
similarity index 92%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/rest/InstantSerializer.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/InstantSerializer.java
index 0aa0f178ac..4803920e03 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/InstantSerializer.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/InstantSerializer.java
@@ -1,4 +1,4 @@
-package eu.europeana.metis.repository.rest;
+package eu.europeana.metis.repository.rest.view;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/RecordView.java b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/RecordView.java
similarity index 95%
rename from metis-repository/src/main/java/eu/europeana/metis/repository/rest/RecordView.java
rename to metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/RecordView.java
index be4665ac8d..e4fdaf3f4b 100644
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/rest/RecordView.java
+++ b/metis-repository/metis-repository-rest/src/main/java/eu/europeana/metis/repository/rest/view/RecordView.java
@@ -1,4 +1,4 @@
-package eu.europeana.metis.repository.rest;
+package eu.europeana.metis.repository.rest.view;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
diff --git a/metis-repository/metis-repository-rest/src/main/resources/application.properties.example b/metis-repository/metis-repository-rest/src/main/resources/application.properties.example
new file mode 100644
index 0000000000..208481f3ca
--- /dev/null
+++ b/metis-repository/metis-repository-rest/src/main/resources/application.properties.example
@@ -0,0 +1,33 @@
+#Spring
+spring.servlet.multipart.max-file-size=5MB
+spring.servlet.multipart.max-request-size=5MB
+spring.autoconfigure.exclude=\
+ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration, \
+ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, \
+ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
+springdoc.packages-to-scan=eu.europeana.metis.repository.rest
+springdoc.paths-to-match=/**
+
+#Server
+server.error.whitelabel.enabled=false
+
+#Socks Proxy
+socks.proxy.enabled=false
+socks.proxy.host=
+socks.proxy.port=
+socks.proxy.username=
+socks.proxy.password=
+
+#Mongo
+mongo.hosts=
+mongo.port=
+mongo.authentication.db=
+mongo.username=
+mongo.password=
+mongo.enable.ssl=
+mongo.application.name=
+mongo.record.db=
+
+#Truststore
+truststore.path=
+truststore.password=
diff --git a/metis-repository/metis-repository-rest/src/main/resources/log4j2.xml b/metis-repository/metis-repository-rest/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..71275a030c
--- /dev/null
+++ b/metis-repository/metis-repository-rest/src/main/resources/log4j2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/metis-repository/src/test/java/eu/europeana/metis/repository/dao/RecordDaoTest.java b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/dao/RecordDaoTest.java
similarity index 97%
rename from metis-repository/src/test/java/eu/europeana/metis/repository/dao/RecordDaoTest.java
rename to metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/dao/RecordDaoTest.java
index 1fc17bd4be..c3cc962cd0 100644
--- a/metis-repository/src/test/java/eu/europeana/metis/repository/dao/RecordDaoTest.java
+++ b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/dao/RecordDaoTest.java
@@ -1,20 +1,21 @@
package eu.europeana.metis.repository.dao;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import eu.europeana.metis.mongo.embedded.EmbeddedLocalhostMongo;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
+import eu.europeana.metis.repository.rest.dao.Record;
+import eu.europeana.metis.repository.rest.dao.RecordDao;
import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.assertNull;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
class RecordDaoTest {
diff --git a/metis-repository/src/test/java/eu/europeana/metis/repository/dao/RecordTest.java b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/dao/RecordTest.java
similarity index 97%
rename from metis-repository/src/test/java/eu/europeana/metis/repository/dao/RecordTest.java
rename to metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/dao/RecordTest.java
index 13c4b657ef..6c9c1e597d 100644
--- a/metis-repository/src/test/java/eu/europeana/metis/repository/dao/RecordTest.java
+++ b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/dao/RecordTest.java
@@ -1,15 +1,15 @@
package eu.europeana.metis.repository.dao;
-import org.bson.types.ObjectId;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.time.Instant;
-
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import eu.europeana.metis.repository.rest.dao.Record;
+import java.time.Instant;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
class RecordTest {
diff --git a/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/HttpHarvestControllerTest.java b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/HttpHarvestControllerTest.java
new file mode 100644
index 0000000000..ede27cb9bc
--- /dev/null
+++ b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/HttpHarvestControllerTest.java
@@ -0,0 +1,160 @@
+package eu.europeana.metis.repository.rest;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import eu.europeana.metis.repository.rest.controller.HttpHarvestController;
+import eu.europeana.metis.repository.rest.dao.Record;
+import eu.europeana.metis.repository.rest.dao.RecordDao;
+import eu.europeana.metis.utils.RestEndpoints;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.server.ResponseStatusException;
+
+class HttpHarvestControllerTest {
+
+ private RecordDao recordDaoMock;
+ private MockMvc httpHarvestControllerMock;
+ private HttpHarvestController httpHarvestController;
+
+ @BeforeEach
+ void setup() {
+ recordDaoMock = mock(RecordDao.class);
+ httpHarvestController = new HttpHarvestController();
+ httpHarvestControllerMock = MockMvcBuilders.standaloneSetup(httpHarvestController)
+ .build();
+ }
+
+ @AfterEach
+ void cleanUp() {
+ reset(recordDaoMock);
+ }
+
+ @Test
+ void testGetDatasetRecords_expectSuccess() throws IOException {
+ Stream recordsStreamMock = makeStreamRecords();
+ when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenReturn(recordsStreamMock);
+ httpHarvestController.setRecordDao(recordDaoMock);
+
+ ResponseEntity resultZip = httpHarvestController.getDatasetRecords("datasetId");
+
+ assertTrue(allContentIsReturned(resultZip.getBody()));
+
+ }
+
+ @Test
+ void testGetDatasetRecords_expectInternalServerError() {
+ when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenThrow(RuntimeException.class);
+ httpHarvestController.setRecordDao(recordDaoMock);
+
+ RuntimeException expectedException = assertThrows(ResponseStatusException.class, () -> {
+ httpHarvestController.getDatasetRecords("datasetId");
+ });
+
+ assertEquals(
+ "500 INTERNAL_SERVER_ERROR \"There was problems while zipping the records.\"; nested exception is java.lang.RuntimeException",
+ expectedException.getMessage());
+
+ }
+
+ @Test
+ void testGetDatasetRecords_expectNotFoundError() {
+ when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenReturn(Stream.empty());
+ httpHarvestController.setRecordDao(recordDaoMock);
+
+ RuntimeException expectedException = assertThrows(ResponseStatusException.class, () -> {
+ httpHarvestController.getDatasetRecords("datasetId");
+ });
+
+ assertEquals("404 NOT_FOUND \"No records found for this dataset.\"", expectedException.getMessage());
+
+ }
+
+ @Test
+ void getDatasetRecordsViaController() throws Exception {
+ Stream recordsStreamMock = makeStreamRecords();
+ when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenReturn(recordsStreamMock);
+ httpHarvestController.setRecordDao(recordDaoMock);
+ MvcResult resultMvc = httpHarvestControllerMock.perform(get(RestEndpoints.REPOSITORY_HTTP_ENDPOINT_ZIP, "datasetId")
+ .content(""))
+ .andDo(print())
+ .andExpect(status().is(200))
+ .andReturn();
+
+ assertTrue(allContentIsReturned(resultMvc.getResponse().getContentAsByteArray()));
+ verify(recordDaoMock, times(1)).getAllRecordsFromDataset("datasetId");
+ }
+
+ @Test
+ void getDatasetRecordsViaController_expectInternalError() throws Exception {
+ when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenThrow(RuntimeException.class);
+ httpHarvestController.setRecordDao(recordDaoMock);
+ httpHarvestControllerMock.perform(get(RestEndpoints.REPOSITORY_HTTP_ENDPOINT_ZIP, "datasetId")
+ .content(""))
+ .andDo(print())
+ .andExpect(status().is(500))
+ .andExpect(content().string(""));
+
+ verify(recordDaoMock, times(1)).getAllRecordsFromDataset("datasetId");
+ }
+
+ @Test
+ void getDatasetRecordsViaController_expectNotFoundError() throws Exception {
+ when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenReturn(Stream.empty());
+ httpHarvestController.setRecordDao(recordDaoMock);
+ httpHarvestControllerMock.perform(get(RestEndpoints.REPOSITORY_HTTP_ENDPOINT_ZIP, "datasetId")
+ .content(""))
+ .andDo(print())
+ .andExpect(status().is(404))
+ .andExpect(content().string(""));
+
+ verify(recordDaoMock, times(1)).getAllRecordsFromDataset("datasetId");
+ }
+
+ private Stream makeStreamRecords() {
+ Instant instantForTest = Instant.now();
+ Record record1 = new Record("recordId1", "datasetId", instantForTest, false, "edmRecord1");
+ Record record2 = new Record("recordId2", "datasetId", instantForTest, false, "edmRecord2");
+ Record record3 = new Record("recordId3", "datasetId", instantForTest, false, "edmRecord3");
+ return Stream.of(record1, record2, record3);
+ }
+
+ private boolean allContentIsReturned(byte[] result) throws IOException {
+ ZipEntry zEntry;
+ boolean areAllRecordsInZip = true;
+
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Objects.requireNonNull(result));
+ ZipInputStream zipIs = new ZipInputStream(new BufferedInputStream(byteArrayInputStream));
+ while ((zEntry = zipIs.getNextEntry()) != null) {
+ areAllRecordsInZip = areAllRecordsInZip && (zEntry.getName().contains("recordId1") || zEntry.getName().contains("recordId2")
+ || zEntry.getName().contains("recordId3"));
+ }
+ zipIs.close();
+
+ return areAllRecordsInZip;
+ }
+
+}
diff --git a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/InsertionResultTest.java b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/InsertionResultTest.java
similarity index 97%
rename from metis-repository/src/test/java/eu/europeana/metis/repository/rest/InsertionResultTest.java
rename to metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/InsertionResultTest.java
index 7bcba3aebd..7211db9ebe 100644
--- a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/InsertionResultTest.java
+++ b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/InsertionResultTest.java
@@ -1,13 +1,13 @@
package eu.europeana.metis.repository.rest;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import eu.europeana.metis.repository.rest.view.InsertionResult;
import java.time.Instant;
import java.util.Set;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
class InsertionResultTest {
diff --git a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/InstantSerializerTest.java b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/InstantSerializerTest.java
similarity index 94%
rename from metis-repository/src/test/java/eu/europeana/metis/repository/rest/InstantSerializerTest.java
rename to metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/InstantSerializerTest.java
index aaa18f4183..655835482f 100644
--- a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/InstantSerializerTest.java
+++ b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/InstantSerializerTest.java
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
+import eu.europeana.metis.repository.rest.view.InstantSerializer;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
diff --git a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/OaiPmhControllerTest.java b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/OaiPmhControllerTest.java
similarity index 98%
rename from metis-repository/src/test/java/eu/europeana/metis/repository/rest/OaiPmhControllerTest.java
rename to metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/OaiPmhControllerTest.java
index 30e714fac7..6a4bba8f8a 100644
--- a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/OaiPmhControllerTest.java
+++ b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/OaiPmhControllerTest.java
@@ -10,8 +10,9 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
-import eu.europeana.metis.repository.dao.Record;
-import eu.europeana.metis.repository.dao.RecordDao;
+import eu.europeana.metis.repository.rest.controller.OaiPmhController;
+import eu.europeana.metis.repository.rest.dao.Record;
+import eu.europeana.metis.repository.rest.dao.RecordDao;
import eu.europeana.metis.utils.RestEndpoints;
import java.io.IOException;
import java.net.URI;
diff --git a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/RecordControllerTest.java b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/RecordControllerTest.java
similarity index 98%
rename from metis-repository/src/test/java/eu/europeana/metis/repository/rest/RecordControllerTest.java
rename to metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/RecordControllerTest.java
index d6b62d53fb..ad0a8ac089 100644
--- a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/RecordControllerTest.java
+++ b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/RecordControllerTest.java
@@ -20,8 +20,10 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import eu.europeana.metis.repository.dao.Record;
-import eu.europeana.metis.repository.dao.RecordDao;
+import eu.europeana.metis.repository.rest.controller.RecordController;
+import eu.europeana.metis.repository.rest.dao.Record;
+import eu.europeana.metis.repository.rest.dao.RecordDao;
+import eu.europeana.metis.repository.rest.view.RecordView;
import eu.europeana.metis.utils.RestEndpoints;
import java.io.InputStream;
import java.time.Instant;
diff --git a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/RecordViewTest.java b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/RecordViewTest.java
similarity index 94%
rename from metis-repository/src/test/java/eu/europeana/metis/repository/rest/RecordViewTest.java
rename to metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/RecordViewTest.java
index ead7b8c8aa..0720e0d7f7 100644
--- a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/RecordViewTest.java
+++ b/metis-repository/metis-repository-rest/src/test/java/eu/europeana/metis/repository/rest/RecordViewTest.java
@@ -3,6 +3,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import eu.europeana.metis.repository.rest.view.RecordView;
import java.time.Instant;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/metis-repository/src/test/resources/record-test.xml b/metis-repository/metis-repository-rest/src/test/resources/record-test.xml
similarity index 75%
rename from metis-repository/src/test/resources/record-test.xml
rename to metis-repository/metis-repository-rest/src/test/resources/record-test.xml
index f2e6074998..97ad167e0c 100644
--- a/metis-repository/src/test/resources/record-test.xml
+++ b/metis-repository/metis-repository-rest/src/test/resources/record-test.xml
@@ -1,23 +1,10 @@
-
+>
Wosiewicz LeszekJanuszewicz Marek
diff --git a/metis-repository/src/test/resources/repository-test-error.zip b/metis-repository/metis-repository-rest/src/test/resources/repository-test-error.zip
similarity index 100%
rename from metis-repository/src/test/resources/repository-test-error.zip
rename to metis-repository/metis-repository-rest/src/test/resources/repository-test-error.zip
diff --git a/metis-repository/src/test/resources/repository-test.zip b/metis-repository/metis-repository-rest/src/test/resources/repository-test.zip
similarity index 100%
rename from metis-repository/src/test/resources/repository-test.zip
rename to metis-repository/metis-repository-rest/src/test/resources/repository-test.zip
diff --git a/metis-repository/pom.xml b/metis-repository/pom.xml
index cb7c91ab1c..6753ddf8cd 100644
--- a/metis-repository/pom.xml
+++ b/metis-repository/pom.xml
@@ -1,120 +1,17 @@
-
-
- metis-framework
- eu.europeana.metis
- 9
-
- 4.0.0
- metis-repository
- war
-
-
- javax.xml.bind
- jaxb-api
-
-
- org.glassfish.jaxb
- jaxb-runtime
-
-
- org.apache.logging.log4j
- log4j-slf4j-impl
-
-
- org.springframework
- spring-core
- ${version.spring}
-
-
- org.springframework
- spring-webmvc
- ${version.spring}
-
-
- javax.servlet
- javax.servlet-api
- ${version.servlet.api}
- provided
-
-
- io.springfox
- springfox-swagger2
- ${version.swagger}
-
-
- io.springfox
- springfox-swagger-ui
- ${version.swagger}
-
-
- com.fasterxml.jackson.core
- jackson-annotations
- ${version.jackson}
-
-
- com.fasterxml.jackson.dataformat
- jackson-dataformat-xml
- ${version.jackson}
-
-
- com.fasterxml.jackson.datatype
- jackson-datatype-jsr310
- ${version.jackson}
-
-
- eu.europeana.metis
- metis-common-utils
- ${project.version}
-
-
- eu.europeana.metis
- metis-common-mongo
- ${project.version}
-
-
- eu.europeana.metis
- metis-harvesting
- ${project.version}
-
-
- org.junit.jupiter
- junit-jupiter-api
-
-
- org.junit.jupiter
- junit-jupiter-engine
-
-
- org.junit.jupiter
- junit-jupiter
- ${version.junit}
- test
-
-
- org.mockito
- mockito-core
-
-
- com.jayway.jsonpath
- json-path
- test
-
-
- org.springframework
- spring-test
-
-
-
-
-
- org.apache.maven.plugins
- maven-war-plugin
- ${version.maven.war.plugin}
-
- false
-
-
-
-
-
+
+ 4.0.0
+
+ eu.europeana.metis
+ 10
+ metis-framework
+
+
+ metis-repository
+ pom
+
+
+ metis-repository-rest
+
+
\ No newline at end of file
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/config/MetisRepositoryRestApplication.java b/metis-repository/src/main/java/eu/europeana/metis/repository/config/MetisRepositoryRestApplication.java
deleted file mode 100644
index e0f06e0b01..0000000000
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/config/MetisRepositoryRestApplication.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package eu.europeana.metis.repository.config;
-
-import com.mongodb.client.MongoClient;
-import eu.europeana.corelib.web.socks.SocksProxy;
-import eu.europeana.metis.mongo.connection.MongoClientProvider;
-import eu.europeana.metis.mongo.connection.MongoProperties;
-import eu.europeana.metis.mongo.connection.MongoProperties.ReadPreferenceValue;
-import eu.europeana.metis.repository.dao.RecordDao;
-import eu.europeana.metis.utils.CustomTruststoreAppender;
-import eu.europeana.metis.utils.CustomTruststoreAppender.TrustStoreConfigurationException;
-import java.util.Collections;
-import javax.annotation.PreDestroy;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.multipart.support.StandardServletMultipartResolver;
-import org.springframework.web.servlet.DispatcherServlet;
-import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
-import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import springfox.documentation.builders.PathSelectors;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.service.Contact;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
-/**
- * The web application making available the repository functionality. This provides all the
- * configuration and is the starting point for all injections and beans. It also performs the
- * required setup.
- */
-@Configuration
-@EnableWebMvc
-@EnableSwagger2
-@ComponentScan(basePackages = {"eu.europeana.metis.repository.config",
- "eu.europeana.metis.repository.rest"})
-public class MetisRepositoryRestApplication implements WebMvcConfigurer, InitializingBean {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(MetisRepositoryRestApplication.class);
-
- private final ApplicationPropertiesHolder properties;
-
- private MongoClient mongoClientForEntities;
-
- /**
- * Constructor.
- *
- * @param properties The properties.
- */
- @Autowired
- public MetisRepositoryRestApplication(ApplicationPropertiesHolder properties) {
- this.properties = properties;
- }
-
- @Override
- public void afterPropertiesSet() throws TrustStoreConfigurationException {
-
- // Set the SOCKS proxy
- if (properties.isSocksProxyEnabled()) {
- new SocksProxy(properties.getSocksProxyHost(), properties.getSocksProxyPort(),
- properties.getSocksProxyUsername(), properties.getSocksProxyPassword()).init();
- }
-
- // Set the truststore.
- LOGGER.info("Append default truststore with custom truststore");
- if (StringUtils.isNotEmpty(properties.getTruststorePath())
- && StringUtils.isNotEmpty(properties.getTruststorePassword())) {
- CustomTruststoreAppender.appendCustomTrustoreToDefault(properties.getTruststorePath(),
- properties.getTruststorePassword());
- }
-
- // Create the mongo connection
- LOGGER.info("Creating Mongo connection");
- final MongoProperties mongoProperties = new MongoProperties<>(
- IllegalArgumentException::new);
- mongoProperties
- .setAllProperties(properties.getMongoHosts(), new int[]{properties.getMongoPort()},
- properties.getMongoAuthenticationDb(), properties.getMongoUsername(),
- properties.getMongoPassword(), properties.isMongoEnableSsl(),
- ReadPreferenceValue.PRIMARY_PREFERRED, properties.getMongoApplicationName());
- mongoClientForEntities = new MongoClientProvider<>(mongoProperties).createMongoClient();
- }
-
- @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
- public StandardServletMultipartResolver getMultipartResolver() {
- return new StandardServletMultipartResolver();
- }
-
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler("/swagger-ui/**")
- .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
- .resourceChain(false);
- }
-
- @Override
- public void addViewControllers(ViewControllerRegistry registry) {
- registry.addRedirectViewController("/", "/swagger-ui/index.html");
- }
-
- @Bean
- public Docket api() {
- return new Docket(DocumentationType.SWAGGER_2)
- .useDefaultResponseMessages(false)
- .select()
- .apis(RequestHandlerSelectors.any())
- .paths(PathSelectors.any())
- .build()
- .apiInfo(apiInfo());
- }
-
- private ApiInfo apiInfo() {
- return new ApiInfo(
- "Metis Repository REST API",
- "Metis Repository REST API",
- "v1",
- "API TOS",
- new Contact("development", "europeana.eu", "development@europeana.eu"),
- "EUPL Licence v1.2",
- "https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12",
- Collections.emptyList());
- }
-
- @Bean
- public RecordDao getRecordDao() {
- return new RecordDao(mongoClientForEntities, properties.getMongoRecordDb());
- }
-
- /**
- * Closes any connections previous acquired.
- */
- @PreDestroy
- public void close() {
- if (mongoClientForEntities != null) {
- mongoClientForEntities.close();
- }
- }
-}
diff --git a/metis-repository/src/main/java/eu/europeana/metis/repository/config/MetisRepositoryRestInitializer.java b/metis-repository/src/main/java/eu/europeana/metis/repository/config/MetisRepositoryRestInitializer.java
deleted file mode 100644
index d506780ffb..0000000000
--- a/metis-repository/src/main/java/eu/europeana/metis/repository/config/MetisRepositoryRestInitializer.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package eu.europeana.metis.repository.config;
-
-import java.io.File;
-import javax.servlet.MultipartConfigElement;
-import javax.servlet.ServletRegistration.Dynamic;
-import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
-
-/**
- * This class is the bootstrap code for Spring. It tells Spring how to start this web application.
- */
-public class MetisRepositoryRestInitializer extends
- AbstractAnnotationConfigDispatcherServletInitializer {
-
- private static final int MAX_UPLOAD_SIZE_IN_MB = 5 * 1024 * 1024; // 5 MB
-
- @Override
- protected Class>[] getRootConfigClasses() {
- return new Class>[0];
- }
-
- @Override
- protected Class>[] getServletConfigClasses() {
- return new Class[]{MetisRepositoryRestApplication.class};
- }
-
- @Override
- protected String[] getServletMappings() {
- return new String[]{"/"};
- }
-
- @Override
- protected void customizeRegistration(Dynamic registration) {
-
- // Call super method
- super.customizeRegistration(registration);
-
- // register a MultipartConfigElement.
- final File uploadDirectory = new File(System.getProperty("java.io.tmpdir"));
- final MultipartConfigElement multipartConfigElement = new MultipartConfigElement(
- uploadDirectory.getAbsolutePath(), MAX_UPLOAD_SIZE_IN_MB, MAX_UPLOAD_SIZE_IN_MB * 2L,
- MAX_UPLOAD_SIZE_IN_MB / 2);
- registration.setMultipartConfig(multipartConfigElement);
- }
-}
diff --git a/metis-repository/src/main/resources/application.properties.example b/metis-repository/src/main/resources/application.properties.example
deleted file mode 100644
index 2680c119c6..0000000000
--- a/metis-repository/src/main/resources/application.properties.example
+++ /dev/null
@@ -1,20 +0,0 @@
-#Socks Proxy
-socks.proxy.enabled=false
-socks.proxy.host=
-socks.proxy.port=
-socks.proxy.username=
-socks.proxy.password=
-
-#Mongo
-mongo.hosts=
-mongo.port=
-mongo.authentication.db=
-mongo.username=
-mongo.password=
-mongo.enable.ssl=
-mongo.application.name=
-mongo.record.db=
-
-#Truststore
-truststore.path=
-truststore.password=
diff --git a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/HttpHarvestControllerTest.java b/metis-repository/src/test/java/eu/europeana/metis/repository/rest/HttpHarvestControllerTest.java
deleted file mode 100644
index df11a48c71..0000000000
--- a/metis-repository/src/test/java/eu/europeana/metis/repository/rest/HttpHarvestControllerTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package eu.europeana.metis.repository.rest;
-
-import eu.europeana.metis.repository.dao.Record;
-import eu.europeana.metis.repository.dao.RecordDao;
-import eu.europeana.metis.utils.RestEndpoints;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.http.ResponseEntity;
-import org.springframework.test.web.servlet.MockMvc;
-import org.springframework.test.web.servlet.MvcResult;
-import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import org.springframework.web.server.ResponseStatusException;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.time.Instant;
-import java.util.Objects;
-import java.util.stream.Stream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
-
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-class HttpHarvestControllerTest {
-
- private RecordDao recordDaoMock;
- private MockMvc httpHarvestControllerMock;
- private HttpHarvestController httpHarvestController;
-
- @BeforeEach
- void setup() {
- recordDaoMock = mock(RecordDao.class);
- httpHarvestController = new HttpHarvestController();
- httpHarvestControllerMock = MockMvcBuilders.standaloneSetup(httpHarvestController)
- .build();
- }
-
- @AfterEach
- void cleanUp() {
- reset(recordDaoMock);
- }
-
- @Test
- void testGetDatasetRecords_expectSuccess() throws IOException {
- Stream recordsStreamMock = makeStreamRecords();
- when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenReturn(recordsStreamMock);
- httpHarvestController.setRecordDao(recordDaoMock);
-
- ResponseEntity resultZip = httpHarvestController.getDatasetRecords("datasetId");
-
- assertTrue(allContentIsReturned(resultZip.getBody()));
-
- }
-
- @Test
- void testGetDatasetRecords_expectInternalServerError() {
- when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenThrow(RuntimeException.class);
- httpHarvestController.setRecordDao(recordDaoMock);
-
- RuntimeException expectedException = assertThrows(ResponseStatusException.class, () -> {
- httpHarvestController.getDatasetRecords("datasetId");
- });
-
- assertEquals("500 INTERNAL_SERVER_ERROR; nested exception is java.lang.RuntimeException", expectedException.getMessage());
-
- }
-
- @Test
- void testGetDatasetRecords_expectNotFoundError() {
- when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenReturn(Stream.empty());
- httpHarvestController.setRecordDao(recordDaoMock);
-
- RuntimeException expectedException = assertThrows(ResponseStatusException.class, () -> {
- httpHarvestController.getDatasetRecords("datasetId");
- });
-
- assertEquals("404 NOT_FOUND \"No records found for this dataset.\"", expectedException.getMessage());
-
- }
-
- @Test
- void getDatasetRecordsViaController() throws Exception {
- Stream recordsStreamMock = makeStreamRecords();
- when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenReturn(recordsStreamMock);
- httpHarvestController.setRecordDao(recordDaoMock);
- MvcResult resultMvc = httpHarvestControllerMock.perform(get(RestEndpoints.REPOSITORY_HTTP_ENDPOINT_ZIP, "datasetId")
- .content(""))
- .andDo(print())
- .andExpect(status().is(200))
- .andReturn();
-
- assertTrue(allContentIsReturned(resultMvc.getResponse().getContentAsByteArray()));
- verify(recordDaoMock, times(1)).getAllRecordsFromDataset("datasetId");
- }
-
- @Test
- void getDatasetRecordsViaController_expectInternalError() throws Exception {
- when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenThrow(RuntimeException.class);
- httpHarvestController.setRecordDao(recordDaoMock);
- httpHarvestControllerMock.perform(get(RestEndpoints.REPOSITORY_HTTP_ENDPOINT_ZIP, "datasetId")
- .content(""))
- .andDo(print())
- .andExpect(status().is(500))
- .andExpect(content().string(""));
-
- verify(recordDaoMock, times(1)).getAllRecordsFromDataset("datasetId");
- }
-
- @Test
- void getDatasetRecordsViaController_expectNotFoundError() throws Exception {
- when(recordDaoMock.getAllRecordsFromDataset("datasetId")).thenReturn(Stream.empty());
- httpHarvestController.setRecordDao(recordDaoMock);
- httpHarvestControllerMock.perform(get(RestEndpoints.REPOSITORY_HTTP_ENDPOINT_ZIP, "datasetId")
- .content(""))
- .andDo(print())
- .andExpect(status().is(404))
- .andExpect(content().string(""));
-
- verify(recordDaoMock, times(1)).getAllRecordsFromDataset("datasetId");
- }
-
- private Stream makeStreamRecords(){
- Instant instantForTest = Instant.now();
- Record record1 = new Record("recordId1", "datasetId", instantForTest, false, "edmRecord1");
- Record record2 = new Record("recordId2", "datasetId", instantForTest, false, "edmRecord2");
- Record record3 = new Record("recordId3", "datasetId", instantForTest, false, "edmRecord3");
- return Stream.of(record1, record2, record3);
- }
-
- private boolean allContentIsReturned(byte[] result) throws IOException {
- ZipEntry zEntry;
- boolean areAllRecordsInZip = true;
-
- ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Objects.requireNonNull(result));
- ZipInputStream zipIs = new ZipInputStream(new BufferedInputStream(byteArrayInputStream));
- while((zEntry = zipIs.getNextEntry()) != null){
- areAllRecordsInZip = areAllRecordsInZip && (zEntry.getName().contains("recordId1") || zEntry.getName().contains("recordId2") || zEntry.getName().contains("recordId3"));
- }
- zipIs.close();
-
- return areAllRecordsInZip;
- }
-
-}
diff --git a/metis-transformation/metis-transformation-service/pom.xml b/metis-transformation/metis-transformation-service/pom.xml
index 5304ce3288..ec62e3dc4c 100644
--- a/metis-transformation/metis-transformation-service/pom.xml
+++ b/metis-transformation/metis-transformation-service/pom.xml
@@ -4,7 +4,7 @@
metis-transformationeu.europeana.metis
- 9
+ 10metis-transformation-service
diff --git a/metis-transformation/pom.xml b/metis-transformation/pom.xml
index 59cdd821ef..a591292303 100644
--- a/metis-transformation/pom.xml
+++ b/metis-transformation/pom.xml
@@ -4,7 +4,7 @@
metis-frameworkeu.europeana.metis
- 9
+ 10metis-transformationpom
diff --git a/metis-validation/metis-validation-common/pom.xml b/metis-validation/metis-validation-common/pom.xml
index ffc16aaf1c..3a8c3eff89 100644
--- a/metis-validation/metis-validation-common/pom.xml
+++ b/metis-validation/metis-validation-common/pom.xml
@@ -4,7 +4,7 @@
metis-validationeu.europeana.metis
- 9
+ 10metis-validation-common
diff --git a/metis-validation/metis-validation-service/pom.xml b/metis-validation/metis-validation-service/pom.xml
index fe8b1906a6..18fe932ce1 100644
--- a/metis-validation/metis-validation-service/pom.xml
+++ b/metis-validation/metis-validation-service/pom.xml
@@ -4,7 +4,7 @@
metis-validationeu.europeana.metis
- 9
+ 10metis-validation-service
diff --git a/metis-validation/pom.xml b/metis-validation/pom.xml
index 2475e1023c..8080c0c875 100644
--- a/metis-validation/pom.xml
+++ b/metis-validation/pom.xml
@@ -4,7 +4,7 @@
metis-frameworkeu.europeana.metis
- 9
+ 10metis-validationpom
diff --git a/pom.xml b/pom.xml
index c35c6f6e2e..6cfe70b20c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
eu.europeana.metismetis-framework
- 9
+ 10pom
@@ -27,7 +27,7 @@
scm:git:https://github.com/europeana/metis-frameworkhttps://github.com/europeana/metis-framework
- v9
+ HEADscm:git:https://github.com/europeana/metis-framework
@@ -121,8 +121,8 @@
UTF-8
- 7-SNAPSHOT
- 7-SNAPSHOT
+ 10-SNAPSHOT
+ 82.9.05.1
@@ -137,7 +137,7 @@
3.0.11.10.01.21
- 2.15.8-SNAPSHOT
+ 2.16.21.0
@@ -169,7 +169,6 @@
2.8.22.5.33.2.0
- 2.34.3.12.2.10.9
@@ -202,6 +201,11 @@
+
+ eu.europeana.metis
+ metis-common-base
+ ${project.version}
+ eu.europeana.metismetis-common-mongo