diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4b6a3ec9..8a2218b1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,6 @@ updates: interval: "weekly" open-pull-requests-limit: 0 - - package-ecosystem: "github-actions" directory: "/" target-branch: "development" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8d206d91..4707b0b0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,7 @@ jobs: # Initialize minimum JDK version - name: Setup Java JDK - uses: actions/setup-java@v4.2.1 + uses: actions/setup-java@v4.5.0 with: distribution: zulu java-version: 17 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 570180db..8bcfce96 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 - name: Set up OpenJDK version ... - uses: actions/setup-java@v4.2.1 + uses: actions/setup-java@v4.5.0 with: distribution: 'zulu' java-version: ${{ matrix.jdk }} @@ -50,7 +50,7 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 - name: Set up OpenJDK version ... - uses: actions/setup-java@v4.2.1 + uses: actions/setup-java@v4.5.0 with: distribution: 'zulu' java-version: ${{ env.currentBuildVersion }} diff --git a/.github/workflows/publishRelease.yml b/.github/workflows/publishRelease.yml index 9912dad1..a1d7bb59 100644 --- a/.github/workflows/publishRelease.yml +++ b/.github/workflows/publishRelease.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v4.2.1 + uses: actions/setup-java@v4.5.0 with: java-version: 17 distribution: zulu diff --git a/CHANGELOG.md b/CHANGELOG.md index a2873a7b..7dd26ca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +## [1.2.3] - 2024-11-08 + +### Added +* Enhanced searching capabilities for related identifiers with new methods in the RelatedIdentifierSpec class. +* Comprehensive unit tests added for the RelatedIdentifierSpec class to ensure robust functionality. + +### Fixed +* Fixed potential issue with unprivileged find +* Improved error messaging for missing publisher during updates in the DataResourceService. + +### Security +* Update actions/setup-java action to v4.5.0 +* Update dependency com.fasterxml.jackson.datatype:jackson-datatype-joda to v2.18.1. +* Update dependency com.fasterxml.jackson.datatype:jackson-datatype-jsr310 to v2.18.1 +* Update dependency com.fasterxml.jackson.module:jackson-module-afterburner to v2.18.1 +* Update dependency com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider to v2.18.1 +* Update dependency com.google.code.gson:gson to v2.11.0 +* Bump commons-io:commons-io from 2.16.1 to 2.17.0. +* Update dependency de.codecentric:spring-boot-admin-starter-client to v3.3.5 +* Update dependency edu.kit.datamanager:service-base to v1.3.2 +* Update plugin io.freefair.lombok to v8.10.2 +* Update plugin io.freefair.maven-publish-java to v8.10.2 +* Update dependency gradle to v8.10.2 +* Update dependency jacoco to v0.8.12 +* Bump org.apache.tika:tika-core from 2.9.2 to 3.0.0 +* Update dependency org.javers:javers-spring-boot-starter-sql to v7.6.3 +* Bump org.javers:javers-spring-boot-starter-sql from 7.6.2 to 7.6.3 +* Update plugin org.owasp.dependencycheck to v11 +* Bump org.postgresql:postgresql from 42.7.3 to 42.7.4. +* Update dependency org.springframework.boot:spring-boot-dependencies to v3.3.5 +* Update dependency org.springframework.data:spring-data-elasticsearch to v5.3.5 +* Update dependency org.springframework.restdocs:spring-restdocs-mockmvc to v3.0.2 +* Update dependency org.springframework:spring-messaging to v6.1.14 + +### Deprecated + +### Removed +* Removed outdated configuration for GitHub Actions in the project setup. + +### Security + ## [1.2.2] - 2024-04-02 ### Fixed @@ -276,7 +317,8 @@ Extracted from the 'base-repo' project. ### Removed - none -[Unreleased]: https://github.com/kit-data-manager/repo-core/compare/v1.2.2...HEAD +[Unreleased]: https://github.com/kit-data-manager/repo-core/compare/v1.2.3...HEAD +[1.2.3]: https://github.com/kit-data-manager/repo-core/compare/v1.2.2...v1.2.3 [1.2.2]: https://github.com/kit-data-manager/repo-core/compare/v1.2.1...v1.2.2 [1.2.1]: https://github.com/kit-data-manager/repo-core/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/kit-data-manager/repo-core/compare/v1.1.2...v1.2.0 diff --git a/build.gradle b/build.gradle index 916397cc..1c0a7d2e 100644 --- a/build.gradle +++ b/build.gradle @@ -14,12 +14,12 @@ * limitations under the License. */ plugins { - id "io.freefair.lombok" version "8.6" - id "io.freefair.maven-publish-java" version "8.6" - id "io.spring.dependency-management" version "1.1.4" + id "io.freefair.lombok" version "8.10.2" + id "io.freefair.maven-publish-java" version "8.10.2" + id "io.spring.dependency-management" version "1.1.6" //id "com.github.kt3k.coveralls" version "2.8.1" - id "org.owasp.dependencycheck" version "9.1.0" - id "org.asciidoctor.jvm.convert" version "4.0.2" + id "org.owasp.dependencycheck" version "11.1.0" + id "org.asciidoctor.jvm.convert" version "4.0.3" //id "org.ajoberstar.grgit" version "2.0.1" id "java" id "jacoco" @@ -32,9 +32,9 @@ plugins { ext { // versions of dependencies - springBootVersion = '3.1.0' - springDocVersion = '2.5.0' - javersVersion = '7.4.2' + springBootVersion = '3.3.5' + springDocVersion = '2.6.0' + javersVersion = '7.6.3' } description = "Core module for data repositories based on metadata model of datacite." @@ -69,7 +69,7 @@ if (project.hasProperty('release')) { dependencies { // Spring - implementation 'org.springframework:spring-messaging:6.0.2' + implementation 'org.springframework:spring-messaging:6.1.14' // Spring Boot implementation "org.springframework.boot:spring-boot-starter-data-rest" implementation "org.springframework.boot:spring-boot-starter-amqp" @@ -77,7 +77,7 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-security" implementation "org.springframework.boot:spring-boot-starter-actuator" implementation "org.springframework.boot:spring-boot-starter-data-jpa" - implementation 'org.springframework.data:spring-data-elasticsearch:5.1.0' + implementation 'org.springframework.data:spring-data-elasticsearch:5.3.5' // springdoc @@ -86,33 +86,34 @@ dependencies { implementation "org.springdoc:springdoc-openapi-starter-webmvc-api:${springDocVersion}" // apache implementation "commons-configuration:commons-configuration:1.10" - implementation "commons-io:commons-io:2.16.1" + implementation "commons-io:commons-io:2.17.0" implementation "org.apache.commons:commons-collections4:4.4" // includes commons-lang3 implementation "org.apache.commons:commons-text:1.12.0" implementation 'org.apache.httpcomponents:httpclient:4.5.14' - implementation "org.apache.tika:tika-core:2.9.2" + implementation "org.apache.tika:tika-core:3.0.0" // javers implementation "org.javers:javers-spring-boot-starter-sql:${javersVersion}" + implementation "com.google.code.gson:gson:2.11.0" // Database - implementation "com.h2database:h2:2.2.224" - implementation "org.postgresql:postgresql:42.7.3" + implementation "com.h2database:h2:2.3.232" + implementation "org.postgresql:postgresql:42.7.4" // XML - implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.17.0" - implementation "com.fasterxml.jackson.module:jackson-module-afterburner:2.17.0" + implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.18.1" + implementation "com.fasterxml.jackson.module:jackson-module-afterburner:2.18.1" - implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0" - implementation "com.fasterxml.jackson.datatype:jackson-datatype-joda:2.17.0" + implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.1" + implementation "com.fasterxml.jackson.datatype:jackson-datatype-joda:2.18.1" //implementation "com.monitorjbl:spring-json-view:1.0.1" - implementation "de.codecentric:spring-boot-admin-starter-client:3.0.4" + implementation "de.codecentric:spring-boot-admin-starter-client:3.3.5" // log4j core implementation "org.apache.logging.log4j:log4j-core" implementation "ch.qos.logback:logback-classic" - implementation "edu.kit.datamanager:service-base:1.3.1" + implementation "edu.kit.datamanager:service-base:1.3.2" implementation "com.github.java-json-tools:json-patch:1.13" implementation "com.github.dozermapper:dozer-core:7.0.0" @@ -122,7 +123,7 @@ dependencies { // boot starter testImplementation 'org.springframework.boot:spring-boot-starter-validation' testImplementation "org.springframework.boot:spring-boot-starter-test" - testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:3.0.0' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:3.0.2' testImplementation "org.springframework.security:spring-security-test" //Java 11 Support @@ -154,7 +155,7 @@ test { } jacoco { - toolVersion = "0.8.11" + toolVersion = "0.8.12" } tasks.withType(Test) { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbf..a4b76b95 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3499ded5..df97d72b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,9 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +134,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85b..9b42019c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/src/main/java/edu/kit/datamanager/repo/dao/spec/dataresource/RelatedIdentifierSpec.java b/src/main/java/edu/kit/datamanager/repo/dao/spec/dataresource/RelatedIdentifierSpec.java index 71d2bbac..a0b932d1 100644 --- a/src/main/java/edu/kit/datamanager/repo/dao/spec/dataresource/RelatedIdentifierSpec.java +++ b/src/main/java/edu/kit/datamanager/repo/dao/spec/dataresource/RelatedIdentifierSpec.java @@ -16,42 +16,74 @@ package edu.kit.datamanager.repo.dao.spec.dataresource; import edu.kit.datamanager.repo.domain.DataResource; +import edu.kit.datamanager.repo.domain.RelatedIdentifier; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; -import org.datacite.schema.kernel_4.Resource.AlternateIdentifiers.AlternateIdentifier; import org.springframework.data.jpa.domain.Specification; /** * * @author jejkal */ -public class RelatedIdentifierSpec{ +public class RelatedIdentifierSpec { /** * Hidden constructor. */ - private RelatedIdentifierSpec(){ + private RelatedIdentifierSpec() { } - public static Specification toSpecification(final String... identifierValues){ + /** + * Search for values in all related identifiers. + * + * @param identifierValues Values to search for. + * @return specification + */ + public static Specification toSpecification(final String... identifierValues) { + return toSpecification(null, identifierValues); + } + + /** + * Search for relation type in all related identifiers. + * + * @param relationType Relation type to search for. + * @return specification + */ + public static Specification toSpecification(final RelatedIdentifier.RELATION_TYPES relationType) { + return toSpecification(relationType, (String[]) null); + } + + /** + * Search for relation type AND values in all related identifiers. + * + * @param relationType Relation type to search for. + * @param identifierValues Values to search for. + * @return specification + */ + public static Specification toSpecification(final RelatedIdentifier.RELATION_TYPES relationType, final String... identifierValues) { Specification newSpec = Specification.where(null); - if(identifierValues == null || identifierValues.length == 0){ + if ((identifierValues == null || identifierValues.length == 0) && (relationType == null)) { return newSpec; } return (Root root, CriteriaQuery query, CriteriaBuilder builder) -> { query.distinct(true); + //join dataresource table with related identifiers table + Join relatedIdentifierJoin = root.join("relatedIdentifiers", JoinType.INNER); + //get all related identifiers of type relationType with one of the provided values + Predicate allPredicates = builder.conjunction(); + if (identifierValues != null && identifierValues.length != 0) { + allPredicates = builder.and(allPredicates, relatedIdentifierJoin.get("value").in(identifierValues)); + } + if (relationType != null) { + allPredicates = builder.and(allPredicates, builder.equal(relatedIdentifierJoin.get("relationType"), relationType)); + } - //join dataresource table with alternate identifiers table - Join altJoin = root.join("relatedIdentifiers", JoinType.INNER); - //get all alternate identifiers NOT of type INTERNAL with one of the provided values - return - builder. - and(altJoin.get("value"). - in((Object[]) identifierValues)); + return allPredicates; }; } } diff --git a/src/main/java/edu/kit/datamanager/repo/service/impl/DataResourceService.java b/src/main/java/edu/kit/datamanager/repo/service/impl/DataResourceService.java index 9da74ef8..84a778d8 100644 --- a/src/main/java/edu/kit/datamanager/repo/service/impl/DataResourceService.java +++ b/src/main/java/edu/kit/datamanager/repo/service/impl/DataResourceService.java @@ -502,7 +502,7 @@ private Page doFind( logger.trace("Checking example for state information."); if (example != null && example.getState() != null) { //example is set...check if example state should be used - if (includeRevoked || !DataResource.State.REVOKED.equals(example.getSubjects())) { + if (includeRevoked || !DataResource.State.REVOKED.equals(example.getState())) { logger.trace("Adding state {} from example.", example.getState()); //we either are allowed to include revoked state or the state is not 'REVOKED', add state from example states.add(example.getState()); @@ -513,12 +513,11 @@ private Page doFind( if (states.isEmpty()) { logger.trace("No state element received from example. Adding default states VOLATILE and FIXED."); - //No state obtained from example...adding default states VOLATILE and FIXED states.add(DataResource.State.VOLATILE); states.add(DataResource.State.FIXED); } - if (includeRevoked) { + if (includeRevoked && !states.contains(DataResource.State.REVOKED)) { logger.trace("Flag 'includeRevoked' is enabled. Adding states REVOKED."); //Add REVOKED state in case this is allowed (e.g. admin access) states.add(DataResource.State.REVOKED); @@ -603,7 +602,7 @@ public DataResource put(DataResource resource, DataResource newResource, errorMessage.append("Empty publication year provided for update.\n"); } if (newResource.getPublisher() == null) { - errorMessage.append("Empty publication year provided for update.\n"); + errorMessage.append("Empty publisher provided for update.\n"); } if (errorMessage.length() > 0) { diff --git a/src/test/java/edu/kit/datamanager/repo/test/integration/RelatedIdentifierSpecificationTest.java b/src/test/java/edu/kit/datamanager/repo/test/integration/RelatedIdentifierSpecificationTest.java new file mode 100644 index 00000000..82a30a21 --- /dev/null +++ b/src/test/java/edu/kit/datamanager/repo/test/integration/RelatedIdentifierSpecificationTest.java @@ -0,0 +1,204 @@ +/* + * Copyright 2024 Karlsruhe Institute of Technology. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package edu.kit.datamanager.repo.test.integration; + +import com.fasterxml.jackson.core.JsonProcessingException; +import edu.kit.datamanager.repo.dao.IDataResourceDao; +import edu.kit.datamanager.repo.dao.spec.dataresource.RelatedIdentifierSpec; +import edu.kit.datamanager.repo.domain.DataResource; +import edu.kit.datamanager.repo.domain.RelatedIdentifier; +import edu.kit.datamanager.repo.domain.Scheme; +import java.util.List; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.support.DirtiesContextTestExecutionListener; +import org.springframework.test.context.transaction.TransactionalTestExecutionListener; +import org.springframework.test.context.web.ServletTestExecutionListener; + +/** + * Test specifications for related identifiers. These can be filtered by + *
  • value and/or + *
  • relation type + *
+ * The setup defines 7 instances. With 2 different relation types and 3 + * different values. One instance is without any relation type. + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +@TestExecutionListeners(listeners = {ServletTestExecutionListener.class, + DependencyInjectionTestExecutionListener.class, + DirtiesContextTestExecutionListener.class, + TransactionalTestExecutionListener.class, + WithSecurityContextTestExecutionListener.class}) +@TestPropertySource(properties = {"spring.datasource.url=jdbc:h2:mem:related_identifiers;DB_CLOSE_DELAY=-1;MODE=LEGACY;NON_KEYWORDS=VALUE"}) +@ActiveProfiles("test") +public class RelatedIdentifierSpecificationTest { + + @Autowired + private IDataResourceDao dataResourceDao; + + private static final String UNKNOWN_RELATED_RESOURCE = "something else"; + private static final String RELATED_RESOURCE_1 = "documentLocation"; + private static final String RELATED_RESOURCE_2 = "documentLocation_2"; + private static final String RELATED_RESOURCE_3 = "http://example.org/some%20stupid url"; + + private static final RelatedIdentifier.RELATION_TYPES RELATION_TYPE_1 = RelatedIdentifier.RELATION_TYPES.HAS_METADATA; + private static final RelatedIdentifier.RELATION_TYPES RELATION_TYPE_2 = RelatedIdentifier.RELATION_TYPES.IS_METADATA_FOR; + private static final RelatedIdentifier.RELATION_TYPES RELATION_TYPE_3 = RelatedIdentifier.RELATION_TYPES.IS_DERIVED_FROM; + + @Before + public void setUp() throws JsonProcessingException { + dataResourceDao.deleteAll(); + + createTestResource("hasMetadataResource1", RELATION_TYPE_1, RELATED_RESOURCE_1); + createTestResource("hasMetadataResource2", RELATION_TYPE_1, RELATED_RESOURCE_2); + createTestResource("hasMetadataResource3", RELATION_TYPE_1, RELATED_RESOURCE_3); + + createTestResource("isMetadataOfResource1", RELATION_TYPE_2, RELATED_RESOURCE_1); + createTestResource("isMetadataOfResource2", RELATION_TYPE_2, RELATED_RESOURCE_2); + createTestResource("isMetadataOfResource3", RELATION_TYPE_2, RELATED_RESOURCE_3); + + createTestResource("noTypeForResource", null, RELATED_RESOURCE_3); + } + + /** + * Test specification for related identifier only. + * @throws Exception Any error. + */ + @Test + public void testGetDataResourcesByRelatedResourceAndValue() throws Exception { + testSingleValue(RELATED_RESOURCE_1, 2); + testSingleValue(RELATED_RESOURCE_1.toUpperCase(), 0); + testSingleValue(RELATED_RESOURCE_1.toLowerCase(), 0); + testSingleValue(RELATED_RESOURCE_2, 2); + testSingleValue(RELATED_RESOURCE_3, 3); + testSingleValue(UNKNOWN_RELATED_RESOURCE, 0); + testSingleValue(null, 0); + + testTwoValues(RELATED_RESOURCE_1, RELATED_RESOURCE_2, 4); + testTwoValues(RELATED_RESOURCE_1, RELATED_RESOURCE_3, 5); + testTwoValues(RELATED_RESOURCE_2, RELATED_RESOURCE_3, 5); + testTwoValues(RELATED_RESOURCE_1.substring(1), RELATED_RESOURCE_3, 3); + testTwoValues(UNKNOWN_RELATED_RESOURCE, RELATED_RESOURCE_1, 2); + testTwoValues(UNKNOWN_RELATED_RESOURCE, RELATED_RESOURCE_1, 2); + testTwoValues(UNKNOWN_RELATED_RESOURCE, RELATED_RESOURCE_2, 2); + testTwoValues(UNKNOWN_RELATED_RESOURCE, RELATED_RESOURCE_3, 3); + testTwoValues(UNKNOWN_RELATED_RESOURCE, RELATED_RESOURCE_1.substring(1), 0); + + Specification toSpecification = RelatedIdentifierSpec.toSpecification(RELATED_RESOURCE_1, RELATED_RESOURCE_2, RELATED_RESOURCE_3); + List findAll = dataResourceDao.findAll(toSpecification); + Assert.assertEquals("Find all related to " + RELATED_RESOURCE_1 + " and " + RELATED_RESOURCE_2 + " and " + RELATED_RESOURCE_3, 7, findAll.size()); + } + + /** + * Test specification for no related identifier provided. + * @throws Exception Any error. + */ + @Test + public void testGetDataResourcesByRelatedResourceAndNoValue() throws Exception { + Specification toSpecification = RelatedIdentifierSpec.toSpecification(new String[0]); + List findAll = dataResourceDao.findAll(toSpecification); + Assert.assertEquals("Find all related to new String[0]", 7, findAll.size()); + + toSpecification = RelatedIdentifierSpec.toSpecification((String[]) null); + findAll = dataResourceDao.findAll(toSpecification); + Assert.assertEquals("Find all related to (String[])null", 7, findAll.size()); + } + + /** + * Test specification for no relation type only. + * @throws Exception Any error. + */ + @Test + public void testGetDataResourcesByRelatedResourceAndRelatedType() throws Exception { + testForRelatedType(RELATION_TYPE_1, 3); + testForRelatedType(RELATION_TYPE_2, 3); + testForRelatedType(RELATION_TYPE_3, 0); + testForRelatedType(null, 7); + } + + /** + * Test specification for relation type AND related identifier provided. + * @throws Exception Any error. + */ + @Test + public void testGetDataResourcesByRelatedResourceFilteredByValueAndType() throws Exception { + testForSingleValueAndType(RELATED_RESOURCE_1, RELATION_TYPE_1, 1); + testForSingleValueAndType(RELATED_RESOURCE_2, RELATION_TYPE_1, 1); + testForSingleValueAndType(RELATED_RESOURCE_3, RELATION_TYPE_1, 1); + + testForSingleValueAndType(RELATED_RESOURCE_1, RELATION_TYPE_2, 1); + testForSingleValueAndType(RELATED_RESOURCE_2, RELATION_TYPE_2, 1); + testForSingleValueAndType(RELATED_RESOURCE_3, RELATION_TYPE_2, 1); + + testForSingleValueAndType(RELATED_RESOURCE_1, RELATION_TYPE_3, 0); + testForSingleValueAndType(RELATED_RESOURCE_2, RELATION_TYPE_3, 0); + testForSingleValueAndType(RELATED_RESOURCE_3, RELATION_TYPE_3, 0); + + testForSingleValueAndType(UNKNOWN_RELATED_RESOURCE, RELATION_TYPE_1, 0); + + Specification toSpecification = RelatedIdentifierSpec.toSpecification(RELATION_TYPE_1, RELATED_RESOURCE_1, RELATED_RESOURCE_3); + List findAll = dataResourceDao.findAll(toSpecification); + Assert.assertEquals("Find all related to " + RELATED_RESOURCE_1 + " and " + RELATED_RESOURCE_3 + " with type " + RELATION_TYPE_1, 2, findAll.size()); + } + + private DataResource createTestResource(String name, RelatedIdentifier.RELATION_TYPES relationType, String relatedResource) { + DataResource resource = DataResource.factoryNewDataResource(name); + resource.setState(DataResource.State.FIXED); + resource.getRelatedIdentifiers().add( + RelatedIdentifier.factoryRelatedIdentifier(relationType, relatedResource, + Scheme.factoryScheme("id", "uri"), "metadata_scheme") + ); + return dataResourceDao.save(resource); + } + + private void testSingleValue(String value, int expectedValue) { + Specification toSpecification = RelatedIdentifierSpec.toSpecification(value); + List findAll = dataResourceDao.findAll(toSpecification); + Assert.assertEquals("Find all related to " + value, expectedValue, findAll.size()); + } + + private void testTwoValues(String value1, String value2, int expectedValue) { + Specification toSpecification = RelatedIdentifierSpec.toSpecification(value1, value2); + List findAll = dataResourceDao.findAll(toSpecification); + Assert.assertEquals("Find all related to " + value1 + " and " + value2, expectedValue, findAll.size()); + } + + private void testForRelatedType(RelatedIdentifier.RELATION_TYPES relationType, int expectedValue) { + Specification toSpecification = RelatedIdentifierSpec.toSpecification(relationType); + List findAll = dataResourceDao.findAll(toSpecification); + Assert.assertEquals("Find all related to with type " + relationType, expectedValue, findAll.size()); + } + + private void testForSingleValueAndType(String value, RelatedIdentifier.RELATION_TYPES relationType, int expectedValue) { + Specification toSpecification = RelatedIdentifierSpec.toSpecification(relationType, value); + List findAll = dataResourceDao.findAll(toSpecification); + Assert.assertEquals("Find all related to " + value + " with type " + relationType, expectedValue, findAll.size()); + } +}