Merge branch 'DSpace:main' into master

This commit is contained in:
Paulo Graça
2025-07-30 08:40:05 +01:00
committed by GitHub
920 changed files with 37256 additions and 13379 deletions

View File

@@ -4,7 +4,6 @@
*/target/
dspace/modules/*/target/
Dockerfile.*
dspace/src/main/docker/dspace-postgres-pgcrypto
dspace/src/main/docker/dspace-postgres-pgcrypto-curl
dspace/src/main/docker/dspace-postgres-loadsql
dspace/src/main/docker/README.md
dspace/src/main/docker-compose/

189
.github/dependabot.yml vendored
View File

@@ -13,6 +13,7 @@ updates:
directory: "/"
schedule:
interval: "weekly"
time: "02:00"
# Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10
# Group together some upgrades in a single PR
@@ -28,7 +29,7 @@ updates:
- "com.google.code.findbugs:*"
- "com.google.errorprone:*"
- "com.puppycrawl.tools:checkstyle"
- "org.sonatype.plugins:*"
- "org.sonatype.*:*"
exclude-patterns:
# Exclude anything from Spring, as that is in a separate group
- "org.springframework.*:*"
@@ -43,9 +44,11 @@ updates:
- "com.h2database:*"
- "io.findify:s3mock*"
- "io.netty:*"
- "org.apache.httpcomponents.client5:*"
- "org.hamcrest:*"
- "org.mock-server:*"
- "org.mockito:*"
- "org.xmlunit:*"
update-types:
- "minor"
- "patch"
@@ -73,7 +76,6 @@ updates:
patterns:
- "org.hibernate.*:*"
update-types:
- "minor"
- "patch"
# Group together all Jakarta deps in a single PR
jakarta:
@@ -103,21 +105,36 @@ updates:
update-types:
- "minor"
- "patch"
# Group Tika, bouncycastle, and asm because they are tightly integrated
# and we theoretically want to keep them in sync.
tika:
applies-to: version-updates
patterns:
- "org.apache.tika:*:*"
- "org.bouncycastle:*:*"
- "org.ow2.asm:*:*"
update-types:
- "minor"
- "patch"
ignore:
# Don't try to auto-update any DSpace dependencies
- dependency-name: "org.dspace:*"
- dependency-name: "org.dspace.*:*"
# Ignore major/minor updates for Hibernate. Only patch updates can be automated.
- dependency-name: "org.hibernate.*:*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*"
update-types: ["version-update:semver-major"]
######################
## dspace-8_x branch
## dspace-9_x branch
######################
- package-ecosystem: "maven"
directory: "/"
target-branch: dspace-8_x
target-branch: dspace-9_x
schedule:
interval: "weekly"
time: "02:00"
# Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10
# Group together some upgrades in a single PR
@@ -133,7 +150,7 @@ updates:
- "com.google.code.findbugs:*"
- "com.google.errorprone:*"
- "com.puppycrawl.tools:checkstyle"
- "org.sonatype.plugins:*"
- "org.sonatype.*:*"
exclude-patterns:
# Exclude anything from Spring, as that is in a separate group
- "org.springframework.*:*"
@@ -148,9 +165,11 @@ updates:
- "com.h2database:*"
- "io.findify:s3mock*"
- "io.netty:*"
- "org.apache.httpcomponents.client5:*"
- "org.hamcrest:*"
- "org.mock-server:*"
- "org.mockito:*"
- "org.xmlunit:*"
update-types:
- "minor"
- "patch"
@@ -178,7 +197,6 @@ updates:
patterns:
- "org.hibernate.*:*"
update-types:
- "minor"
- "patch"
# Group together all Jakarta deps in a single PR
jakarta:
@@ -208,21 +226,36 @@ updates:
update-types:
- "minor"
- "patch"
# Group Tika, bouncycastle, and asm because they are tightly integrated
# and we theoretically want to keep them in sync.
tika:
applies-to: version-updates
patterns:
- "org.apache.tika:*:*"
- "org.bouncycastle:*:*"
- "org.ow2.asm:*:*"
update-types:
- "minor"
- "patch"
ignore:
# Don't try to auto-update any DSpace dependencies
- dependency-name: "org.dspace:*"
- dependency-name: "org.dspace.*:*"
# Ignore major/minor updates for Hibernate. Only patch updates can be automated.
- dependency-name: "org.hibernate.*:*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*"
update-types: [ "version-update:semver-major" ]
######################
## dspace-7_x branch
## dspace-8_x branch
######################
- package-ecosystem: "maven"
directory: "/"
target-branch: dspace-7_x
target-branch: dspace-8_x
schedule:
interval: "weekly"
time: "02:00"
# Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10
# Group together some upgrades in a single PR
@@ -238,7 +271,7 @@ updates:
- "com.google.code.findbugs:*"
- "com.google.errorprone:*"
- "com.puppycrawl.tools:checkstyle"
- "org.sonatype.plugins:*"
- "org.sonatype.*:*"
exclude-patterns:
# Exclude anything from Spring, as that is in a separate group
- "org.springframework.*:*"
@@ -253,9 +286,11 @@ updates:
- "com.h2database:*"
- "io.findify:s3mock*"
- "io.netty:*"
- "org.apache.httpcomponents.client5:*"
- "org.hamcrest:*"
- "org.mock-server:*"
- "org.mockito:*"
- "org.xmlunit:*"
update-types:
- "minor"
- "patch"
@@ -283,7 +318,6 @@ updates:
patterns:
- "org.hibernate.*:*"
update-types:
- "minor"
- "patch"
# Group together all Jakarta deps in a single PR
jakarta:
@@ -295,6 +329,127 @@ updates:
update-types:
- "minor"
- "patch"
# Group together all Spring deps in a single PR
spring:
applies-to: version-updates
patterns:
- "org.springframework:*"
- "org.springframework.*:*"
update-types:
- "minor"
- "patch"
# Group together all WebJARs deps in a single PR
webjars:
applies-to: version-updates
patterns:
- "org.webjars:*"
- "org.webjars.*:*"
update-types:
- "minor"
- "patch"
# Group Tika, bouncycastle, and asm because they are tightly integrated
# and we theoretically want to keep them in sync.
tika:
applies-to: version-updates
patterns:
- "org.apache.tika:*:*"
- "org.bouncycastle:*:*"
- "org.ow2.asm:*:*"
update-types:
- "minor"
- "patch"
ignore:
# Don't try to auto-update any DSpace dependencies
- dependency-name: "org.dspace:*"
- dependency-name: "org.dspace.*:*"
# Ignore major/minor updates for Hibernate. Only patch updates can be automated.
- dependency-name: "org.hibernate.*:*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*"
update-types: [ "version-update:semver-major" ]
######################
## dspace-7_x branch
######################
- package-ecosystem: "maven"
directory: "/"
target-branch: dspace-7_x
schedule:
interval: "weekly"
time: "02:00"
# Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10
# Group together some upgrades in a single PR
groups:
# Group together all Build Tools in a single PR
build-tools:
applies-to: version-updates
patterns:
- "org.apache.maven.plugins:*"
- "*:*-maven-plugin"
- "*:maven-*-plugin"
- "com.github.spotbugs:spotbugs"
- "com.google.code.findbugs:*"
- "com.google.errorprone:*"
- "com.puppycrawl.tools:checkstyle"
- "org.sonatype.*:*"
exclude-patterns:
# Exclude anything from Spring, as that is in a separate group
- "org.springframework.*:*"
update-types:
- "minor"
- "patch"
test-tools:
applies-to: version-updates
patterns:
- "junit:*"
- "com.github.stefanbirker:system-rules"
- "com.h2database:*"
- "io.findify:s3mock*"
- "io.netty:*"
- "org.hamcrest:*"
- "org.mock-server:*"
- "org.mockito:*"
- "org.xmlunit:*"
update-types:
- "minor"
- "patch"
# Group together all Apache Commons deps in a single PR
apache-commons:
applies-to: version-updates
patterns:
- "org.apache.commons:*"
- "commons-*:commons-*"
update-types:
- "minor"
- "patch"
# Group together all fasterxml deps in a single PR
fasterxml:
applies-to: version-updates
patterns:
- "com.fasterxml:*"
- "com.fasterxml.*:*"
update-types:
- "minor"
- "patch"
# Group together all Hibernate deps in a single PR
hibernate:
applies-to: version-updates
patterns:
- "org.hibernate.*:*"
update-types:
- "patch"
# Group together all Javax deps in a single PR
# NOTE: Javax is only used in 7.x and has been replaced by Jakarta in 8.x and later
jakarta:
applies-to: version-updates
patterns:
- "javax.*:*"
- "*:javax.mail"
- "org.glassfish.jaxb:jaxb-runtime"
update-types:
- "minor"
- "patch"
# Group together all Google deps in a single PR
# NOTE: These Google deps are only used in 7.x and have been removed in 8.x and later
google-apis:
@@ -325,6 +480,17 @@ updates:
update-types:
- "minor"
- "patch"
# Group Tika, bouncycastle, and asm because they are tightly integrated
# and we theoretically want to keep them in sync.
tika:
applies-to: version-updates
patterns:
- "org.apache.tika:*:*"
- "org.bouncycastle:*:*"
- "org.ow2.asm:*:*"
update-types:
- "minor"
- "patch"
ignore:
# Don't try to auto-update any DSpace dependencies
- dependency-name: "org.dspace:*"
@@ -336,6 +502,9 @@ updates:
# See https://github.com/DSpace/DSpace/pull/9888#issuecomment-2408165545
- dependency-name: "org.springframework.security:*"
versions: [">=5.8.0"]
# Ignore major/minor updates for Hibernate. Only patch updates can be automated.
- dependency-name: "org.hibernate.*:*"
update-types: ["version-update:semver-major", "version-update:semver-minor"]
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*"
update-types: [ "version-update:semver-major" ]

View File

@@ -113,39 +113,19 @@ jobs:
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_SOLR_URL }}
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_SOLR_URL }}
###########################################################
# Build/Push the 'dspace/dspace-postgres-pgcrypto' image
###########################################################
dspace-postgres-pgcrypto:
########################################################
# Build/Push the 'dspace/dspace-postgres-loadsql' image
########################################################
dspace-postgres-loadsql:
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
if: github.repository == 'dspace/dspace'
uses: ./.github/workflows/reusable-docker-build.yml
with:
build_id: dspace-postgres-pgcrypto-prod
image_name: dspace/dspace-postgres-pgcrypto
# Must build out of subdirectory to have access to install script for pgcrypto.
build_id: dspace-postgres-loadsql
image_name: dspace/dspace-postgres-loadsql
# Must build out of subdirectory to have access to install script.
# NOTE: this context will build the image based on the Dockerfile in the specified directory
dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
########################################################################
# Build/Push the 'dspace/dspace-postgres-pgcrypto' image (-loadsql tag)
########################################################################
dspace-postgres-pgcrypto-loadsql:
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
if: github.repository == 'dspace/dspace'
uses: ./.github/workflows/reusable-docker-build.yml
with:
build_id: dspace-postgres-pgcrypto-loadsql
image_name: dspace/dspace-postgres-pgcrypto
# Must build out of subdirectory to have access to install script for pgcrypto.
# NOTE: this context will build the image based on the Dockerfile in the specified directory
dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/
# Suffix all tags with "-loadsql". Otherwise, it uses the same
# tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above.
tags_flavor: suffix=-loadsql
dockerfile_context: ./dspace/src/main/docker/dspace-postgres-loadsql/
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -158,7 +138,7 @@ jobs:
if: github.repository == 'dspace/dspace'
runs-on: ubuntu-latest
# Must run after all major images are built
needs: [dspace, dspace-test, dspace-cli, dspace-postgres-pgcrypto, dspace-solr]
needs: [dspace, dspace-test, dspace-cli, dspace-solr]
env:
# Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080
dspace__P__server__P__url: http://127.0.0.1:8080/server
@@ -220,6 +200,19 @@ jobs:
result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections)
echo "$result"
echo "$result" | grep -oE "\"Dog in Yard\","
# Verify basic backend logging is working.
# 1. Access the top communities list. Verify that the "Before request" INFO statement is logged
# 2. Access an invalid endpoint (and ignore 404 response). Verify that a "status:404" WARN statement is logged
- name: Verify backend is logging properly
run: |
wget -O/dev/null -q http://127.0.0.1:8080/server/api/core/communities/search/top
logs=$(docker compose -f docker-compose.yml logs -n 5 dspace)
echo "$logs"
echo "$logs" | grep -o "Before request \[GET /server/api/core/communities/search/top\]"
wget -O/dev/null -q http://127.0.0.1:8080/server/api/does/not/exist || true
logs=$(docker compose -f docker-compose.yml logs -n 5 dspace)
echo "$logs"
echo "$logs" | grep -o "status:404 exception: The repository type does.not was not found"
# Verify Handle Server can be stared and is working properly
# 1. First generate the "[dspace]/handle-server" folder with the sitebndl.zip
# 2. Start the Handle Server (and wait 20 seconds to let it start up)

View File

@@ -72,7 +72,7 @@ env:
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
# Current DSpace branches (and architecture) which are deployed to demo.dspace.org & sandbox.dspace.org respectively
DEPLOY_DEMO_BRANCH: 'dspace-8_x'
DEPLOY_DEMO_BRANCH: 'dspace-9_x'
DEPLOY_SANDBOX_BRANCH: 'main'
DEPLOY_ARCH: 'linux/amd64'
# Registry used during building of Docker images. (All images are later copied to docker.io registry)
@@ -86,17 +86,16 @@ jobs:
matrix:
# Architectures / Platforms for which we will build Docker images
arch: [ 'linux/amd64', 'linux/arm64' ]
os: [ ubuntu-latest ]
isPr:
- ${{ github.event_name == 'pull_request' }}
# If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work.
# The below exclude therefore ensures we do NOT build ARM64 for PRs.
exclude:
- isPr: true
os: ubuntu-latest
arch: linux/arm64
runs-on: ${{ matrix.os }}
# If ARM64, then use the Ubuntu ARM64 runner. Otherwise, use the Ubuntu AMD64 runner
runs-on: ${{ matrix.arch == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
steps:
# This step converts the slashes in the "arch" matrix values above into dashes & saves to env.ARCH_NAME
@@ -122,10 +121,6 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# https://github.com/docker/setup-qemu-action
- name: Set up QEMU emulation to build for multiple architectures
uses: docker/setup-qemu-action@v3
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3

3
.gitignore vendored
View File

@@ -28,6 +28,9 @@ nbdist/
nbactions.xml
nb-configuration.xml
## Ignore project files created by Visual Studio Code
.vscode/
## Ignore all *.properties file in root folder, EXCEPT build.properties (the default)
## KEPT FOR BACKWARDS COMPATIBILITY WITH 5.x (build.properties is now replaced with local.cfg)
/*.properties

View File

@@ -10,7 +10,7 @@ DSpace is a community built and supported project. We do not have a centralized
## Contribute new code via a Pull Request
We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone.
Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC8x/Release+Notes).
Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC9x/Release+Notes).
Code Contribution Checklist
- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests)

View File

@@ -50,6 +50,10 @@ ADD --chown=dspace dspace-oai/pom.xml /app/dspace-oai/
RUN mkdir -p /app/dspace-rdf
ADD --chown=dspace dspace-rdf/pom.xml /app/dspace-rdf/
# 'dspace-saml2' module POM
RUN mkdir -p /app/dspace-saml2
ADD --chown=dspace dspace-saml2/pom.xml /app/dspace-saml2/
# 'dspace-server-webapp' module POM
RUN mkdir -p /app/dspace-server-webapp
ADD --chown=dspace dspace-server-webapp/pom.xml /app/dspace-server-webapp/

View File

@@ -21,71 +21,74 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Apache Software License, Version 2.0:
* Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net)
* AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.261 - https://aws.amazon.com/sdkforjava)
* AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.261 - https://aws.amazon.com/sdkforjava)
* AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.261 - https://aws.amazon.com/sdkforjava)
* JMES Path Query library (com.amazonaws:jmespath-java:1.12.261 - https://aws.amazon.com/sdkforjava)
* AWS SDK for Java - Core (com.amazonaws:aws-java-sdk-core:1.12.783 - https://aws.amazon.com/sdkforjava)
* AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.783 - https://aws.amazon.com/sdkforjava)
* AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.783 - https://aws.amazon.com/sdkforjava)
* JMES Path Query library (com.amazonaws:jmespath-java:1.12.783 - https://aws.amazon.com/sdkforjava)
* Titanium JSON-LD 1.1 (JRE11) (com.apicatalog:titanium-json-ld:1.3.2 - https://github.com/filip26/titanium-json-ld)
* HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc)
* com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.19.0 - https://drewnoakes.com/code/exif/)
* parso (com.epam:parso:2.0.14 - https://github.com/epam/parso)
* ClassMate (com.fasterxml:classmate:1.6.0 - https://github.com/FasterXML/java-classmate)
* Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.16.0 - https://github.com/FasterXML/jackson)
* Jackson-core (com.fasterxml.jackson.core:jackson-core:2.16.0 - https://github.com/FasterXML/jackson-core)
* jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.16.0 - https://github.com/FasterXML/jackson)
* Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 - http://github.com/FasterXML/jackson-dataformats-binary)
* Internet Time Utility (com.ethlo.time:itu:1.7.0 - https://github.com/ethlo/itu)
* ClassMate (com.fasterxml:classmate:1.5.1 - https://github.com/FasterXML/java-classmate)
* Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.19.0 - https://github.com/FasterXML/jackson)
* Jackson-core (com.fasterxml.jackson.core:jackson-core:2.19.0 - https://github.com/FasterXML/jackson-core)
* jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.19.0 - https://github.com/FasterXML/jackson)
* Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.17.2 - https://github.com/FasterXML/jackson-dataformats-binary)
* Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary)
* Jackson-dataformat-TOML (com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.15.2 - https://github.com/FasterXML/jackson-dataformats-text)
* Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.2 - https://github.com/FasterXML/jackson-dataformats-text)
* Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.4 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8)
* Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
* Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8)
* Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.19.0 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
* Jackson Jakarta-RS: base (com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-base:2.16.2 - https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-base)
* Jackson Jakarta-RS: JSON (com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider:2.16.2 - https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-json-provider)
* Jackson module: Jakarta XML Bind Annotations (jakarta.xml.bind) (com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:2.16.2 - https://github.com/FasterXML/jackson-modules-base)
* Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.15.4 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names)
* Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.0.1 - https://github.com/cowtowncoder/java-uuid-generator)
* Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.18.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names)
* Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.1.0 - https://github.com/cowtowncoder/java-uuid-generator)
* Woodstox (com.fasterxml.woodstox:woodstox-core:6.5.1 - https://github.com/FasterXML/woodstox)
* zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.16 - https://github.com/flipkart-incubator/zjsonpatch/)
* Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine)
* Caffeine cache (com.github.ben-manes.caffeine:caffeine:3.1.6 - https://github.com/ben-manes/caffeine)
* Caffeine cache (com.github.ben-manes.caffeine:caffeine:3.1.8 - https://github.com/ben-manes/caffeine)
* JSON.simple (com.github.cliftonlabs:json-simple:3.0.2 - https://cliftonlabs.github.io/json-simple/)
* btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf)
* jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils)
* jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils)
* json-patch (com.github.java-json-tools:json-patch:1.13 - https://github.com/java-json-tools/json-patch)
* json-schema-core (com.github.java-json-tools:json-schema-core:1.2.14 - https://github.com/java-json-tools/json-schema-core)
* json-schema-validator (com.github.java-json-tools:json-schema-validator:2.2.14 - https://github.com/java-json-tools/json-schema-validator)
* msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple)
* uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template)
* JCIP Annotations under Apache License (com.github.stephenc.jcip:jcip-annotations:1.0-1 - http://stephenc.github.com/jcip-annotations)
* Google APIs Client Library for Java (com.google.api-client:google-api-client:1.23.0 - https://github.com/google/google-api-java-client/google-api-client)
* Google Analytics API v3-rev145-1.23.0 (com.google.apis:google-api-services-analytics:v3-rev145-1.23.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-analytics)
* FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
* Gson (com.google.code.gson:gson:2.10.1 - https://github.com/google/gson/gson)
* error-prone annotations (com.google.errorprone:error_prone_annotations:2.10.0 - https://errorprone.info/error_prone_annotations)
* Gson (com.google.code.gson:gson:2.13.1 - https://github.com/google/gson)
* error-prone annotations (com.google.errorprone:error_prone_annotations:2.38.0 - https://errorprone.info/error_prone_annotations)
* Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess)
* Guava: Google Core Libraries for Java (com.google.guava:guava:32.0.0-jre - https://github.com/google/guava)
* Guava: Google Core Libraries for Java (JDK5 Backport) (com.google.guava:guava-jdk5:17.0 - http://code.google.com/p/guava-libraries/guava-jdk5)
* Guava: Google Core Libraries for Java (com.google.guava:guava:32.1.3-jre - https://github.com/google/guava)
* Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture)
* Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.23.0 - https://github.com/google/google-http-java-client/google-http-client)
* GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.41.7 - https://github.com/googleapis/google-http-java-client/google-http-client-gson)
* Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.23.0 - https://github.com/google/google-http-java-client/google-http-client-jackson2)
* Google Guice - Core Library (com.google.inject:guice:7.0.0 - https://github.com/google/guice/guice)
* Google Guice - Extensions - AssistedInject (com.google.inject.extensions:guice-assistedinject:7.0.0 - https://github.com/google/guice/extensions-parent/guice-assistedinject)
* J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/)
* J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/)
* Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.33.3 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client)
* ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap)
* libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/)
* Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.5 - https://jackcess.sourceforge.io)
* Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.2 - http://jackcessencrypt.sf.net)
* Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.8 - https://jackcess.sourceforge.io)
* Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.3 - http://jackcessencrypt.sf.net)
* json-path (com.jayway.jsonpath:json-path:2.9.0 - https://github.com/jayway/JsonPath)
* json-path-assert (com.jayway.jsonpath:json-path-assert:2.9.0 - https://github.com/jayway/JsonPath)
* Disruptor Framework (com.lmax:disruptor:3.4.2 - http://lmax-exchange.github.com/disruptor)
* MaxMind DB Reader (com.maxmind.db:maxmind-db:2.1.0 - http://dev.maxmind.com/)
* MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.17.0 - https://dev.maxmind.com/geoip?lang=en)
* Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.37.3 - https://bitbucket.org/connect2id/nimbus-jose-jwt)
* opencsv (com.opencsv:opencsv:5.9 - http://opencsv.sf.net)
* JsonSchemaValidator (com.networknt:json-schema-validator:1.0.76 - https://github.com/networknt/json-schema-validator)
* Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.28 - https://bitbucket.org/connect2id/nimbus-jose-jwt)
* Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.48 - https://bitbucket.org/connect2id/nimbus-jose-jwt)
* opencsv (com.opencsv:opencsv:5.11 - http://opencsv.sf.net)
* java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst)
* rome (com.rometools:rome:1.19.0 - http://rometools.com/rome)
* rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules)
* rome-utils (com.rometools:rome-utils:1.19.0 - http://rometools.com/rome-utils)
* mockwebserver (com.squareup.okhttp3:mockwebserver:4.12.0 - https://square.github.io/okhttp/)
* okhttp (com.squareup.okhttp3:okhttp:4.12.0 - https://square.github.io/okhttp/)
* okio (com.squareup.okio:okio:3.6.0 - https://github.com/square/okio/)
* okio (com.squareup.okio:okio-jvm:3.6.0 - https://github.com/square/okio/)
* T-Digest (com.tdunning:t-digest:3.1 - https://github.com/tdunning/t-digest)
* config (com.typesafe:config:1.3.3 - https://github.com/lightbend/config)
* ssl-config-core (com.typesafe:ssl-config-core_2.13:0.3.8 - https://github.com/lightbend/ssl-config)
@@ -98,102 +101,113 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging)
* JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk)
* SparseBitSet (com.zaxxer:SparseBitSet:1.3 - https://github.com/brettwooldridge/SparseBitSet)
* Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.9.4 - https://commons.apache.org/proper/commons-beanutils/)
* Apache Commons CLI (commons-cli:commons-cli:1.6.0 - https://commons.apache.org/proper/commons-cli/)
* Apache Commons Codec (commons-codec:commons-codec:1.16.0 - https://commons.apache.org/proper/commons-codec/)
* Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.10.1 - https://commons.apache.org/proper/commons-beanutils)
* Apache Commons CLI (commons-cli:commons-cli:1.9.0 - https://commons.apache.org/proper/commons-cli/)
* Apache Commons Codec (commons-codec:commons-codec:1.18.0 - https://commons.apache.org/proper/commons-codec/)
* Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/)
* Commons Digester (commons-digester:commons-digester:2.1 - http://commons.apache.org/digester/)
* Apache Commons IO (commons-io:commons-io:2.15.1 - https://commons.apache.org/proper/commons-io/)
* Apache Commons IO (commons-io:commons-io:2.19.0 - https://commons.apache.org/proper/commons-io/)
* Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/)
* Apache Commons Logging (commons-logging:commons-logging:1.3.0 - https://commons.apache.org/proper/commons-logging/)
* Apache Commons Validator (commons-validator:commons-validator:1.7 - http://commons.apache.org/proper/commons-validator/)
* Apache Commons Logging (commons-logging:commons-logging:1.3.5 - https://commons.apache.org/proper/commons-logging/)
* Apache Commons Validator (commons-validator:commons-validator:1.9.0 - http://commons.apache.org/proper/commons-validator/)
* GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson)
* broker-client (eu.openaire:broker-client:1.1.2 - http://api.openaire.eu/broker/broker-client)
* OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu)
* Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core)
* Metrics Core (io.dropwizard.metrics:metrics-core:4.2.25 - https://metrics.dropwizard.io/metrics-core)
* Graphite Integration for Metrics (io.dropwizard.metrics:metrics-graphite:4.1.5 - https://metrics.dropwizard.io/metrics-graphite)
* Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9)
* Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx)
* JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm)
* SWORD v2 Common Server Library (forked) (io.gdcc:sword2-server:2.0.0 - https://github.com/gdcc/sword2-server)
* micrometer-commons (io.micrometer:micrometer-commons:1.12.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-core (io.micrometer:micrometer-core:1.12.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-jakarta9 (io.micrometer:micrometer-jakarta9:1.12.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-observation (io.micrometer:micrometer-observation:1.12.6 - https://github.com/micrometer-metrics/micrometer)
* Netty/Buffer (io.netty:netty-buffer:4.1.106.Final - https://netty.io/netty-buffer/)
* micrometer-commons (io.micrometer:micrometer-commons:1.14.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-commons (io.micrometer:micrometer-commons:1.14.7 - https://github.com/micrometer-metrics/micrometer)
* micrometer-core (io.micrometer:micrometer-core:1.14.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-jakarta9 (io.micrometer:micrometer-jakarta9:1.14.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-observation (io.micrometer:micrometer-observation:1.14.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-observation (io.micrometer:micrometer-observation:1.14.7 - https://github.com/micrometer-metrics/micrometer)
* Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/)
* Netty/Codec (io.netty:netty-codec:4.1.106.Final - https://netty.io/netty-codec/)
* Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/)
* Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.53.Final - https://netty.io/netty-codec-http/)
* Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.53.Final - https://netty.io/netty-codec-socks/)
* Netty/Common (io.netty:netty-common:4.1.106.Final - https://netty.io/netty-common/)
* Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.86.Final - https://netty.io/netty-codec-http/)
* Netty/Codec/HTTP2 (io.netty:netty-codec-http2:4.1.86.Final - https://netty.io/netty-codec-http2/)
* Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.86.Final - https://netty.io/netty-codec-socks/)
* Netty/Common (io.netty:netty-common:4.1.99.Final - https://netty.io/netty-common/)
* Netty/Handler (io.netty:netty-handler:4.1.106.Final - https://netty.io/netty-handler/)
* Netty/Handler (io.netty:netty-handler:4.1.99.Final - https://netty.io/netty-handler/)
* Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.53.Final - https://netty.io/netty-handler-proxy/)
* Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.86.Final - https://netty.io/netty-handler-proxy/)
* Netty/Resolver (io.netty:netty-resolver:4.1.99.Final - https://netty.io/netty-resolver/)
* Netty/Transport (io.netty:netty-transport:4.1.106.Final - https://netty.io/netty-transport/)
* Netty/TomcatNative [BoringSSL - Static] (io.netty:netty-tcnative-boringssl-static:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-boringssl-static/)
* Netty/TomcatNative [OpenSSL - Classes] (io.netty:netty-tcnative-classes:2.0.56.Final - https://github.com/netty/netty-tcnative/netty-tcnative-classes/)
* Netty/Transport (io.netty:netty-transport:4.1.99.Final - https://netty.io/netty-transport/)
* Netty/Transport/Classes/Epoll (io.netty:netty-transport-classes-epoll:4.1.99.Final - https://netty.io/netty-transport-classes-epoll/)
* Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.99.Final - https://netty.io/netty-transport-native-epoll/)
* Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.106.Final - https://netty.io/netty-transport-native-unix-common/)
* Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.99.Final - https://netty.io/netty-transport-native-unix-common/)
* OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api)
* OpenTracing-noop (io.opentracing:opentracing-noop:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-noop)
* OpenTracing-util (io.opentracing:opentracing-util:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-util)
* Prometheus Java Simpleclient (io.prometheus:simpleclient:0.16.0 - http://github.com/prometheus/client_java/simpleclient)
* Prometheus Java Simpleclient Common (io.prometheus:simpleclient_common:0.16.0 - http://github.com/prometheus/client_java/simpleclient_common)
* Prometheus Java Simpleclient Httpserver (io.prometheus:simpleclient_httpserver:0.16.0 - http://github.com/prometheus/client_java/simpleclient_httpserver)
* Prometheus Java Span Context Supplier - Common (io.prometheus:simpleclient_tracer_common:0.16.0 - http://github.com/prometheus/client_java/simpleclient_tracer/simpleclient_tracer_common)
* Prometheus Java Span Context Supplier - OpenTelemetry (io.prometheus:simpleclient_tracer_otel:0.16.0 - http://github.com/prometheus/client_java/simpleclient_tracer/simpleclient_tracer_otel)
* Prometheus Java Span Context Supplier - OpenTelemetry Agent (io.prometheus:simpleclient_tracer_otel_agent:0.16.0 - http://github.com/prometheus/client_java/simpleclient_tracer/simpleclient_tracer_otel_agent)
* Google S2 geometry library (io.sgr:s2-geometry-library-java:1.0.0 - https://github.com/sgr-io/s2-geometry-library-java)
* Jandex: Core (io.smallrye:jandex:3.1.2 - https://smallrye.io)
* swagger-annotations (io.swagger:swagger-annotations:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations)
* swagger-compat-spec-parser (io.swagger:swagger-compat-spec-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-compat-spec-parser)
* swagger-core (io.swagger:swagger-core:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-core)
* swagger-models (io.swagger:swagger-models:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-models)
* swagger-parser (io.swagger:swagger-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser)
* swagger-annotations (io.swagger.core.v3:swagger-annotations:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations)
* swagger-annotations (io.swagger:swagger-annotations:1.6.9 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations)
* swagger-compat-spec-parser (io.swagger:swagger-compat-spec-parser:1.0.64 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-compat-spec-parser)
* swagger-core (io.swagger:swagger-core:1.6.9 - https://github.com/swagger-api/swagger-core/modules/swagger-core)
* swagger-models (io.swagger:swagger-models:1.6.9 - https://github.com/swagger-api/swagger-core/modules/swagger-models)
* swagger-parser (io.swagger:swagger-parser:1.0.64 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser)
* swagger-annotations (io.swagger.core.v3:swagger-annotations:2.2.8 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations)
* swagger-annotations-jakarta (io.swagger.core.v3:swagger-annotations-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations-jakarta)
* swagger-core (io.swagger.core.v3:swagger-core:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-core)
* swagger-core (io.swagger.core.v3:swagger-core:2.2.8 - https://github.com/swagger-api/swagger-core/modules/swagger-core)
* swagger-core-jakarta (io.swagger.core.v3:swagger-core-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-core-jakarta)
* swagger-integration-jakarta (io.swagger.core.v3:swagger-integration-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-integration-jakarta)
* swagger-jaxrs2-jakarta (io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-jaxrs2-jakarta)
* swagger-models (io.swagger.core.v3:swagger-models:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-models)
* swagger-models (io.swagger.core.v3:swagger-models:2.2.8 - https://github.com/swagger-api/swagger-core/modules/swagger-models)
* swagger-models-jakarta (io.swagger.core.v3:swagger-models-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-models-jakarta)
* swagger-parser (io.swagger.parser.v3:swagger-parser:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser)
* swagger-parser (io.swagger.parser.v3:swagger-parser-core:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-core)
* swagger-parser-v2-converter (io.swagger.parser.v3:swagger-parser-v2-converter:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter)
* swagger-parser-v3 (io.swagger.parser.v3:swagger-parser-v3:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3)
* swagger-parser (io.swagger.parser.v3:swagger-parser:2.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser)
* swagger-parser (io.swagger.parser.v3:swagger-parser-core:2.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-core)
* swagger-parser-v2-converter (io.swagger.parser.v3:swagger-parser-v2-converter:2.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter)
* swagger-parser-v3 (io.swagger.parser.v3:swagger-parser-v3:2.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3)
* Jakarta Dependency Injection (jakarta.inject:jakarta.inject-api:2.0.1 - https://github.com/eclipse-ee4j/injection-api)
* Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:3.0.2 - https://beanvalidation.org)
* JSR107 API and SPI (javax.cache:cache-api:1.1.1 - https://github.com/jsr107/jsr107spec)
* javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/)
* Bean Validation API (javax.validation:validation-api:1.1.0.Final - http://beanvalidation.org)
* jdbm (jdbm:jdbm:1.0 - no url defined)
* Joda-Time (joda-time:joda-time:2.12.5 - https://www.joda.org/joda-time/)
* Joda-Time (joda-time:joda-time:2.12.7 - https://www.joda.org/joda-time/)
* Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy)
* Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.14.11 - https://bytebuddy.net/byte-buddy)
* Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent)
* eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties)
* json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.19.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core)
* json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.36.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core)
* "Java Concurrency in Practice" book annotations (net.jcip:jcip-annotations:1.0 - http://jcip.net/)
* ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.0 - https://urielch.github.io/)
* ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.2 - https://urielch.github.io/)
* JSON Small and Fast Parser (net.minidev:json-smart:2.5.0 - https://urielch.github.io/)
* JSON Small and Fast Parser (net.minidev:json-smart:2.5.2 - https://urielch.github.io/)
* java-support (net.shibboleth.utilities:java-support:8.4.2 - http://shibboleth.net/java-support/)
* OGNL - Object Graph Navigation Library (ognl:ognl:3.3.4 - https://github.com/jkuhnert/ognl/)
* Abdera Core (org.apache.abdera:abdera-core:1.1.3 - http://abdera.apache.org/abdera-core)
* I18N Libraries (org.apache.abdera:abdera-i18n:1.1.3 - http://abdera.apache.org)
* Abdera Parser (org.apache.abdera:abdera-parser:1.1.3 - http://abdera.apache.org/abdera-parser)
* Apache Ant Core (org.apache.ant:ant:1.10.14 - https://ant.apache.org/)
* Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.14 - https://ant.apache.org/)
* Apache Commons BCEL (org.apache.bcel:bcel:6.7.0 - https://commons.apache.org/proper/commons-bcel)
* Apache Ant Core (org.apache.ant:ant:1.10.15 - https://ant.apache.org/)
* Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.15 - https://ant.apache.org/)
* Apache Commons BCEL (org.apache.bcel:bcel:6.10.0 - https://commons.apache.org/proper/commons-bcel)
* Calcite Core (org.apache.calcite:calcite-core:1.35.0 - https://calcite.apache.org)
* Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org)
* Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica)
* Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica)
* Apache Commons Collections (org.apache.commons:commons-collections4:4.4 - https://commons.apache.org/proper/commons-collections/)
* Apache Commons Compress (org.apache.commons:commons-compress:1.26.0 - https://commons.apache.org/proper/commons-compress/)
* Apache Commons Configuration (org.apache.commons:commons-configuration2:2.10.1 - https://commons.apache.org/proper/commons-configuration/)
* Apache Commons CSV (org.apache.commons:commons-csv:1.10.0 - https://commons.apache.org/proper/commons-csv/)
* Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.11.0 - https://commons.apache.org/dbcp/)
* Apache Commons Collections (org.apache.commons:commons-collections4:4.5.0 - https://commons.apache.org/proper/commons-collections/)
* Apache Commons Compress (org.apache.commons:commons-compress:1.27.1 - https://commons.apache.org/proper/commons-compress/)
* Apache Commons Configuration (org.apache.commons:commons-configuration2:2.12.0 - https://commons.apache.org/proper/commons-configuration/)
* Apache Commons CSV (org.apache.commons:commons-csv:1.14.0 - https://commons.apache.org/proper/commons-csv/)
* Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.13.0 - https://commons.apache.org/proper/commons-dbcp/)
* Apache Commons Digester (org.apache.commons:commons-digester3:3.2 - http://commons.apache.org/digester/)
* Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/)
* Apache Commons Exec (org.apache.commons:commons-exec:1.4.0 - https://commons.apache.org/proper/commons-exec/)
* Apache Commons Lang (org.apache.commons:commons-lang3:3.14.0 - https://commons.apache.org/proper/commons-lang/)
* Apache Commons Lang (org.apache.commons:commons-lang3:3.17.0 - https://commons.apache.org/proper/commons-lang/)
* Apache Commons Math (org.apache.commons:commons-math3:3.6.1 - http://commons.apache.org/proper/commons-math/)
* Apache Commons Pool (org.apache.commons:commons-pool2:2.12.0 - https://commons.apache.org/proper/commons-pool/)
* Apache Commons Text (org.apache.commons:commons-text:1.10.0 - https://commons.apache.org/proper/commons-text)
* Apache Commons Pool (org.apache.commons:commons-pool2:2.12.1 - https://commons.apache.org/proper/commons-pool/)
* Apache Commons Text (org.apache.commons:commons-text:1.13.1 - https://commons.apache.org/proper/commons-text)
* Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client)
* Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework)
* Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes)
@@ -207,120 +221,130 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.16 - http://hc.apache.org/httpcomponents-core-ga)
* Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.14 - http://hc.apache.org/httpcomponents-client-ga)
* Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.1.3 - https://hc.apache.org/httpcomponents-client-5.0.x/5.1.3/httpclient5/)
* Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.3.1 - https://hc.apache.org/httpcomponents-client-5.0.x/5.3.1/httpclient5/)
* Apache HttpClient (org.apache.httpcomponents.client5:httpclient5:5.4.4 - https://hc.apache.org/httpcomponents-client-5.4.x/5.4.4/httpclient5/)
* Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5/)
* Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.2.4 - https://hc.apache.org/httpcomponents-core-5.2.x/5.2.4/httpcore5/)
* Apache HttpComponents Core HTTP/1.1 (org.apache.httpcomponents.core5:httpcore5:5.3.4 - https://hc.apache.org/httpcomponents-core-5.3.x/5.3.4/httpcore5/)
* Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/)
* Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.2.4 - https://hc.apache.org/httpcomponents-core-5.2.x/5.2.4/httpcore5-h2/)
* Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.10 - http://james.apache.org/mime4j/apache-mime4j-core)
* Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.11 - http://james.apache.org/mime4j/apache-mime4j-dom)
* Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:4.9.0 - https://jena.apache.org/apache-jena-libs/)
* Apache Jena - ARQ (org.apache.jena:jena-arq:4.9.0 - https://jena.apache.org/jena-arq/)
* Apache Jena - Base (org.apache.jena:jena-base:4.9.0 - https://jena.apache.org/jena-base/)
* Apache Jena - Core (org.apache.jena:jena-core:4.9.0 - https://jena.apache.org/jena-core/)
* Apache Jena - DBOE Base (org.apache.jena:jena-dboe-base:4.9.0 - https://jena.apache.org/jena-dboe-base/)
* Apache Jena - DBOE Indexes (org.apache.jena:jena-dboe-index:4.9.0 - https://jena.apache.org/jena-dboe-index/)
* Apache Jena - DBOE Storage (org.apache.jena:jena-dboe-storage:4.9.0 - https://jena.apache.org/jena-dboe-storage/)
* Apache Jena - DBOE Transactional Datastructures (org.apache.jena:jena-dboe-trans-data:4.9.0 - https://jena.apache.org/jena-dboe-trans-data/)
* Apache Jena - DBOE Transactions (org.apache.jena:jena-dboe-transaction:4.9.0 - https://jena.apache.org/jena-dboe-transaction/)
* Apache Jena - IRI (org.apache.jena:jena-iri:4.9.0 - https://jena.apache.org/jena-iri/)
* Apache Jena - RDF Connection (org.apache.jena:jena-rdfconnection:4.9.0 - https://jena.apache.org/jena-rdfconnection/)
* Apache Jena - RDF Patch (org.apache.jena:jena-rdfpatch:4.9.0 - https://jena.apache.org/jena-rdfpatch/)
* Apache Jena - SHACL (org.apache.jena:jena-shacl:4.9.0 - https://jena.apache.org/jena-shacl/)
* Apache Jena - ShEx (org.apache.jena:jena-shex:4.9.0 - https://jena.apache.org/jena-shex/)
* Apache Jena - TDB1 (Native Triple Store) (org.apache.jena:jena-tdb:4.9.0 - https://jena.apache.org/jena-tdb/)
* Apache Jena - TDB2 (Native Triple Store) (org.apache.jena:jena-tdb2:4.9.0 - https://jena.apache.org/jena-tdb2/)
* Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.3.4 - https://hc.apache.org/httpcomponents-core-5.3.x/5.3.4/httpcore5-h2/)
* Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-core)
* Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.12 - http://james.apache.org/mime4j/apache-mime4j-dom)
* jclouds blobstore core (org.apache.jclouds:jclouds-blobstore:2.7.0 - https://jclouds.apache.org/jclouds-blobstore/)
* jclouds Components Core (org.apache.jclouds:jclouds-core:2.7.0 - https://jclouds.apache.org/jclouds-core/)
* jclouds filesystem core (org.apache.jclouds.api:filesystem:2.7.0 - https://jclouds.apache.org/filesystem/)
* jclouds s3 api (org.apache.jclouds.api:s3:2.7.0 - https://jclouds.apache.org/s3/)
* jclouds sts api (org.apache.jclouds.api:sts:2.7.0 - https://jclouds.apache.org/sts/)
* jclouds Amazon Simple Storage Service (S3) provider (org.apache.jclouds.provider:aws-s3:2.7.0 - https://jclouds.apache.org/aws-s3/)
* Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:4.10.0 - https://jena.apache.org/apache-jena-libs/)
* Apache Jena - ARQ (org.apache.jena:jena-arq:4.10.0 - https://jena.apache.org/jena-arq/)
* Apache Jena - Base (org.apache.jena:jena-base:4.10.0 - https://jena.apache.org/jena-base/)
* Apache Jena - Core (org.apache.jena:jena-core:4.10.0 - https://jena.apache.org/jena-core/)
* Apache Jena - DBOE Base (org.apache.jena:jena-dboe-base:4.10.0 - https://jena.apache.org/jena-dboe-base/)
* Apache Jena - DBOE Indexes (org.apache.jena:jena-dboe-index:4.10.0 - https://jena.apache.org/jena-dboe-index/)
* Apache Jena - DBOE Storage (org.apache.jena:jena-dboe-storage:4.10.0 - https://jena.apache.org/jena-dboe-storage/)
* Apache Jena - DBOE Transactional Datastructures (org.apache.jena:jena-dboe-trans-data:4.10.0 - https://jena.apache.org/jena-dboe-trans-data/)
* Apache Jena - DBOE Transactions (org.apache.jena:jena-dboe-transaction:4.10.0 - https://jena.apache.org/jena-dboe-transaction/)
* Apache Jena - IRI (org.apache.jena:jena-iri:4.10.0 - https://jena.apache.org/jena-iri/)
* Apache Jena - RDF Connection (org.apache.jena:jena-rdfconnection:4.10.0 - https://jena.apache.org/jena-rdfconnection/)
* Apache Jena - RDF Patch (org.apache.jena:jena-rdfpatch:4.10.0 - https://jena.apache.org/jena-rdfpatch/)
* Apache Jena - SHACL (org.apache.jena:jena-shacl:4.10.0 - https://jena.apache.org/jena-shacl/)
* Apache Jena - ShEx (org.apache.jena:jena-shex:4.10.0 - https://jena.apache.org/jena-shex/)
* Apache Jena - TDB1 (Native Triple Store) (org.apache.jena:jena-tdb:4.10.0 - https://jena.apache.org/jena-tdb/)
* Apache Jena - TDB2 (Native Triple Store) (org.apache.jena:jena-tdb2:4.10.0 - https://jena.apache.org/jena-tdb2/)
* Kerby-kerb core (org.apache.kerby:kerb-core:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-core)
* Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util)
* Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1)
* Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix)
* Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-1.2-api/)
* Apache Log4j API (org.apache.logging.log4j:log4j-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/)
* Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/)
* Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-jul/)
* Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/)
* Apache Log4j API (org.apache.logging.log4j:log4j-api:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/)
* Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/)
* Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-jul/)
* Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/)
* Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j-impl/)
* Apache Log4j SLF4J 2.0 Binding (org.apache.logging.log4j:log4j-slf4j2-impl:2.21.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j2-impl/)
* Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-web/)
* Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common)
* Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu)
* Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji)
* Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori)
* Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic)
* Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn)
* Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel)
* Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs)
* Lucene Classification (org.apache.lucene:lucene-classification:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-classification)
* Lucene codecs (org.apache.lucene:lucene-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-codecs)
* Lucene Core (org.apache.lucene:lucene-core:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-core)
* Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-expressions)
* Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-grouping)
* Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-highlighter)
* Lucene Join (org.apache.lucene:lucene-join:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-join)
* Lucene Memory (org.apache.lucene:lucene-memory:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-memory)
* Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-misc)
* Lucene Queries (org.apache.lucene:lucene-queries:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queries)
* Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queryparser)
* Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-sandbox)
* Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras)
* Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial3d)
* Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-suggest)
* Apache FontBox (org.apache.pdfbox:fontbox:2.0.31 - http://pdfbox.apache.org/)
* Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/)
* SLF4J 2 Provider for Log4j API (org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j2-impl/)
* Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-web/)
* Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common)
* Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu)
* Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji)
* Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori)
* Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic)
* Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn)
* Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel)
* Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs)
* Lucene Classification (org.apache.lucene:lucene-classification:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-classification)
* Lucene codecs (org.apache.lucene:lucene-codecs:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-codecs)
* Lucene Core (org.apache.lucene:lucene-core:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-core)
* Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-expressions)
* Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-grouping)
* Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-highlighter)
* Lucene Join (org.apache.lucene:lucene-join:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-join)
* Lucene Memory (org.apache.lucene:lucene-memory:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-memory)
* Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-misc)
* Lucene Queries (org.apache.lucene:lucene-queries:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-queries)
* Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-queryparser)
* Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-sandbox)
* Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras)
* Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-spatial3d)
* Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.4 - https://lucene.apache.org/lucene-parent/lucene-suggest)
* Apache FontBox (org.apache.pdfbox:fontbox:2.0.34 - http://pdfbox.apache.org/)
* PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/)
* Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/)
* Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox/)
* Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox-tools/)
* Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.31 - https://www.apache.org/pdfbox-parent/xmpbox/)
* Apache POI - Common (org.apache.poi:poi:5.2.5 - https://poi.apache.org/)
* Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.5 - https://poi.apache.org/)
* Apache POI (org.apache.poi:poi-ooxml-lite:5.2.5 - https://poi.apache.org/)
* Apache POI (org.apache.poi:poi-scratchpad:5.2.5 - https://poi.apache.org/)
* Apache Solr Core (org.apache.solr:solr-core:8.11.3 - https://lucene.apache.org/solr-parent/solr-core)
* Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.3 - https://lucene.apache.org/solr-parent/solr-solrj)
* Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.34 - https://www.apache.org/pdfbox-parent/pdfbox/)
* Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.34 - https://www.apache.org/pdfbox-parent/pdfbox-tools/)
* Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.34 - https://www.apache.org/pdfbox-parent/xmpbox/)
* Apache POI - Common (org.apache.poi:poi:5.4.1 - https://poi.apache.org/)
* Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.4.1 - https://poi.apache.org/)
* Apache POI (org.apache.poi:poi-ooxml-lite:5.4.1 - https://poi.apache.org/)
* Apache POI (org.apache.poi:poi-scratchpad:5.4.1 - https://poi.apache.org/)
* Apache XML Security for Java (org.apache.santuario:xmlsec:2.3.4 - https://santuario.apache.org/)
* Apache Solr Core (org.apache.solr:solr-core:8.11.4 - https://lucene.apache.org/solr-parent/solr-core)
* Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.4 - https://lucene.apache.org/solr-parent/solr-solrj)
* Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl)
* Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec)
* Apache Thrift (org.apache.thrift:libthrift:0.18.1 - http://thrift.apache.org)
* Apache Tika core (org.apache.tika:tika-core:2.9.2 - https://tika.apache.org/)
* Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.2 - https://tika.apache.org/tika-parser-apple-module/)
* Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.2 - https://tika.apache.org/tika-parser-audiovideo-module/)
* Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.2 - https://tika.apache.org/tika-parser-cad-module/)
* Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.2 - https://tika.apache.org/tika-parser-code-module/)
* Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.2 - https://tika.apache.org/tika-parser-crypto-module/)
* Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.2 - https://tika.apache.org/tika-parser-digest-commons/)
* Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.2 - https://tika.apache.org/tika-parser-font-module/)
* Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.2 - https://tika.apache.org/tika-parser-html-module/)
* Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.2 - https://tika.apache.org/tika-parser-image-module/)
* Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.2 - https://tika.apache.org/tika-parser-mail-commons/)
* Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.2 - https://tika.apache.org/tika-parser-mail-module/)
* Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.2 - https://tika.apache.org/tika-parser-microsoft-module/)
* Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.2 - https://tika.apache.org/tika-parser-miscoffice-module/)
* Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.2 - https://tika.apache.org/tika-parser-news-module/)
* Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.2 - https://tika.apache.org/tika-parser-ocr-module/)
* Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.2 - https://tika.apache.org/tika-parser-pdf-module/)
* Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.2 - https://tika.apache.org/tika-parser-pkg-module/)
* Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.2 - https://tika.apache.org/tika-parser-text-module/)
* Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.2 - https://tika.apache.org/tika-parser-webarchive-module/)
* Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.2 - https://tika.apache.org/tika-parser-xml-module/)
* Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.2 - https://tika.apache.org/tika-parser-xmp-commons/)
* Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.2 - https://tika.apache.org/tika-parser-zip-commons/)
* Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.2 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/)
* tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:10.1.24 - https://tomcat.apache.org/)
* tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:10.1.24 - https://tomcat.apache.org/)
* tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:10.1.24 - https://tomcat.apache.org/)
* Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-core/)
* Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.2 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/)
* Apache Thrift (org.apache.thrift:libthrift:0.19.0 - http://thrift.apache.org)
* Apache Tika core (org.apache.tika:tika-core:2.9.4 - https://tika.apache.org/)
* Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.4 - https://tika.apache.org/tika-parser-apple-module/)
* Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.4 - https://tika.apache.org/tika-parser-audiovideo-module/)
* Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.4 - https://tika.apache.org/tika-parser-cad-module/)
* Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.4 - https://tika.apache.org/tika-parser-code-module/)
* Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.4 - https://tika.apache.org/tika-parser-crypto-module/)
* Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.4 - https://tika.apache.org/tika-parser-digest-commons/)
* Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.4 - https://tika.apache.org/tika-parser-font-module/)
* Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.4 - https://tika.apache.org/tika-parser-html-module/)
* Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.4 - https://tika.apache.org/tika-parser-image-module/)
* Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.4 - https://tika.apache.org/tika-parser-mail-commons/)
* Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.4 - https://tika.apache.org/tika-parser-mail-module/)
* Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.4 - https://tika.apache.org/tika-parser-microsoft-module/)
* Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.4 - https://tika.apache.org/tika-parser-miscoffice-module/)
* Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.4 - https://tika.apache.org/tika-parser-news-module/)
* Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.4 - https://tika.apache.org/tika-parser-ocr-module/)
* Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.4 - https://tika.apache.org/tika-parser-pdf-module/)
* Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.4 - https://tika.apache.org/tika-parser-pkg-module/)
* Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.4 - https://tika.apache.org/tika-parser-text-module/)
* Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.4 - https://tika.apache.org/tika-parser-webarchive-module/)
* Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.4 - https://tika.apache.org/tika-parser-xml-module/)
* Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.4 - https://tika.apache.org/tika-parser-xmp-commons/)
* Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.4 - https://tika.apache.org/tika-parser-zip-commons/)
* Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.4 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/)
* tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:10.1.40 - https://tomcat.apache.org/)
* tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:10.1.40 - https://tomcat.apache.org/)
* tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:10.1.40 - https://tomcat.apache.org/)
* Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.4.1 - http://velocity.apache.org/engine/devel/velocity-engine-core/)
* Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/)
* Apache Velocity Tools - Generic tools (org.apache.velocity.tools:velocity-tools-generic:3.1 - https://velocity.apache.org/tools/devel/velocity-tools-generic/)
* Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.14 - http://ws.apache.org/axiom/)
* Axiom Impl (org.apache.ws.commons.axiom:axiom-impl:1.2.14 - http://ws.apache.org/axiom/)
* XmlBeans (org.apache.xmlbeans:xmlbeans:5.2.0 - https://xmlbeans.apache.org/)
* XmlBeans (org.apache.xmlbeans:xmlbeans:5.3.0 - https://xmlbeans.apache.org/)
* Apache ZooKeeper - Server (org.apache.zookeeper:zookeeper:3.6.2 - http://zookeeper.apache.org/zookeeper)
* Apache ZooKeeper - Jute (org.apache.zookeeper:zookeeper-jute:3.6.2 - http://zookeeper.apache.org/zookeeper-jute)
* org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian)
* AssertJ Core (org.assertj:assertj-core:3.24.2 - https://assertj.github.io/doc/#assertj-core)
* AssertJ Core (org.assertj:assertj-core:3.26.3 - https://assertj.github.io/doc/#assertj-core)
* Evo Inflector (org.atteo:evo-inflector:1.3 - http://atteo.org/static/evo-inflector)
* Awaitility (org.awaitility:awaitility:4.2.1 - http://awaitility.org)
* attoparser (org.attoparser:attoparser:2.0.7.RELEASE - https://www.attoparser.org)
* Awaitility (org.awaitility:awaitility:4.2.2 - http://awaitility.org)
* jose4j (org.bitbucket.b_c:jose4j:0.6.5 - https://bitbucket.org/b_c/jose4j/)
* TagSoup (org.ccil.cowan.tagsoup:tagsoup:1.2.1 - http://home.ccil.org/~cowan/XML/tagsoup/)
* Woodstox (org.codehaus.woodstox:wstx-asl:3.2.6 - http://woodstox.codehaus.org)
* Cryptacular Library (org.cryptacular:cryptacular:1.2.5 - http://www.cryptacular.org)
* jems (org.dmfs:jems:1.18 - https://github.com/dmfs/jems)
* rfc3986-uri (org.dmfs:rfc3986-uri:0.8.1 - https://github.com/dmfs/uri-toolkit)
* Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty)
@@ -335,124 +359,134 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client)
* Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation)
* Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy)
* Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http)
* Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io)
* Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.57.v20241219 - https://jetty.org/jetty-deploy/)
* Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.57.v20241219 - https://jetty.org/jetty-http/)
* Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.57.v20241219 - https://jetty.org/jetty-io/)
* Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx)
* Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite)
* Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security)
* Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security)
* Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server)
* Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet)
* Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.57.v20241219 - https://jetty.org/jetty-security/)
* Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.57.v20241219 - https://jetty.org/jetty-server/)
* Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.57.v20241219 - https://jetty.org/jetty-servlet/)
* Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-servlets)
* Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util)
* Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax)
* Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp)
* Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.57.v20241219 - https://jetty.org/jetty-util/)
* Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.57.v20241219 - https://jetty.org/jetty-util-ajax/)
* Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.57.v20241219 - https://jetty.org/jetty-webapp/)
* Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-xml)
* Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-xml)
* Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.57.v20241219 - https://jetty.org/jetty-xml/)
* Jetty :: ALPN :: API (org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 - http://www.eclipse.org/jetty/alpn-api)
* Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client)
* Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common)
* Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-common/)
* Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack)
* Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport)
* Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.15.v20190215 - https://eclipse.org/jetty/http2-parent/http2-server)
* Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-server)
* Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas)
* Ehcache (org.ehcache:ehcache:3.10.8 - http://ehcache.org)
* flyway-core (org.flywaydb:flyway-core:10.10.0 - https://flywaydb.org/flyway-core)
* flyway-database-postgresql (org.flywaydb:flyway-database-postgresql:10.10.0 - https://flywaydb.org/flyway-database-postgresql)
* flyway-core (org.flywaydb:flyway-core:10.22.0 - https://flywaydb.org/flyway-core)
* flyway-database-postgresql (org.flywaydb:flyway-database-postgresql:10.22.0 - https://flywaydb.org/flyway-database-postgresql)
* Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava)
* Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava)
* jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:8.0.1.Final - http://hibernate.org/validator/hibernate-validator)
* Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:8.0.1.Final - http://hibernate.org/validator/hibernate-validator-cdi)
* org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations)
* leveldb (org.iq80.leveldb:leveldb:0.12 - http://github.com/dain/leveldb/leveldb)
* leveldb-api (org.iq80.leveldb:leveldb-api:0.12 - http://github.com/dain/leveldb/leveldb-api)
* Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/)
* JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.org)
* Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/)
* JBoss Logging 3 (org.jboss.logging:jboss-logging:3.6.1.Final - http://www.jboss.org)
* JDOM (org.jdom:jdom2:2.0.6.1 - http://www.jdom.org)
* jtwig-core (org.jtwig:jtwig-core:5.87.0.RELEASE - http://jtwig.org)
* jtwig-reflection (org.jtwig:jtwig-reflection:5.87.0.RELEASE - http://jtwig.org)
* jtwig-spring (org.jtwig:jtwig-spring:5.87.0.RELEASE - http://jtwig.org)
* jtwig-spring-boot-starter (org.jtwig:jtwig-spring-boot-starter:5.87.0.RELEASE - http://jtwig.org)
* jtwig-web (org.jtwig:jtwig-web:5.87.0.RELEASE - http://jtwig.org)
* IntelliJ IDEA Annotations (org.jetbrains:annotations:13.0 - http://www.jetbrains.org)
* Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.21 - https://kotlinlang.org/)
* Kotlin Stdlib Common (org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 - https://kotlinlang.org/)
* Kotlin Stdlib Jdk7 (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 - https://kotlinlang.org/)
* Kotlin Stdlib Jdk8 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.21 - https://kotlinlang.org/)
* Proj4J (org.locationtech.proj4j:proj4j:1.1.5 - https://github.com/locationtech/proj4j)
* Spatial4J (org.locationtech.spatial4j:spatial4j:0.7 - https://projects.eclipse.org/projects/locationtech.spatial4j)
* MockServer Java Client (org.mock-server:mockserver-client-java:5.11.2 - http://www.mock-server.com)
* MockServer Core (org.mock-server:mockserver-core:5.11.2 - http://www.mock-server.com)
* MockServer JUnit 4 Integration (org.mock-server:mockserver-junit-rule:5.11.2 - http://www.mock-server.com)
* MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.11.2 - http://www.mock-server.com)
* Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty)
* Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester)
* Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util)
* Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api)
* jwarc (org.netpreserve:jwarc:0.29.0 - https://github.com/iipc/jwarc)
* MockServer Java Client (org.mock-server:mockserver-client-java:5.15.0 - https://www.mock-server.com)
* MockServer Core (org.mock-server:mockserver-core:5.15.0 - https://www.mock-server.com)
* MockServer JUnit 4 Integration (org.mock-server:mockserver-junit-rule:5.15.0 - https://www.mock-server.com)
* MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.15.0 - https://www.mock-server.com)
* jwarc (org.netpreserve:jwarc:0.31.1 - https://github.com/iipc/jwarc)
* Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis)
* parboiled-core (org.parboiled:parboiled-core:1.3.1 - http://parboiled.org)
* parboiled-java (org.parboiled:parboiled-java:1.3.1 - http://parboiled.org)
* org.roaringbitmap:RoaringBitmap (org.roaringbitmap:RoaringBitmap:0.9.45 - https://github.com/RoaringBitmap/RoaringBitmap)
* org.roaringbitmap:shims (org.roaringbitmap:shims:0.9.45 - https://github.com/RoaringBitmap/RoaringBitmap)
* OpenSAML :: Core (org.opensaml:opensaml-core:4.3.2 - http://shibboleth.net/opensaml-core/)
* OpenSAML :: Messaging API (org.opensaml:opensaml-messaging-api:4.3.2 - http://shibboleth.net/opensaml-messaging-api/)
* OpenSAML :: Profile API (org.opensaml:opensaml-profile-api:4.3.2 - http://shibboleth.net/opensaml-profile-api/)
* OpenSAML :: SAML Provider API (org.opensaml:opensaml-saml-api:4.3.2 - http://shibboleth.net/opensaml-saml-api/)
* OpenSAML :: SAML Provider Implementations (org.opensaml:opensaml-saml-impl:4.3.2 - http://shibboleth.net/opensaml-saml-impl/)
* OpenSAML :: Security API (org.opensaml:opensaml-security-api:4.3.2 - http://shibboleth.net/opensaml-security-api/)
* OpenSAML :: Security Implementation (org.opensaml:opensaml-security-impl:4.3.2 - http://shibboleth.net/opensaml-security-impl/)
* OpenSAML :: SOAP Provider API (org.opensaml:opensaml-soap-api:4.3.2 - http://shibboleth.net/opensaml-soap-api/)
* OpenSAML :: SOAP Provider Implementations (org.opensaml:opensaml-soap-impl:4.3.2 - http://shibboleth.net/opensaml-soap-impl/)
* OpenSAML :: Storage API (org.opensaml:opensaml-storage-api:4.3.2 - http://shibboleth.net/opensaml-storage-api/)
* OpenSAML :: XML Security API (org.opensaml:opensaml-xmlsec-api:4.3.2 - http://shibboleth.net/opensaml-xmlsec-api/)
* OpenSAML :: XML Security Implementation (org.opensaml:opensaml-xmlsec-impl:4.3.2 - http://shibboleth.net/opensaml-xmlsec-impl/)
* org.roaringbitmap:RoaringBitmap (org.roaringbitmap:RoaringBitmap:1.0.0 - https://github.com/RoaringBitmap/RoaringBitmap)
* RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/)
* Scala Library (org.scala-lang:scala-library:2.13.11 - https://www.scala-lang.org/)
* Scala Library (org.scala-lang:scala-library:2.13.2 - https://www.scala-lang.org/)
* Scala Compiler (org.scala-lang:scala-reflect:2.13.0 - https://www.scala-lang.org/)
* scala-collection-compat (org.scala-lang.modules:scala-collection-compat_2.13:2.1.6 - http://www.scala-lang.org/)
* scala-java8-compat (org.scala-lang.modules:scala-java8-compat_2.13:0.9.0 - http://www.scala-lang.org/)
* scala-parser-combinators (org.scala-lang.modules:scala-parser-combinators_2.13:1.1.2 - http://www.scala-lang.org/)
* scala-xml (org.scala-lang.modules:scala-xml_2.13:1.3.0 - http://www.scala-lang.org/)
* JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert)
* JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:2.0.11 - http://www.slf4j.org)
* Spring AOP (org.springframework:spring-aop:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Beans (org.springframework:spring-beans:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Context (org.springframework:spring-context:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Context Support (org.springframework:spring-context-support:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Core (org.springframework:spring-core:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Expression Language (SpEL) (org.springframework:spring-expression:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Commons Logging Bridge (org.springframework:spring-jcl:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring JDBC (org.springframework:spring-jdbc:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Object/Relational Mapping (org.springframework:spring-orm:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring TestContext Framework (org.springframework:spring-test:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Transaction (org.springframework:spring-tx:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Web (org.springframework:spring-web:6.1.8 - https://github.com/spring-projects/spring-framework)
* Spring Web MVC (org.springframework:spring-webmvc:6.1.8 - https://github.com/spring-projects/spring-framework)
* spring-boot (org.springframework.boot:spring-boot:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-actuator (org.springframework.boot:spring-boot-actuator:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot)
* Spring Boot Configuration Processor (org.springframework.boot:spring-boot-configuration-processor:2.0.0.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-tools/spring-boot-configuration-processor)
* spring-boot-starter (org.springframework.boot:spring-boot-starter:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-test (org.springframework.boot:spring-boot-test:3.2.6 - https://spring.io/projects/spring-boot)
* spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot)
* Spring Data Core (org.springframework.data:spring-data-commons:3.2.6 - https://spring.io/projects/spring-data)
* Spring Data REST - Core (org.springframework.data:spring-data-rest-core:4.2.6 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core)
* Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:4.2.6 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc)
* Spring HATEOAS (org.springframework.hateoas:spring-hateoas:2.2.2 - https://github.com/spring-projects/spring-hateoas)
* JSONassert (org.skyscreamer:jsonassert:1.5.3 - https://github.com/skyscreamer/JSONassert)
* JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:2.0.17 - http://www.slf4j.org)
* Spring AOP (org.springframework:spring-aop:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Beans (org.springframework:spring-beans:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Context (org.springframework:spring-context:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Context Support (org.springframework:spring-context-support:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Core (org.springframework:spring-core:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Expression Language (SpEL) (org.springframework:spring-expression:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Commons Logging Bridge (org.springframework:spring-jcl:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring JDBC (org.springframework:spring-jdbc:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Object/Relational Mapping (org.springframework:spring-orm:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring TestContext Framework (org.springframework:spring-test:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Transaction (org.springframework:spring-tx:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Web (org.springframework:spring-web:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Web MVC (org.springframework:spring-webmvc:6.2.7 - https://github.com/spring-projects/spring-framework)
* spring-boot (org.springframework.boot:spring-boot:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-actuator (org.springframework.boot:spring-boot-actuator:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter (org.springframework.boot:spring-boot-starter:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-thymeleaf (org.springframework.boot:spring-boot-starter-thymeleaf:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-test (org.springframework.boot:spring-boot-test:3.4.5 - https://spring.io/projects/spring-boot)
* spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:3.4.5 - https://spring.io/projects/spring-boot)
* Spring Data Core (org.springframework.data:spring-data-commons:3.4.5 - https://spring.io/projects/spring-data)
* Spring Data REST - Core (org.springframework.data:spring-data-rest-core:4.4.5 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core)
* Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:4.4.5 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc)
* Spring HATEOAS (org.springframework.hateoas:spring-hateoas:2.4.1 - https://github.com/spring-projects/spring-hateoas)
* Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:3.0.0 - https://github.com/spring-projects/spring-plugin/spring-plugin-core)
* spring-security-config (org.springframework.security:spring-security-config:6.2.4 - https://spring.io/projects/spring-security)
* spring-security-core (org.springframework.security:spring-security-core:6.2.4 - https://spring.io/projects/spring-security)
* spring-security-crypto (org.springframework.security:spring-security-crypto:6.2.4 - https://spring.io/projects/spring-security)
* spring-security-test (org.springframework.security:spring-security-test:6.2.4 - https://spring.io/projects/spring-security)
* spring-security-web (org.springframework.security:spring-security-web:6.2.4 - https://spring.io/projects/spring-security)
* spring-security-config (org.springframework.security:spring-security-config:6.4.5 - https://spring.io/projects/spring-security)
* spring-security-core (org.springframework.security:spring-security-core:6.4.5 - https://spring.io/projects/spring-security)
* spring-security-crypto (org.springframework.security:spring-security-crypto:6.4.5 - https://spring.io/projects/spring-security)
* spring-security-saml2-service-provider (org.springframework.security:spring-security-saml2-service-provider:6.4.5 - https://spring.io/projects/spring-security)
* spring-security-test (org.springframework.security:spring-security-test:6.4.5 - https://spring.io/projects/spring-security)
* spring-security-web (org.springframework.security:spring-security-web:6.4.5 - https://spring.io/projects/spring-security)
* thymeleaf (org.thymeleaf:thymeleaf:3.1.3.RELEASE - http://www.thymeleaf.org/thymeleaf-lib/thymeleaf)
* thymeleaf-spring6 (org.thymeleaf:thymeleaf-spring6:3.1.3.RELEASE - http://www.thymeleaf.org/thymeleaf-lib/thymeleaf-spring6)
* unbescape (org.unbescape:unbescape:1.1.6.RELEASE - http://www.unbescape.org)
* snappy-java (org.xerial.snappy:snappy-java:1.1.10.1 - https://github.com/xerial/snappy-java)
* xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/)
* org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.10.0 - https://www.xmlunit.org/)
* org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/)
* org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.8.0 - https://www.xmlunit.org/xmlunit-placeholders/)
* SnakeYAML (org.yaml:snakeyaml:2.2 - https://bitbucket.org/snakeyaml/snakeyaml)
* software.amazon.ion:ion-java (software.amazon.ion:ion-java:1.0.2 - https://github.com/amznlabs/ion-java/)
* org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.9.1 - https://www.xmlunit.org/xmlunit-placeholders/)
* SnakeYAML (org.yaml:snakeyaml:2.3 - https://bitbucket.org/snakeyaml/snakeyaml)
* Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/)
BSD License:
@@ -463,27 +497,31 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* JSONLD Java :: Core (com.github.jsonld-java:jsonld-java:0.13.4 - http://github.com/jsonld-java/jsonld-java/jsonld-java/)
* curvesapi (com.github.virtuald:curvesapi:1.08 - https://github.com/virtuald/curvesapi)
* Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.15.0 - https://developers.google.com/protocol-buffers/protobuf-java/)
* Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.23.3 - https://developers.google.com/protocol-buffers/protobuf-java/)
* Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.24.3 - https://developers.google.com/protocol-buffers/protobuf-java/)
* JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/)
* dnsjava (dnsjava:dnsjava:2.1.9 - http://www.dnsjava.org)
* jmustache (com.samskivert:jmustache:1.15 - http://github.com/samskivert/jmustache)
* dnsjava (dnsjava:dnsjava:3.6.3 - https://github.com/dnsjava/dnsjava)
* jaxen (jaxen:jaxen:2.0.0 - http://www.cafeconleche.org/jaxen/jaxen)
* ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.13.1 - https://www.antlr.org/antlr4-runtime/)
* ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.13.2 - https://www.antlr.org/antlr4-runtime/)
* commons-compiler (org.codehaus.janino:commons-compiler:3.1.8 - http://janino-compiler.github.io/commons-compiler/)
* janino (org.codehaus.janino:janino:3.1.8 - http://janino-compiler.github.io/janino/)
* Stax2 API (org.codehaus.woodstox:stax2-api:4.2.1 - http://github.com/FasterXML/stax2-api)
* Hamcrest Date (org.exparity:hamcrest-date:2.0.8 - https://github.com/exparity/hamcrest-date)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/)
* Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/)
* HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/)
* HdrHistogram (org.hdrhistogram:HdrHistogram:2.2.2 - http://hdrhistogram.github.io/HdrHistogram/)
* JBibTeX (org.jbibtex:jbibtex:1.0.20 - http://www.jbibtex.org)
* asm (org.ow2.asm:asm:8.0.1 - http://asm.ow2.io/)
* asm-analysis (org.ow2.asm:asm-analysis:8.0.1 - http://asm.ow2.io/)
* asm-commons (org.ow2.asm:asm-commons:8.0.1 - http://asm.ow2.io/)
* asm-tree (org.ow2.asm:asm-tree:8.0.1 - http://asm.ow2.io/)
* asm-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/)
* PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.3 - https://jdbc.postgresql.org)
* PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.5 - https://jdbc.postgresql.org)
* Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections)
* JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio)
* XZ for Java (org.tukaani:xz:1.10 - https://tukaani.org/xz/java.html)
* XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/)
CC0:
@@ -497,7 +535,7 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:2.3.1 - http://jaxb.java.net/jaxb-bundles/jaxb-impl)
* Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca)
* Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
* Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.0.0 - https://projects.eclipse.org/projects/ee4j.servlet)
* Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.1.0 - https://projects.eclipse.org/projects/ee4j.servlet)
* jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta)
* JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/)
* javax.annotation API (javax.annotation:javax.annotation-api:1.3 - http://jcp.org/en/jsr/detail?id=250)
@@ -506,13 +544,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* jaxb-api (javax.xml.bind:jaxb-api:2.3.1 - https://github.com/javaee/jaxb-spec/jaxb-api)
* JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight)
* Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
* HK2 API module (org.glassfish.hk2:hk2-api:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
* ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
* HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
* HK2 API module (org.glassfish.hk2:hk2-api:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
* ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
* HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
* OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator)
* aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
Cordra (Version 2) License Agreement:
@@ -536,8 +574,8 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* JAXB Core (org.glassfish.jaxb:jaxb-core:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
* JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
* TXW2 Runtime (org.glassfish.jaxb:txw2:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* MIME streaming extension (org.jvnet.mimepull:mimepull:1.9.15 - https://github.com/eclipse-ee4j/metro-mimepull)
* org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core)
@@ -546,16 +584,15 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Eclipse Public License:
* System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/)
* H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com)
* H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com)
* Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca)
* Jakarta Expression Language API (jakarta.el:jakarta.el-api:5.0.1 - https://projects.eclipse.org/projects/ee4j.el)
* Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
* Jakarta Persistence API (jakarta.persistence:jakarta.persistence-api:3.1.0 - https://github.com/eclipse-ee4j/jpa-api)
* Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.0.0 - https://projects.eclipse.org/projects/ee4j.servlet)
* Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.1.0 - https://projects.eclipse.org/projects/ee4j.servlet)
* jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta)
* Jakarta RESTful WS API (jakarta.ws.rs:jakarta.ws.rs-api:3.1.0 - https://github.com/eclipse-ee4j/jaxrs-api)
* JUnit (junit:junit:4.13.2 - http://junit.org)
* AspectJ Weaver (org.aspectj:aspectjweaver:1.9.22 - https://www.eclipse.org/aspectj/)
* AspectJ Weaver (org.aspectj:aspectjweaver:1.9.24 - https://www.eclipse.org/aspectj/)
* Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
* Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/)
@@ -569,65 +606,72 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client)
* Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation)
* Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy)
* Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http)
* Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io)
* Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.57.v20241219 - https://jetty.org/jetty-deploy/)
* Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.57.v20241219 - https://jetty.org/jetty-http/)
* Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.57.v20241219 - https://jetty.org/jetty-io/)
* Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx)
* Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite)
* Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security)
* Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security)
* Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server)
* Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet)
* Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.57.v20241219 - https://jetty.org/jetty-security/)
* Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.57.v20241219 - https://jetty.org/jetty-server/)
* Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.57.v20241219 - https://jetty.org/jetty-servlet/)
* Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-servlets)
* Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util)
* Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax)
* Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp)
* Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.57.v20241219 - https://jetty.org/jetty-util/)
* Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.57.v20241219 - https://jetty.org/jetty-util-ajax/)
* Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.57.v20241219 - https://jetty.org/jetty-webapp/)
* Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-xml)
* Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-xml)
* Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.57.v20241219 - https://jetty.org/jetty-xml/)
* Jetty :: ALPN :: API (org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 - http://www.eclipse.org/jetty/alpn-api)
* Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client)
* Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common)
* Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.57.v20241219 - https://jetty.org/http2-parent/http2-common/)
* Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack)
* Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport)
* Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.15.v20190215 - https://eclipse.org/jetty/http2-parent/http2-server)
* Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-server)
* Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas)
* JSON-P Default Provider (org.glassfish:jakarta.json:2.0.1 - https://github.com/eclipse-ee4j/jsonp)
* HK2 API module (org.glassfish.hk2:hk2-api:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
* ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
* HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
* HK2 API module (org.glassfish.hk2:hk2-api:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
* ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
* HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
* OSGi resource locator (org.glassfish.hk2:osgi-resource-locator:1.0.3 - https://projects.eclipse.org/projects/ee4j/osgi-resource-locator)
* aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.6 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core)
* org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common)
* Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty)
* Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester)
* Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util)
GENERAL PUBLIC LICENSE, version 3 (GPL-3.0):
* juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet)
GNU Lesser General Public License (LGPL):
* juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet)
* btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf)
* jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils)
* jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils)
* json-patch (com.github.java-json-tools:json-patch:1.13 - https://github.com/java-json-tools/json-patch)
* json-schema-core (com.github.java-json-tools:json-schema-core:1.2.14 - https://github.com/java-json-tools/json-schema-core)
* json-schema-validator (com.github.java-json-tools:json-schema-validator:2.2.14 - https://github.com/java-json-tools/json-schema-validator)
* msg-simple (com.github.java-json-tools:msg-simple:1.2 - https://github.com/java-json-tools/msg-simple)
* uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template)
* openpdf (com.github.librepdf:openpdf:2.0.3 - https://github.com/LibrePDF/OpenPDF/openpdf)
* FindBugs-Annotations (com.google.code.findbugs:annotations:3.0.1u2 - http://findbugs.sourceforge.net/)
* JHighlight (org.codelibs:jhighlight:1.1.0 - https://github.com/codelibs/jhighlight)
* Cryptacular Library (org.cryptacular:cryptacular:1.2.5 - http://www.cryptacular.org)
* Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:6.0.6.Final - http://hibernate.org)
* Hibernate ORM - hibernate-core (org.hibernate.orm:hibernate-core:6.4.8.Final - https://hibernate.org/orm)
* Hibernate ORM - hibernate-jcache (org.hibernate.orm:hibernate-jcache:6.4.8.Final - https://hibernate.org/orm)
* Hibernate ORM - hibernate-jpamodelgen (org.hibernate.orm:hibernate-jpamodelgen:6.4.8.Final - https://hibernate.org/orm)
* im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/)
* Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/)
* Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/)
* Flying Saucer Core Renderer (org.xhtmlrenderer:flying-saucer-core:9.12.0 - http://code.google.com/p/flying-saucer/flying-saucer-core/)
* Flying Saucer PDF Rendering (org.xhtmlrenderer:flying-saucer-pdf:9.12.0 - http://code.google.com/p/flying-saucer/flying-saucer-pdf/)
* XOM (xom:xom:1.3.9 - https://xom.nu)
Go License:
@@ -648,25 +692,22 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* better-files (com.github.pathikrit:better-files_2.13:3.9.1 - https://github.com/pathikrit/better-files)
* Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver)
* dd-plist (com.googlecode.plist:dd-plist:1.28 - http://www.github.com/3breadt/dd-plist)
* DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.10 - https://github.com/dbmdz/iiif-apis)
* DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.11 - https://github.com/dbmdz/iiif-apis)
* s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock)
* ClassGraph (io.github.classgraph:classgraph:4.8.165 - https://github.com/classgraph/classgraph)
* JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple)
* Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk18on:1.77 - https://www.bouncycastle.org/java.html)
* Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.67 - http://www.bouncycastle.org/java.html)
* Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html)
* Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.67 - http://www.bouncycastle.org/java.html)
* Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html)
* Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html)
* Bouncy Castle JavaMail S/MIME APIs (org.bouncycastle:bcmail-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec)
* Checker Qual (org.checkerframework:checker-qual:3.31.0 - https://checkerframework.org)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* Checker Qual (org.checkerframework:checker-qual:3.49.3 - https://checkerframework.org/)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito)
* mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito)
* SLF4J API Module (org.slf4j:slf4j-api:2.0.11 - http://www.slf4j.org)
* SLF4J Extensions Module (org.slf4j:slf4j-ext:1.7.28 - http://www.slf4j.org)
* SLF4J API Module (org.slf4j:slf4j-api:2.0.17 - http://www.slf4j.org)
* HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org)
* toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org)
* backbone (org.webjars.bowergithub.jashkenas:backbone:1.4.1 - https://www.webjars.org)
@@ -674,28 +715,29 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* jquery (org.webjars.bowergithub.jquery:jquery-dist:3.7.1 - https://www.webjars.org)
* urijs (org.webjars.bowergithub.medialize:uri.js:1.19.11 - https://www.webjars.org)
* bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org)
* core-js (org.webjars.npm:core-js:3.37.1 - https://www.webjars.org)
* @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.6.1 - https://www.webjars.org)
* core-js (org.webjars.npm:core-js:3.42.0 - https://www.webjars.org)
* @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.15.2 - https://www.webjars.org)
Mozilla Public License:
* juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet)
* H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com)
* Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/)
* Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/)
* juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet)
* openpdf (com.github.librepdf:openpdf:2.0.3 - https://github.com/LibrePDF/OpenPDF/openpdf)
* H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com)
* Saxon-HE (net.sf.saxon:Saxon-HE:9.9.1-8 - http://www.saxonica.com/)
* Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/)
* Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino)
Public Domain:
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* AOP alliance (aopalliance:aopalliance:1.0 - http://aopalliance.sourceforge.net)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/)
* HdrHistogram (org.hdrhistogram:HdrHistogram:2.2.2 - http://hdrhistogram.github.io/HdrHistogram/)
* JSON in Java (org.json:json:20231013 - https://github.com/douglascrockford/JSON-java)
* LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/)
* Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections)
* XZ for Java (org.tukaani:xz:1.9 - https://tukaani.org/xz/java.html)
UnRar License:
@@ -703,16 +745,16 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Unicode/ICU License:
* ICU4J (com.ibm.icu:icu4j:62.1 - http://icu-project.org/)
* ICU4J (com.ibm.icu:icu4j:62.2 - http://icu-project.org/)
W3C license:
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
jQuery license:
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
* jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.10 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
* jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)

View File

@@ -33,18 +33,18 @@ Prior versions of DSpace (v6.x and below) used two different UIs (XMLUI and JSPU
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/).
The latest DSpace Installation instructions are available at:
https://wiki.lyrasis.org/display/DSDOC8x/Installing+DSpace
https://wiki.lyrasis.org/display/DSDOC9x/Installing+DSpace
Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL)
and a servlet container (usually Tomcat) in order to function.
More information about these and all other prerequisites can be found in the Installation instructions above.
## Running DSpace 8 in Docker
## Running DSpace 9 in Docker
NOTE: At this time, we do not have production-ready Docker images for DSpace.
That said, we do have quick-start Docker Compose scripts for development or testing purposes.
See [Running DSpace 8 with Docker Compose](dspace/src/main/docker-compose/README.md)
See [Running DSpace 9 with Docker Compose](dspace/src/main/docker-compose/README.md)
## Contributing

View File

@@ -7,4 +7,5 @@
<!-- TODO: We should have these turned on. But, currently there's a known bug with indentation checks
on JMockIt Expectations blocks and similar. See https://github.com/checkstyle/checkstyle/issues/3739 -->
<suppress checks="Indentation" files="src[/\\]test[/\\]java"/>
<suppress checks="Regexp" files="DSpaceHttpClientFactory\.java"/>
</suppressions>

View File

@@ -136,5 +136,22 @@ For more information on CheckStyle configurations below, see: http://checkstyle.
<module name="OneStatementPerLine"/>
<!-- Require that "catch" statements are not empty (must at least contain a comment) -->
<module name="EmptyCatchBlock"/>
<!-- Require to use DSpaceHttpClientFactory.getClient() statement instead of creating directly the client -->
<module name="Regexp">
<property name="format" value="HttpClientBuilder\.create\s*\(\s*\)" />
<property name="message" value="Use DSpaceHttpClientFactory.getClient() instead of HttpClientBuilder.create()" />
<property name="illegalPattern" value="true"/>
<property name="ignoreComments" value="true"/>
</module>
<!-- Require to use DSpaceHttpClientFactory.getClient() statement instead of creating directly the client -->
<module name="Regexp">
<property name="format" value="HttpClients\.createDefault\s*\(\s*\)" />
<property name="message" value="Use DSpaceHttpClientFactory.getClient() instead of HttpClients.createDefault()" />
<property name="illegalPattern" value="true"/>
<property name="ignoreComments" value="true"/>
</module>
</module>
</module>

View File

@@ -24,6 +24,8 @@ services:
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
solr__P__server: http://dspacesolr:8983/solr
# matomo.tracker.url: Ensure we are using the 'matomo' image for Matomo
matomo__P__tracker__P__url: http://matomo
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
proxies__P__trusted__P__ipranges: '172.23.0'
@@ -63,13 +65,12 @@ services:
# DSpace PostgreSQL database container
dspacedb:
container_name: dspacedb
# Uses a custom Postgres image with pgcrypto installed
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}"
build:
# Must build out of subdirectory to have access to install script for pgcrypto
context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
# Uses the base PostgreSQL image
image: "docker.io/postgres:${POSTGRES_VERSION:-15}"
environment:
PGDATA: /pgdata
POSTGRES_DB: dspace
POSTGRES_USER: dspace
POSTGRES_PASSWORD: dspace
networks:
dspacenet:
@@ -91,7 +92,7 @@ services:
additional_contexts:
solrconfigs: ./dspace/solr/
args:
SOLR_VERSION: "${SOLR_VER:-8.11}"
SOLR_VERSION: "${SOLR_VER:-9.8}"
networks:
dspacenet:
ports:
@@ -103,10 +104,15 @@ services:
volumes:
# Keep Solr data directory between reboots
- solr_data:/var/solr/data
# NOTE: We are not running Solr as "root", but we need root permissions to copy our cores to the mounted
# /var/solr/data directory. Then we start Solr as the "solr" user.
user: root
# Initialize all DSpace Solr cores then start Solr:
# * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op
# * Second, copy configsets to this core:
# Updates to Solr configs require the container to be rebuilt/restarted: `docker compose -p d7 up -d --build dspacesolr`
# * Third, ensure all new folders are owned by "solr" user
# * Finally, start Solr as the "solr" user via the provided solr-foreground script
entrypoint:
- /bin/bash
- '-c'
@@ -124,7 +130,8 @@ services:
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
exec solr -f
chown -R solr:solr /var/solr
runuser -u solr -- solr-foreground
volumes:
assetstore:
pgdata:

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId>
<version>9.0-SNAPSHOT</version>
<version>10.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
@@ -102,7 +102,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<version>3.6.1</version>
<executions>
<execution>
<phase>validate</phase>
@@ -177,7 +177,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>3.2.0</version>
<version>3.3.0</version>
<executions>
<execution>
<id>workflow-curation</id>
@@ -500,10 +500,6 @@
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
@@ -516,10 +512,6 @@
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
</dependency>
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
@@ -657,11 +649,18 @@
<version>1.1.1</version>
</dependency>
<!-- guava is needed by OAuth, Guice, Mockserver, ORCID, s3mock, Solr, JClouds -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- Gson is needed by JENA, borker-client, OAuth, Handle and JClouds -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
@@ -735,7 +734,7 @@
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.781</version>
<version>1.12.788</version>
</dependency>
<!-- TODO: This may need to be replaced with the "orcid-model" artifact once this ticket is resolved:
@@ -776,7 +775,7 @@
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.10</version>
<version>5.12.0</version>
</dependency>
<!-- Email templating -->
@@ -789,7 +788,7 @@
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.10.0</version>
<version>2.10.3</version>
<scope>test</scope>
</dependency>
@@ -864,5 +863,86 @@
</exclusion>
</exclusions>
</dependency>
<!-- JClouds Assetstorage Support -->
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-core</artifactId>
<version>${jclouds.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</exclusion>
<exclusion>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-blobstore</artifactId>
<version>${jclouds.version}</version>
<exclusions>
<exclusion>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.jclouds.api</groupId>
<artifactId>filesystem</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jclouds.provider</groupId>
<artifactId>aws-s3</artifactId>
<version>${jclouds.version}</version>
</dependency>
<!-- required frontpage generation -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.3.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.13.1</version>
<exclusions>
<!-- Conflicts with Hibernate. Use version that is brought in via Hibernate -->
<exclusion>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -8,8 +8,10 @@
package org.dspace.access.status;
import java.sql.SQLException;
import java.util.Date;
import java.time.LocalDate;
import org.dspace.content.AccessStatus;
import org.dspace.content.Bitstream;
import org.dspace.content.Item;
import org.dspace.core.Context;
@@ -21,22 +23,37 @@ public interface AccessStatusHelper {
* Calculate the access status for the item.
*
* @param context the DSpace context
* @param item the item
* @param item the item
* @param threshold the embargo threshold date
* @return an access status value
* @param type the type of calculation
* @return the access status
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getAccessStatusFromItem(Context context, Item item, Date threshold)
throws SQLException;
public AccessStatus getAccessStatusFromItem(Context context,
Item item, LocalDate threshold, String type) throws SQLException;
/**
* Retrieve embargo information for the item
* Calculate the anonymous access status for the item.
*
* @param context the DSpace context
* @param item the item to check for embargo information
* @param threshold the embargo threshold date
* @return an embargo date
* @return the access status
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getEmbargoFromItem(Context context, Item item, Date threshold) throws SQLException;
public AccessStatus getAnonymousAccessStatusFromItem(Context context,
Item item, LocalDate threshold) throws SQLException;
/**
* Calculate the access status for the bitstream.
*
* @param context the DSpace context
* @param bitstream the bitstream
* @param threshold the embargo threshold date
* @param type the type of calculation
* @return the access status
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public AccessStatus getAccessStatusFromBitstream(Context context,
Bitstream bitstream, LocalDate threshold, String type) throws SQLException;
}

View File

@@ -10,9 +10,13 @@ package org.dspace.access.status;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.access.status.service.AccessStatusService;
import org.dspace.content.AccessStatus;
import org.dspace.content.Bitstream;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.core.service.PluginService;
@@ -23,10 +27,15 @@ import org.springframework.beans.factory.annotation.Autowired;
* Implementation for the access status calculation service.
*/
public class AccessStatusServiceImpl implements AccessStatusService {
private static final Logger log = LogManager.getLogger(AccessStatusServiceImpl.class);
// Plugin implementation, set from the DSpace configuration by init().
protected AccessStatusHelper helper = null;
protected Date forever_date = null;
protected LocalDate forever_date = null;
protected String itemCalculationType = null;
protected String bitstreamCalculationType = null;
@Autowired(required = true)
protected ConfigurationService configurationService;
@@ -56,20 +65,39 @@ public class AccessStatusServiceImpl implements AccessStatusService {
int month = configurationService.getIntProperty("access.status.embargo.forever.month");
int day = configurationService.getIntProperty("access.status.embargo.forever.day");
forever_date = Date.from(LocalDate.of(year, month, day)
forever_date = LocalDate.of(year, month, day)
.atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant());
.toLocalDate();
itemCalculationType = getAccessStatusCalculationType("access.status.for-user.item");
bitstreamCalculationType = getAccessStatusCalculationType("access.status.for-user.bitstream");
}
}
@Override
public String getAccessStatus(Context context, Item item) throws SQLException {
return helper.getAccessStatusFromItem(context, item, forever_date);
public AccessStatus getAccessStatus(Context context, Item item) throws SQLException {
return helper.getAccessStatusFromItem(context, item, forever_date, itemCalculationType);
}
@Override
public String getEmbargoFromItem(Context context, Item item) throws SQLException {
return helper.getEmbargoFromItem(context, item, forever_date);
public AccessStatus getAnonymousAccessStatus(Context context, Item item) throws SQLException {
return helper.getAnonymousAccessStatusFromItem(context, item, forever_date);
}
@Override
public AccessStatus getAccessStatus(Context context, Bitstream bitstream) throws SQLException {
return helper.getAccessStatusFromBitstream(context, bitstream, forever_date, bitstreamCalculationType);
}
private String getAccessStatusCalculationType(String key) {
String value = configurationService.getProperty(key, DefaultAccessStatusHelper.STATUS_FOR_ANONYMOUS);
if (!StringUtils.equalsIgnoreCase(value, DefaultAccessStatusHelper.STATUS_FOR_ANONYMOUS) &&
!StringUtils.equalsIgnoreCase(value, DefaultAccessStatusHelper.STATUS_FOR_CURRENT_USER)) {
log.warn("The configuration parameter \"" + key
+ "\" contains an invalid value. Valid values include: 'anonymous' and 'current'.");
value = DefaultAccessStatusHelper.STATUS_FOR_ANONYMOUS;
}
return value;
}
}

View File

@@ -8,16 +8,18 @@
package org.dspace.access.status;
import java.sql.SQLException;
import java.time.Instant;
import java.util.Date;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.AccessStatus;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
@@ -26,21 +28,23 @@ import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.GroupService;
/**
* Default plugin implementation of the access status helper.
* The getAccessStatusFromItem method provides a simple logic to
* calculate the access status of an item based on the policies of
* the primary or the first bitstream in the original bundle.
* Users can override this method for enhanced functionality.
*
* The getEmbargoInformationFromItem method provides a simple logic to
* * retrieve embargo information of bitstreams from an item based on the policies of
* * the primary or the first bitstream in the original bundle.
* * Users can override this method for enhanced functionality.
*
* The methods provides a simple logic to calculate the access status
* of an item based on the policies of the primary or the first bitstream
* in the original bundle. Users can override those methods for
* enhanced functionality.
*/
public class DefaultAccessStatusHelper implements AccessStatusHelper {
public static final String STATUS_FOR_CURRENT_USER = "current";
public static final String STATUS_FOR_ANONYMOUS = "anonymous";
public static final String EMBARGO = "embargo";
public static final String METADATA_ONLY = "metadata.only";
public static final String OPEN_ACCESS = "open.access";
@@ -53,13 +57,15 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
AuthorizeServiceFactory.getInstance().getResourcePolicyService();
protected AuthorizeService authorizeService =
AuthorizeServiceFactory.getInstance().getAuthorizeService();
protected GroupService groupService =
EPersonServiceFactory.getInstance().getGroupService();
public DefaultAccessStatusHelper() {
super();
}
/**
* Look at the item's policies to determine an access status value.
* Look at the item's primary or first bitstream policies to determine an access status value.
* It is also considering a date threshold for embargoes and restrictions.
*
* If the item is null, simply returns the "unknown" value.
@@ -67,14 +73,70 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
* @param context the DSpace context
* @param item the item to check for embargoes
* @param threshold the embargo threshold date
* @return an access status value
* @param type the type of calculation
* @return the access status
*/
@Override
public String getAccessStatusFromItem(Context context, Item item, Date threshold)
public AccessStatus getAccessStatusFromItem(Context context, Item item, LocalDate threshold, String type)
throws SQLException {
if (item == null) {
return UNKNOWN;
return new AccessStatus(UNKNOWN, null);
}
Bitstream bitstream = getPrimaryOrFirstBitstreamInOriginalBundle(item);
if (bitstream == null) {
return new AccessStatus(METADATA_ONLY, null);
}
return getAccessStatusFromBitstream(context, bitstream, threshold, type);
}
/**
* Look at the bitstream policies to determine an access status value.
* It is also considering a date threshold for embargoes and restrictions.
*
* If the bitstream is null, simply returns the "unknown" value.
*
* @param context the DSpace context
* @param bitstream the bitstream to check for embargoes
* @param threshold the embargo threshold date
* @param type the type of calculation
* @return the access status
*/
@Override
public AccessStatus getAccessStatusFromBitstream(Context context,
Bitstream bitstream, LocalDate threshold, String type) throws SQLException {
if (bitstream == null) {
return new AccessStatus(UNKNOWN, null);
}
List<ResourcePolicy> policies = getReadPolicies(context, bitstream, type);
LocalDate availabilityDate = findAvailabilityDate(policies, threshold);
// Get the access status based on the availability date
String accessStatus = getAccessStatusFromAvailabilityDate(availabilityDate, threshold);
return new AccessStatus(accessStatus, availabilityDate);
}
/**
* Look at the anonymous policies of the primary (or first)
* bitstream of the item to retrieve its embargo.
*
* @param context the DSpace context
* @param item the item
* @param threshold the embargo threshold date
* @return the access status
*/
@Override
public AccessStatus getAnonymousAccessStatusFromItem(Context context, Item item, LocalDate threshold)
throws SQLException {
return getAccessStatusFromItem(context, item, threshold, STATUS_FOR_ANONYMOUS);
}
/**
* Look in the item's original bundle. First, try to get the primary bitstream.
* If the bitstream is null, simply returns the first one.
*
* @param item the DSpace item
* @return the bitstream
*/
private Bitstream getPrimaryOrFirstBitstreamInOriginalBundle(Item item) {
// Consider only the original bundles.
List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME);
// Check for primary bitstreams first.
@@ -92,157 +154,159 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
.findFirst()
.orElse(null);
}
return calculateAccessStatusForDso(context, bitstream, threshold);
return bitstream;
}
/**
* Look at the DSpace object's policies to determine an access status value.
* Retrieves the anonymous read policies for a DSpace object
*
* @param context the DSpace context
* @param dso the DSpace object
* @return a list of policies
*/
private List<ResourcePolicy> getAnonymousReadPolicies(Context context, DSpaceObject dso)
throws SQLException {
// Only consider read policies. Use the find without a group
// as it's not returning all expected values
List<ResourcePolicy> readPolicies = resourcePolicyService.find(context, dso, Constants.READ);
// Filter the policies with the anonymous group
List<ResourcePolicy> filteredPolicies = readPolicies.stream()
.filter(p -> p.getGroup() != null && StringUtils.equals(p.getGroup().getName(), Group.ANONYMOUS))
.collect(Collectors.toList());
return filteredPolicies;
}
/**
* Retrieves the current user read policies for a DSpace object
*
* @param context the DSpace context
* @param dso the DSpace object
* @return a list of policies
*/
private List<ResourcePolicy> getCurrentUserReadPolicies(Context context, DSpaceObject dso)
throws SQLException {
// First, look if the current user can read the object
boolean canRead = authorizeService.authorizeActionBoolean(context, dso, Constants.READ);
// If it's true, it can't be an embargo or a restriction, shortcircuit the process
// and return a null value (indicating an open access)
if (canRead) {
return null;
}
// Only consider read policies
List<ResourcePolicy> policies = resourcePolicyService.find(context, dso, Constants.READ);
// Only calculate the embargo date for the current user
EPerson currentUser = context.getCurrentUser();
List<ResourcePolicy> readPolicies = new ArrayList<ResourcePolicy>();
for (ResourcePolicy policy : policies) {
EPerson eperson = policy.getEPerson();
if (eperson != null && currentUser != null && eperson.getID() == currentUser.getID()) {
readPolicies.add(policy);
continue;
}
Group group = policy.getGroup();
if (group != null && groupService.isMember(context, currentUser, group)) {
readPolicies.add(policy);
}
}
return readPolicies;
}
/**
* Retrieves the read policies for a DSpace object based on the type
*
* If the type is current, consider the current logged in user
* If the type is anonymous, only consider the anonymous group
*
* @param context the DSpace context
* @param dso the DSpace object
* @param type the type of calculation
* @return a list of policies
*/
private List<ResourcePolicy> getReadPolicies(Context context, DSpaceObject dso, String type)
throws SQLException {
if (StringUtils.equalsIgnoreCase(type, STATUS_FOR_CURRENT_USER)) {
return getCurrentUserReadPolicies(context, dso);
} else {
// Only calculate the status for the anonymous group read policies
return getAnonymousReadPolicies(context, dso);
}
}
/**
* Look at the read policies to retrieve the access status availability date.
*
* @param readPolicies the read policies
* @param threshold the embargo threshold date
* @return an availability date
*/
private LocalDate findAvailabilityDate(List<ResourcePolicy> readPolicies, LocalDate threshold) {
// If the list is null, the object is readable
if (readPolicies == null) {
return null;
}
// If there's no policies, return the threshold date (restriction)
if (readPolicies.size() == 0) {
return threshold;
}
LocalDate availabilityDate = null;
LocalDate currentDate = LocalDate.now();
boolean takeMostRecentDate = true;
// Looks at all read policies
for (ResourcePolicy policy : readPolicies) {
boolean isValid = resourcePolicyService.isDateValid(policy);
// If any policy is valid, the object is accessible
if (isValid) {
return null;
}
// There may be an active embargo
LocalDate startDate = policy.getStartDate();
// Ignore policy with no start date or which is expired
if (startDate == null || startDate.isBefore(currentDate)) {
continue;
}
// Policy with a start date over the threshold (restriction)
// overrides the embargos
if (!startDate.isBefore(threshold)) {
takeMostRecentDate = false;
}
// Take the most recent embargo date if there is no restriction, otherwise
// take the highest date (account for rare cases where more than one resource
// policy exists)
if (availabilityDate == null) {
availabilityDate = startDate;
} else if (takeMostRecentDate) {
availabilityDate = startDate.isBefore(availabilityDate) ? startDate : availabilityDate;
} else {
availabilityDate = startDate.isAfter(availabilityDate) ? startDate : availabilityDate;
}
}
return availabilityDate;
}
/**
* Look at the DSpace object availability date to determine an access status value.
*
* If the object is null, returns the "metadata.only" value.
* If any policy attached to the object is valid for the anonymous group,
* returns the "open.access" value.
* Otherwise, if the policy start date is before the embargo threshold date,
* returns the "embargo" value.
* Every other cases return the "restricted" value.
* If there's no availability date, returns the "open.access" value.
* If the availability date is after or equal to the embargo
* threshold date, returns the "restricted" value.
* Every other cases return the "embargo" value.
*
* @param context the DSpace context
* @param dso the DSpace object
* @param threshold the embargo threshold date
* @param availabilityDate the DSpace object availability date
* @param threshold the embargo threshold date
* @return an access status value
*/
private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
throws SQLException {
if (dso == null) {
return METADATA_ONLY;
}
// Only consider read policies.
List<ResourcePolicy> policies = authorizeService
.getPoliciesActionFilter(context, dso, Constants.READ);
int openAccessCount = 0;
int embargoCount = 0;
int restrictedCount = 0;
int unknownCount = 0;
// Looks at all read policies.
for (ResourcePolicy policy : policies) {
boolean isValid = resourcePolicyService.isDateValid(policy);
Group group = policy.getGroup();
// The group must not be null here. However,
// if it is, consider this as an unexpected case.
if (group == null) {
unknownCount++;
} else if (StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
// Only calculate the status for the anonymous group.
if (isValid) {
// If the policy is valid, the anonymous group have access
// to the bitstream.
openAccessCount++;
} else {
Date startDate = policy.getStartDate();
if (startDate != null && !startDate.before(threshold)) {
// If the policy start date have a value and if this value
// is equal or superior to the configured forever date, the
// access status is also restricted.
restrictedCount++;
} else {
// If the current date is not between the policy start date
// and end date, the access status is embargo.
embargoCount++;
}
}
}
}
if (openAccessCount > 0) {
private String getAccessStatusFromAvailabilityDate(LocalDate availabilityDate, LocalDate threshold) {
// If there is no availability date, it's an open access.
if (availabilityDate == null) {
return OPEN_ACCESS;
}
if (embargoCount > 0 && restrictedCount == 0) {
return EMBARGO;
// If the policy start date have a value and if this value
// is equal or superior to the configured forever date, the
// access status is also restricted.
if (!availabilityDate.isBefore(threshold)) {
return RESTRICTED;
}
if (unknownCount > 0) {
return UNKNOWN;
}
return RESTRICTED;
}
/**
* Look at the policies of the primary (or first) bitstream of the item to retrieve its embargo.
*
* If the item is null, simply returns an empty map with no embargo information.
*
* @param context the DSpace context
* @param item the item to embargo
* @return an access status value
*/
@Override
public String getEmbargoFromItem(Context context, Item item, Date threshold)
throws SQLException {
Date embargoDate;
// If Item status is not "embargo" then return a null embargo date.
String accessStatus = getAccessStatusFromItem(context, item, threshold);
if (item == null || !accessStatus.equals(EMBARGO)) {
return null;
}
// Consider only the original bundles.
List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME);
// Check for primary bitstreams first.
Bitstream bitstream = bundles.stream()
.map(bundle -> bundle.getPrimaryBitstream())
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
if (bitstream == null) {
// If there is no primary bitstream,
// take the first bitstream in the bundles.
bitstream = bundles.stream()
.map(bundle -> bundle.getBitstreams())
.flatMap(List::stream)
.findFirst()
.orElse(null);
}
if (bitstream == null) {
return null;
}
embargoDate = this.retrieveShortestEmbargo(context, bitstream);
return embargoDate != null ? embargoDate.toString() : null;
}
/**
*
*/
private Date retrieveShortestEmbargo(Context context, Bitstream bitstream) throws SQLException {
Date embargoDate = null;
// Only consider read policies.
List<ResourcePolicy> policies = authorizeService
.getPoliciesActionFilter(context, bitstream, Constants.READ);
// Looks at all read policies.
for (ResourcePolicy policy : policies) {
boolean isValid = resourcePolicyService.isDateValid(policy);
Group group = policy.getGroup();
if (group != null && StringUtils.equals(group.getName(), Group.ANONYMOUS)) {
// Only calculate the status for the anonymous group.
if (!isValid) {
// If the policy is not valid there is an active embargo
Date startDate = policy.getStartDate();
if (startDate != null && !startDate.before(Date.from(Instant.now()))) {
// There is an active embargo: aim to take the shortest embargo (account for rare cases where
// more than one resource policy exists)
if (embargoDate == null) {
embargoDate = startDate;
} else {
embargoDate = startDate.before(embargoDate) ? startDate : embargoDate;
}
}
}
}
}
return embargoDate;
return EMBARGO;
}
}

View File

@@ -9,6 +9,8 @@ package org.dspace.access.status.service;
import java.sql.SQLException;
import org.dspace.content.AccessStatus;
import org.dspace.content.Bitstream;
import org.dspace.content.Item;
import org.dspace.core.Context;
@@ -39,19 +41,29 @@ public interface AccessStatusService {
* Calculate the access status for an Item while considering the forever embargo date threshold.
*
* @param context the DSpace context
* @param item the item
* @return an access status value
* @param item the item
* @return the access status
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getAccessStatus(Context context, Item item) throws SQLException;
public AccessStatus getAccessStatus(Context context, Item item) throws SQLException;
/**
* Retrieve embargo information for the item
* Calculate the anonymous access status for an Item while considering the forever embargo date threshold.
*
* @param context the DSpace context
* @param item the item to check for embargo information
* @return an embargo date
* @return the access status
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getEmbargoFromItem(Context context, Item item) throws SQLException;
public AccessStatus getAnonymousAccessStatus(Context context, Item item) throws SQLException;
/**
* Calculate the access status for a bitstream while considering the forever embargo date threshold.
*
* @param context the DSpace context
* @param bitstream the bitstream
* @return the access status
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public AccessStatus getAccessStatus(Context context, Bitstream bitstream) throws SQLException;
}

View File

@@ -9,12 +9,12 @@ package org.dspace.administer;
import java.io.IOException;
import java.sql.SQLException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang.time.DateUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.ProcessStatus;
import org.dspace.core.Context;
@@ -96,7 +96,7 @@ public class ProcessCleaner extends DSpaceRunnable<ProcessCleanerConfiguration<P
private void performDeletion(Context context) throws SQLException, IOException, AuthorizeException {
List<ProcessStatus> statuses = getProcessToDeleteStatuses();
Date creationDate = calculateCreationDate();
Instant creationDate = calculateCreationDate();
handler.logInfo("Searching for processes with status: " + statuses);
List<Process> processes = processService.findByStatusAndCreationTimeOlderThan(context, statuses, creationDate);
@@ -126,8 +126,8 @@ public class ProcessCleaner extends DSpaceRunnable<ProcessCleanerConfiguration<P
return statuses;
}
private Date calculateCreationDate() {
return DateUtils.addDays(new Date(), -days);
private Instant calculateCreationDate() {
return Instant.now().minus(days, ChronoUnit.DAYS);
}
@Override

View File

@@ -10,7 +10,6 @@ package org.dspace.administer;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath;
@@ -18,6 +17,7 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.dspace.app.util.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -49,8 +49,9 @@ public class RegistryImporter {
*/
public static Document loadXML(String filename)
throws IOException, ParserConfigurationException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
// This XML builder will *not* disable external entities as XML
// registries are considered trusted content
DocumentBuilder builder = XMLUtils.getTrustedDocumentBuilder();
Document document = builder.parse(new File(filename));

View File

@@ -13,7 +13,6 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath;
@@ -21,7 +20,15 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.XMLUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.BitstreamFormat;
import org.dspace.content.factory.ContentServiceFactory;
@@ -41,7 +48,7 @@ import org.xml.sax.SAXException;
* <P>
* <code>RegistryLoader -bitstream bitstream-formats.xml</code>
* <P>
* <code>RegistryLoader -dc dc-types.xml</code>
* <code>RegistryLoader -metadata dc-types.xml</code>
*
* @author Robert Tansley
* @version $Revision$
@@ -50,7 +57,7 @@ public class RegistryLoader {
/**
* log4j category
*/
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(RegistryLoader.class);
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(RegistryLoader.class);
protected static BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance()
.getBitstreamFormatService();
@@ -67,50 +74,99 @@ public class RegistryLoader {
* @throws Exception if error
*/
public static void main(String[] argv) throws Exception {
String usage = "Usage: " + RegistryLoader.class.getName()
+ " (-bitstream | -metadata) registry-file.xml";
Context context = null;
// Set up command-line options and parse arguments
CommandLineParser parser = new DefaultParser();
Options options = createCommandLineOptions();
try {
context = new Context();
CommandLine line = parser.parse(options, argv);
// Check if help option was entered or no options provided
if (line.hasOption('h') || line.getOptions().length == 0) {
printHelp(options);
System.exit(0);
}
Context context = new Context();
// Can't update registries anonymously, so we need to turn off
// authorisation
context.turnOffAuthorisationSystem();
// Work out what we're loading
if (argv[0].equalsIgnoreCase("-bitstream")) {
RegistryLoader.loadBitstreamFormats(context, argv[1]);
} else if (argv[0].equalsIgnoreCase("-metadata")) {
// Call MetadataImporter, as it handles Metadata schema updates
MetadataImporter.loadRegistry(argv[1], true);
} else {
System.err.println(usage);
try {
// Work out what we're loading
if (line.hasOption('b')) {
String filename = line.getOptionValue('b');
if (StringUtils.isEmpty(filename)) {
System.err.println("No file path provided for bitstream format registry");
printHelp(options);
System.exit(1);
}
RegistryLoader.loadBitstreamFormats(context, filename);
} else if (line.hasOption('m')) {
String filename = line.getOptionValue('m');
if (StringUtils.isEmpty(filename)) {
System.err.println("No file path provided for metadata registry");
printHelp(options);
System.exit(1);
}
// Call MetadataImporter, as it handles Metadata schema updates
MetadataImporter.loadRegistry(filename, true);
} else {
System.err.println("No registry type specified");
printHelp(options);
System.exit(1);
}
// Commit changes and close Context
context.complete();
System.exit(0);
} catch (Exception e) {
log.fatal(LogHelper.getHeader(context, "error_loading_registries", ""), e);
System.err.println("Error: \n - " + e.getMessage());
System.exit(1);
} finally {
// Clean up our context, if it still exists & it was never completed
if (context != null && context.isValid()) {
context.abort();
}
}
// Commit changes and close Context
context.complete();
System.exit(0);
} catch (ArrayIndexOutOfBoundsException ae) {
System.err.println(usage);
} catch (ParseException e) {
System.err.println("Error parsing command-line arguments: " + e.getMessage());
printHelp(options);
System.exit(1);
} catch (Exception e) {
log.fatal(LogHelper.getHeader(context, "error_loading_registries",
""), e);
System.err.println("Error: \n - " + e.getMessage());
System.exit(1);
} finally {
// Clean up our context, if it still exists & it was never completed
if (context != null && context.isValid()) {
context.abort();
}
}
}
/**
* Create the command-line options
* @return the command-line options
*/
private static Options createCommandLineOptions() {
Options options = new Options();
options.addOption("b", "bitstream", true, "load bitstream format registry from specified file");
options.addOption("m", "metadata", true, "load metadata registry from specified file");
options.addOption("h", "help", false, "print this help message");
return options;
}
/**
* Print the help message
* @param options the command-line options
*/
private static void printHelp(Options options) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("RegistryLoader",
"Load bitstream format or metadata registries into the database\n",
options,
"\nExamples:\n" +
" RegistryLoader -b bitstream-formats.xml\n" +
" RegistryLoader -m dc-types.xml",
true);
}
/**
* Load Bitstream Format metadata
*
@@ -210,8 +266,9 @@ public class RegistryLoader {
*/
private static Document loadXML(String filename) throws IOException,
ParserConfigurationException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
// This XML builder will *not* disable external entities as XML
// registries are considered trusted content
DocumentBuilder builder = XMLUtils.getTrustedDocumentBuilder();
return builder.parse(new File(filename));
}
@@ -221,7 +278,7 @@ public class RegistryLoader {
* contains:
* <P>
* <code>
* &lt;foo&gt;&lt;mimetype&gt;application/pdf&lt;/mimetype&gt;&lt;/foo&gt;
* <foo><mimetype>application/pdf</mimetype></foo>
* </code>
* passing this the <code>foo</code> node and <code>mimetype</code> will
* return <code>application/pdf</code>.
@@ -262,10 +319,10 @@ public class RegistryLoader {
* document contains:
* <P>
* <code>
* &lt;foo&gt;
* &lt;bar&gt;val1&lt;/bar&gt;
* &lt;bar&gt;val2&lt;/bar&gt;
* &lt;/foo&gt;
* <foo>
* <bar>val1</bar>
* <bar>val2</bar>
* </foo>
* </code>
* passing this the <code>foo</code> node and <code>bar</code> will
* return <code>val1</code> and <code>val2</code>.

View File

@@ -27,7 +27,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath;
@@ -43,6 +42,7 @@ import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.util.XMLUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
@@ -613,8 +613,8 @@ public class StructBuilder {
*/
private static org.w3c.dom.Document loadXML(InputStream input)
throws IOException, ParserConfigurationException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
// This builder factory does not disable external DTD, entities, etc.
DocumentBuilder builder = XMLUtils.getTrustedDocumentBuilder();
org.w3c.dom.Document document = builder.parse(input);

View File

@@ -7,7 +7,7 @@
*/
package org.dspace.alerts;
import java.util.Date;
import java.time.ZonedDateTime;
import jakarta.persistence.Cacheable;
import jakarta.persistence.Column;
@@ -17,8 +17,6 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.dspace.core.ReloadableEntity;
@@ -46,8 +44,7 @@ public class SystemWideAlert implements ReloadableEntity<Integer> {
private String allowSessions;
@Column(name = "countdown_to")
@Temporal(TemporalType.TIMESTAMP)
private Date countdownTo;
private ZonedDateTime countdownTo;
@Column(name = "active")
private boolean active;
@@ -115,7 +112,7 @@ public class SystemWideAlert implements ReloadableEntity<Integer> {
*
* @return the date to which will be count down when the system-wide alert is active
*/
public Date getCountdownTo() {
public ZonedDateTime getCountdownTo() {
return countdownTo;
}
@@ -124,7 +121,7 @@ public class SystemWideAlert implements ReloadableEntity<Integer> {
*
* @param countdownTo The date to which will be count down
*/
public void setCountdownTo(final Date countdownTo) {
public void setCountdownTo(final ZonedDateTime countdownTo) {
this.countdownTo = countdownTo;
}

View File

@@ -9,7 +9,7 @@ package org.dspace.alerts;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.time.ZonedDateTime;
import java.util.List;
import org.apache.logging.log4j.Logger;
@@ -39,7 +39,7 @@ public class SystemWideAlertServiceImpl implements SystemWideAlertService {
@Override
public SystemWideAlert create(final Context context, final String message,
final AllowSessionsEnum allowSessionsType,
final Date countdownTo, final boolean active) throws SQLException,
final ZonedDateTime countdownTo, final boolean active) throws SQLException,
AuthorizeException {
if (!authorizeService.isAdmin(context)) {
throw new AuthorizeException(

View File

@@ -9,7 +9,7 @@ package org.dspace.alerts.service;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.time.ZonedDateTime;
import java.util.List;
import org.dspace.alerts.AllowSessionsEnum;
@@ -35,7 +35,7 @@ public interface SystemWideAlertService {
* @throws SQLException If something goes wrong
*/
SystemWideAlert create(Context context, String message, AllowSessionsEnum allowSessionsType,
Date countdownTo, boolean active
ZonedDateTime countdownTo, boolean active
) throws SQLException, AuthorizeException;
/**

View File

@@ -16,10 +16,10 @@ import static org.dspace.core.Constants.CONTENT_BUNDLE_NAME;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -154,7 +154,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
}
ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(TimeZone.getTimeZone("UTC"));
mapper.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
BulkAccessControlInput accessControl;
context = new Context(Context.Mode.BATCH_EDIT);
setEPerson(context);
@@ -312,7 +312,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
* check the validation of access condition,
* the access condition name must equal to one of configured access conditions,
* then call {@link AccessConditionOption#validateResourcePolicy(
* Context, String, Date, Date)} if exception happens so, it's invalid.
* Context, String, LocalDate, LocalDate)} if exception happens so, it's invalid.
*
* @param accessCondition the accessCondition
* @throws BulkAccessControlException if the accessCondition is invalid
@@ -416,7 +416,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
discoverQuery.setQuery(query);
discoverQuery.setStart(start);
discoverQuery.setMaxResults(limit);
discoverQuery.setSortField("search.resourceid", DiscoverQuery.SORT_ORDER.asc);
return discoverQuery;
}
@@ -596,8 +596,8 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
String name = accessCondition.getName();
String description = accessCondition.getDescription();
Date startDate = accessCondition.getStartDate();
Date endDate = accessCondition.getEndDate();
LocalDate startDate = accessCondition.getStartDate();
LocalDate endDate = accessCondition.getEndDate();
try {
accessConditionOption.createResourcePolicy(context, obj, name, description, startDate, endDate);
@@ -651,7 +651,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
}
private void AppendAccessConditionsInfo(StringBuilder message, List<AccessCondition> accessConditions) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
DateTimeFormatter dateFormat = DateTimeFormatter.ISO_LOCAL_DATE;
message.append("{");
for (int i = 0; i < accessConditions.size(); i++) {

View File

@@ -7,7 +7,7 @@
*/
package org.dspace.app.bulkaccesscontrol.model;
import java.util.Date;
import java.time.LocalDate;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.dspace.app.bulkaccesscontrol.BulkAccessControl;
@@ -25,15 +25,15 @@ public class AccessCondition {
private String description;
@JsonDeserialize(using = MultiFormatDateDeserializer.class)
private Date startDate;
private LocalDate startDate;
@JsonDeserialize(using = MultiFormatDateDeserializer.class)
private Date endDate;
private LocalDate endDate;
public AccessCondition() {
}
public AccessCondition(String name, String description, Date startDate, Date endDate) {
public AccessCondition(String name, String description, LocalDate startDate, LocalDate endDate) {
this.name = name;
this.description = description;
this.startDate = startDate;
@@ -48,11 +48,11 @@ public class AccessCondition {
return description;
}
public Date getStartDate() {
public LocalDate getStartDate() {
return startDate;
}
public Date getEndDate() {
public LocalDate getEndDate() {
return endDate;
}

View File

@@ -19,6 +19,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -457,7 +458,7 @@ public class DSpaceCSV implements Serializable {
List<Collection> collections = i.getCollections();
for (Collection c : collections) {
// Only add if it is not the owning collection
if (!c.getHandle().equals(owningCollectionHandle)) {
if (!Objects.equals(c.getHandle(), owningCollectionHandle)) {
line.add("collection", c.getHandle());
}
}

View File

@@ -0,0 +1,182 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.bulkedit;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.content.MetadataDSpaceCsvExportServiceImpl;
import org.dspace.content.MetadataField;
import org.dspace.content.factory.ContentReportServiceFactory;
import org.dspace.content.service.MetadataDSpaceCsvExportService;
import org.dspace.contentreport.Filter;
import org.dspace.contentreport.FilteredItems;
import org.dspace.contentreport.FilteredItemsQuery;
import org.dspace.contentreport.QueryOperator;
import org.dspace.contentreport.QueryPredicate;
import org.dspace.contentreport.service.ContentReportService;
import org.dspace.core.Context;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;
import org.dspace.kernel.ServiceManager;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.services.ConfigurationService;
import org.dspace.utils.DSpace;
/**
* Metadata exporter to allow the batch export of metadata from a Filtered Items content report execution into a file
*
* @author Jean-François Morin (Université Laval)
*/
public class MetadataExportFilteredItemsReport extends DSpaceRunnable
<MetadataExportFilteredItemsReportScriptConfiguration<MetadataExportFilteredItemsReport>> {
private static final String EXPORT_CSV = "exportCSV";
public static final String DEFAULT_FILENAME = "metadataExportFilteredItems.csv";
private boolean help = false;
private String[] collectionUuids;
private String[] queryPredicates;
private String[] queryFilters;
private ConfigurationService configurationService;
private ContentReportService contentReportService;
private EPersonService ePersonService;
private MetadataDSpaceCsvExportService metadataDSpaceCsvExportService;
@SuppressWarnings("unchecked")
@Override
public MetadataExportFilteredItemsReportScriptConfiguration<MetadataExportFilteredItemsReport>
getScriptConfiguration() {
return new DSpace().getServiceManager()
.getServiceByName("metadata-export-filtered-items-report",
MetadataExportFilteredItemsReportScriptConfiguration.class);
}
@Override
public void setup() throws ParseException {
ServiceManager serviceManager = new DSpace().getServiceManager();
configurationService = serviceManager.getServicesByType(ConfigurationService.class).get(0);
contentReportService = ContentReportServiceFactory.getInstance().getContentReportService();
ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
metadataDSpaceCsvExportService = serviceManager.getServiceByName(
MetadataDSpaceCsvExportServiceImpl.class.getCanonicalName(),
MetadataDSpaceCsvExportService.class);
if (commandLine.hasOption('h')) {
help = true;
return;
}
if (commandLine.hasOption('c')) {
collectionUuids = commandLine.getOptionValues('c');
}
if (commandLine.hasOption("qp")) {
queryPredicates = commandLine.getOptionValues("qp");
}
if (commandLine.hasOption('f')) {
queryFilters = commandLine.getOptionValues('f');
}
}
@Override
public void internalRun() throws Exception {
if (help) {
loghelpinfo();
printHelp();
return;
}
handler.logDebug("starting content report export");
Context context = new Context();
context.setCurrentUser(ePersonService.find(context, getEpersonIdentifier()));
List<String> collUuids = List.of();
if (collectionUuids != null) {
// Using a temporary Set to eliminate duplicates, if any
Set<String> setUuids = arrayToStream(collectionUuids)
.map(uuids -> uuids.split("[^0-9A-Fa-f\\-]+"))
.flatMap(Arrays::stream)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
collUuids = new ArrayList<>(setUuids);
}
List<QueryPredicate> predicates = List.of();
if (queryPredicates != null) {
predicates = arrayToStream(queryPredicates)
.filter(StringUtils::isNotBlank)
.map(pred -> buildPredicate(context, pred))
.collect(Collectors.toList());
}
Set<Filter> filters = EnumSet.noneOf(Filter.class);
if (queryFilters != null) {
Arrays.stream(queryFilters)
.map(Filter::getFilters)
.flatMap(Set::stream)
.filter(f -> f != null)
.forEach(filters::add);
}
handler.logDebug("building query");
FilteredItemsQuery query = FilteredItemsQuery.of(
collUuids, predicates, 0, Integer.MAX_VALUE, filters, List.of());
handler.logDebug("creating iterator");
FilteredItems items = contentReportService.findFilteredItems(context, query);
handler.logDebug("creating dspacecsv");
DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService.export(context, items.getItems().iterator(),
true, handler);
handler.logDebug("writing to file " + getFileNameOrExportFile());
handler.writeFilestream(context, getFileNameOrExportFile(), dSpaceCSV.getInputStream(), EXPORT_CSV);
context.restoreAuthSystemState();
context.complete();
}
protected void loghelpinfo() {
handler.logInfo("metadata-export-filtered-items-report");
}
protected String getFileNameOrExportFile() {
return configurationService.getProperty("contentreport.metadataquery.csv.filename.default", DEFAULT_FILENAME);
}
private static Stream<String> arrayToStream(String... array) {
return Optional.ofNullable(array)
.stream()
.flatMap(Arrays::stream)
.filter(StringUtils::isNotBlank);
}
private QueryPredicate buildPredicate(Context context, String exp) {
String[] tokens = exp.split("\\:");
String field = tokens.length > 0 ? tokens[0].trim() : "";
QueryOperator operator = tokens.length > 1 ? QueryOperator.get(tokens[1].trim()) : null;
String value = tokens.length > 2 ? StringUtils.trimToEmpty(tokens[2]) : "";
try {
List<MetadataField> fields = contentReportService.getMetadataFields(context, field);
return QueryPredicate.of(fields, operator, value);
} catch (SQLException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,29 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.bulkedit;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
/**
* The CLI version of the {@link MetadataExportFilteredItemsReport} script
*
* @author Jean-François Morin (Université Laval)
*/
public class MetadataExportFilteredItemsReportCli extends MetadataExportFilteredItemsReport {
@Override
protected String getFileNameOrExportFile() {
return Optional.ofNullable(commandLine.getOptionValue('n'))
.filter(StringUtils::isNotBlank)
.orElseGet(() -> super.getFileNameOrExportFile());
}
}

View File

@@ -0,0 +1,36 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.bulkedit;
import org.apache.commons.cli.Options;
import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* This is the CLI version of the {@link MetadataExportFilteredItemsReportScriptConfiguration} class that handles the
* configuration for the {@link MetadataExportFilteredItemsReportCli} script
*
* @author Jean-François Morin (Université Laval)
*/
public class MetadataExportFilteredItemsReportCliScriptConfiguration
extends MetadataExportFilteredItemsReportScriptConfiguration<MetadataExportFilteredItemsReportCli> {
@Autowired
private ConfigurationService configurationService;
@Override
public Options getOptions() {
Options options = super.getOptions();
String filename = configurationService.getProperty("contentreport.metadataquery.csv.filename.default",
MetadataExportFilteredItemsReport.DEFAULT_FILENAME);
options.addOption("n", "filename", true, "the filename to export to (default: " + filename + ")");
return options;
}
}

View File

@@ -0,0 +1,56 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.bulkedit;
import org.apache.commons.cli.Options;
import org.dspace.scripts.configuration.ScriptConfiguration;
/**
* The {@link ScriptConfiguration} for the {@link MetadataExportFilteredItemsReport} script
*
* @author Jean-François Morin (Université Laval)
*/
public class MetadataExportFilteredItemsReportScriptConfiguration<T extends MetadataExportFilteredItemsReport>
extends ScriptConfiguration<T> {
private Class<T> dspaceRunnableclass;
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableclass;
}
@Override
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
dspaceRunnableclass = dspaceRunnableClass;
}
@Override
public Options getOptions() {
if (options == null) {
Options options = new Options();
options.addOption("c", "collections", true,
"UUIDs of collections to search for eligible records");
options.getOption("c").setType(String.class);
options.addOption("qp", "queryPredicates", true,
"Predicates or field queries used as criteria to filter records");
options.getOption("qp").setType(String.class);
options.addOption("f", "filters", true, """
Filters from the org.dspace.contentreport.Filter enumeration
used to filter records. Any filtered included here is considered as being selected,
and is considered unselected otherwise.""");
options.getOption("f").setType(String.class);
options.addOption("h", "help", false, "help");
super.options = options;
}
return options;
}
}

View File

@@ -143,7 +143,7 @@ public class MetadataExportSearch extends DSpaceRunnable<MetadataExportSearchScr
Iterator<Item> itemIterator = searchService.iteratorSearch(context, dso, discoverQuery);
handler.logDebug("creating dspacecsv");
DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService.export(context, itemIterator, true);
DSpaceCSV dSpaceCSV = metadataDSpaceCsvExportService.export(context, itemIterator, true, handler);
handler.logDebug("writing to file " + getFileNameOrExportFile());
handler.writeFilestream(context, getFileNameOrExportFile(), dSpaceCSV.getInputStream(), EXPORT_CSV);
context.restoreAuthSystemState();

View File

@@ -23,6 +23,7 @@ import java.util.UUID;
import jakarta.annotation.Nullable;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.RelationshipUtils;
@@ -89,7 +90,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
/**
* The authority controlled fields
*/
protected static Set<String> authorityControlled;
protected Set<String> authorityControlled;
/**
* The prefix of the authority controlled field
@@ -742,10 +743,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
if (value == null || !value.contains(csv.getAuthoritySeparator())) {
simplyCopyValue(value, dcv);
} else {
String[] parts = value.split(csv.getAuthoritySeparator());
dcv.setValue(parts[0]);
dcv.setAuthority(parts[1]);
dcv.setConfidence((parts.length > 2 ? Integer.valueOf(parts[2]) : Choices.CF_ACCEPTED));
resolveValueAndAuthority(value, dcv);
}
// fromAuthority==null: with the current implementation metadata values from external authority sources
@@ -1145,12 +1143,12 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
}
// look up the value and authority in solr
List<AuthorityValue> byValue = authorityValueService.findByValue(c, schema, element, qualifier, value);
List<AuthorityValue> byValue = authorityValueService.findByValue(schema, element, qualifier, value);
AuthorityValue authorityValue = null;
if (byValue.isEmpty()) {
String toGenerate = fromAuthority.generateString() + value;
String field = schema + "_" + element + (StringUtils.isNotBlank(qualifier) ? "_" + qualifier : "");
authorityValue = authorityValueService.generate(c, toGenerate, value, field);
authorityValue = authorityValueService.generate(toGenerate, value, field);
dcv.setAuthority(toGenerate);
} else {
authorityValue = byValue.get(0);
@@ -1162,10 +1160,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
} else if (value == null || !value.contains(csv.getAuthoritySeparator())) {
simplyCopyValue(value, dcv);
} else {
String[] parts = value.split(csv.getEscapedAuthoritySeparator());
dcv.setValue(parts[0]);
dcv.setAuthority(parts[1]);
dcv.setConfidence((parts.length > 2 ? Integer.valueOf(parts[2]) : Choices.CF_ACCEPTED));
resolveValueAndAuthority(value, dcv);
}
return dcv;
}
@@ -1176,6 +1171,35 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
dcv.setConfidence(Choices.CF_UNSET);
}
private void resolveValueAndAuthority(String value, BulkEditMetadataValue dcv) {
// Cells with valid authority are composed of three parts ~ <value>, <authority>, <confidence>
// The value itself may also include the authority separator though
String[] parts = value.split(csv.getEscapedAuthoritySeparator());
// If we don't have enough parts, assume the whole string is the value
if (parts.length < 3) {
simplyCopyValue(value, dcv);
return;
}
try {
// The last part of the cell must be a confidence value (integer)
int confidence = Integer.parseInt(parts[parts.length - 1]);
String authority = parts[parts.length - 2];
String plainValue = String.join(
csv.getAuthoritySeparator(),
ArrayUtils.subarray(parts, 0, parts.length - 2)
);
dcv.setValue(plainValue);
dcv.setAuthority(authority);
dcv.setConfidence(confidence);
} catch (NumberFormatException e) {
// Otherwise assume the whole string is the value
simplyCopyValue(value, dcv);
}
}
/**
* Method to find if a String occurs in an array of Strings
*
@@ -1368,10 +1392,10 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
/**
* is the field is defined as authority controlled
*/
private static boolean isAuthorityControlledField(String md) {
private boolean isAuthorityControlledField(String md) {
String mdf = md.contains(":") ? StringUtils.substringAfter(md, ":") : md;
mdf = StringUtils.substringBefore(mdf, "[");
return authorityControlled.contains(mdf);
return authorityControlled.contains(mdf) || authorityControlled.contains(md);
}
/**
@@ -1802,5 +1826,4 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
String targetType, String originType, String originTypeName) {
return RelationshipUtils.matchRelationshipType(relTypes, targetType, originType, originTypeName);
}
}

View File

@@ -9,9 +9,8 @@ package org.dspace.app.checker;
import java.io.FileNotFoundException;
import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@@ -149,7 +148,7 @@ public final class ChecksumChecker {
+ " old results from the database.");
}
Date processStart = Calendar.getInstance().getTime();
Instant processStart = Instant.now();
BitstreamDispatcher dispatcher = null;
@@ -180,10 +179,8 @@ public final class ChecksumChecker {
// run checker process for specified duration
try {
dispatcher = new LimitedDurationDispatcher(
new SimpleDispatcher(context, processStart, true), new Date(
System.currentTimeMillis()
+ Utils.parseDuration(line
.getOptionValue('d'))));
new SimpleDispatcher(context, processStart, true), Instant.ofEpochMilli(
Instant.now().toEpochMilli() + Utils.parseDuration(line.getOptionValue('d'))));
} catch (Exception e) {
LOG.fatal("Couldn't parse " + line.getOptionValue('d')
+ " as a duration: ", e);

View File

@@ -0,0 +1,152 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.client;
import static org.apache.commons.collections4.ListUtils.emptyIfNull;
import java.util.List;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.dspace.services.ConfigurationService;
import org.dspace.utils.DSpace;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Factory of {@link HttpClient} with common configurations.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class DSpaceHttpClientFactory {
@Autowired
private ConfigurationService configurationService;
@Autowired
private DSpaceProxyRoutePlanner proxyRoutePlanner;
@Autowired(required = false)
private List<HttpRequestInterceptor> requestInterceptors;
@Autowired(required = false)
private List<HttpResponseInterceptor> responseInterceptors;
/**
* Get an instance of {@link DSpaceHttpClientFactory} from the Spring context.
* @return the bean instance
*/
public static DSpaceHttpClientFactory getInstance() {
return new DSpace().getSingletonService(DSpaceHttpClientFactory.class);
}
/**
* Build an instance of {@link HttpClient} setting the proxy if configured.
*
* @return the client
*/
public CloseableHttpClient build() {
return build(HttpClientBuilder.create(), true);
}
/**
* return a Builder if an instance of {@link HttpClient} pre-setting the proxy if configured.
*
* @return the client
*/
public HttpClientBuilder builder(boolean setProxy) {
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
if (setProxy) {
clientBuilder.setRoutePlanner(proxyRoutePlanner);
}
getRequestInterceptors().forEach(clientBuilder::addInterceptorLast);
getResponseInterceptors().forEach(clientBuilder::addInterceptorLast);
return clientBuilder;
}
/**
* Build an instance of {@link HttpClient} without setting the proxy, even if
* configured.
*
* @return the client
*/
public CloseableHttpClient buildWithoutProxy() {
return build(HttpClientBuilder.create(), false);
}
/**
* Build an instance of {@link HttpClient} setting the proxy if configured,
* disabling automatic retries and setting the maximum total connection.
*
* @param maxConnTotal the maximum total connection value
* @return the client
*/
public CloseableHttpClient buildWithoutAutomaticRetries(int maxConnTotal) {
HttpClientBuilder clientBuilder = HttpClientBuilder.create()
.disableAutomaticRetries()
.setMaxConnTotal(maxConnTotal);
return build(clientBuilder, true);
}
/**
* Build an instance of {@link HttpClient} setting the proxy if configured with
* the given request configuration.
* @param requestConfig the request configuration
* @return the client
*/
public CloseableHttpClient buildWithRequestConfig(RequestConfig requestConfig) {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig);
return build(httpClientBuilder, true);
}
private CloseableHttpClient build(HttpClientBuilder clientBuilder, boolean setProxy) {
if (setProxy) {
clientBuilder.setRoutePlanner(proxyRoutePlanner);
}
getRequestInterceptors().forEach(clientBuilder::addInterceptorLast);
getResponseInterceptors().forEach(clientBuilder::addInterceptorLast);
return clientBuilder.build();
}
public ConfigurationService getConfigurationService() {
return configurationService;
}
public void setConfigurationService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
public List<HttpRequestInterceptor> getRequestInterceptors() {
return emptyIfNull(requestInterceptors);
}
public void setRequestInterceptors(List<HttpRequestInterceptor> requestInterceptors) {
this.requestInterceptors = requestInterceptors;
}
public List<HttpResponseInterceptor> getResponseInterceptors() {
return emptyIfNull(responseInterceptors);
}
public void setResponseInterceptors(List<HttpResponseInterceptor> responseInterceptors) {
this.responseInterceptors = responseInterceptors;
}
public DSpaceProxyRoutePlanner getProxyRoutePlanner() {
return proxyRoutePlanner;
}
public void setProxyRoutePlanner(DSpaceProxyRoutePlanner proxyRoutePlanner) {
this.proxyRoutePlanner = proxyRoutePlanner;
}
}

View File

@@ -0,0 +1,73 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.client;
import java.util.Arrays;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.impl.conn.DefaultRoutePlanner;
import org.apache.http.protocol.HttpContext;
import org.dspace.services.ConfigurationService;
/**
* Extension of {@link DefaultRoutePlanner} that determine the proxy based on
* the configuration service, ignoring configured hosts.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class DSpaceProxyRoutePlanner extends DefaultRoutePlanner {
private ConfigurationService configurationService;
public DSpaceProxyRoutePlanner(ConfigurationService configurationService) {
super(null);
this.configurationService = configurationService;
}
@Override
protected HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
if (isTargetHostConfiguredToBeIgnored(target)) {
return null;
}
String proxyHost = configurationService.getProperty("http.proxy.host");
String proxyPort = configurationService.getProperty("http.proxy.port");
if (StringUtils.isAnyBlank(proxyHost, proxyPort)) {
return null;
}
try {
return new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http");
} catch (NumberFormatException e) {
throw new RuntimeException("Invalid proxy port configuration: " + proxyPort);
}
}
private boolean isTargetHostConfiguredToBeIgnored(HttpHost target) {
String[] hostsToIgnore = configurationService.getArrayProperty("http.proxy.hosts-to-ignore");
if (ArrayUtils.isEmpty(hostsToIgnore)) {
return false;
}
return Arrays.stream(hostsToIgnore)
.anyMatch(host -> matchesHost(host, target.getHostName()));
}
private boolean matchesHost(String hostPattern, String hostName) {
if (hostName.equals(hostPattern)) {
return true;
} else if (hostPattern.startsWith("*")) {
return hostName.endsWith(StringUtils.removeStart(hostPattern, "*"));
} else if (hostPattern.endsWith("*")) {
return hostName.startsWith(StringUtils.removeEnd(hostPattern, "*"));
}
return false;
}
}

View File

@@ -18,10 +18,11 @@ import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -678,7 +679,7 @@ public class ItemExportServiceImpl implements ItemExportService {
context.turnOffAuthorisationSystem();
String fileName = assembleFileName("item", eperson,
new Date());
LocalDate.now());
String workParentDir = getExportWorkDirectory()
+ System.getProperty("file.separator")
+ fileName;
@@ -750,16 +751,16 @@ public class ItemExportServiceImpl implements ItemExportService {
@Override
public String assembleFileName(String type, EPerson eperson,
Date date) throws Exception {
LocalDate date) throws Exception {
// to format the date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MMM_dd");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MMM_dd");
String downloadDir = getExportDownloadDirectory(eperson);
// used to avoid name collision
int count = 1;
boolean exists = true;
String fileName = null;
while (exists) {
fileName = type + "_export_" + sdf.format(date) + "_" + count + "_"
fileName = type + "_export_" + formatter.format(date) + "_" + count + "_"
+ eperson.getID();
exists = new File(downloadDir
+ System.getProperty("file.separator") + fileName + ".zip")
@@ -915,14 +916,12 @@ public class ItemExportServiceImpl implements ItemExportService {
public void deleteOldExportArchives(EPerson eperson) throws Exception {
int hours = configurationService
.getIntProperty("org.dspace.app.itemexport.life.span.hours");
Calendar now = Calendar.getInstance();
now.setTime(new Date());
now.add(Calendar.HOUR, -hours);
Instant modifiedTime = Instant.now().minus(hours, ChronoUnit.HOURS);
File downloadDir = new File(getExportDownloadDirectory(eperson));
if (downloadDir.exists()) {
File[] files = downloadDir.listFiles();
for (File file : files) {
if (file.lastModified() < now.getTimeInMillis()) {
if (file.lastModified() < modifiedTime.toEpochMilli()) {
if (!file.delete()) {
logError("Unable to delete export file");
}
@@ -935,9 +934,7 @@ public class ItemExportServiceImpl implements ItemExportService {
@Override
public void deleteOldExportArchives() throws Exception {
int hours = configurationService.getIntProperty("org.dspace.app.itemexport.life.span.hours");
Calendar now = Calendar.getInstance();
now.setTime(new Date());
now.add(Calendar.HOUR, -hours);
Instant modifiedTime = Instant.now().minus(hours, ChronoUnit.HOURS);
File downloadDir = new File(configurationService.getProperty("org.dspace.app.itemexport.download.dir"));
if (downloadDir.exists()) {
// Get a list of all the sub-directories, potentially one for each ePerson.
@@ -946,7 +943,7 @@ public class ItemExportServiceImpl implements ItemExportService {
// For each sub-directory delete any old files.
File[] files = dir.listFiles();
for (File file : files) {
if (file.lastModified() < now.getTimeInMillis()) {
if (file.lastModified() < modifiedTime.toEpochMilli()) {
if (!file.delete()) {
logError("Unable to delete old files");
}

View File

@@ -8,7 +8,7 @@
package org.dspace.app.itemexport.service;
import java.io.InputStream;
import java.util.Date;
import java.time.LocalDate;
import java.util.Iterator;
import java.util.List;
@@ -130,7 +130,7 @@ public interface ItemExportService {
* @throws Exception if error
*/
public String assembleFileName(String type, EPerson eperson,
Date date) throws Exception;
LocalDate date) throws Exception;
/**

View File

@@ -11,11 +11,9 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
/**
@@ -23,7 +21,7 @@ import java.util.List;
*/
public class BatchUpload {
private Date date;
private Instant date;
private File dir;
private boolean successful;
private int itemsImported;
@@ -65,9 +63,7 @@ public class BatchUpload {
String dirName = dir.getName();
long timeMillis = Long.parseLong(dirName);
Calendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(timeMillis);
this.date = calendar.getTime();
this.date = Instant.ofEpochMilli(timeMillis);
try {
this.itemsImported = countLines(dir + File.separator + "mapfile");
@@ -149,7 +145,7 @@ public class BatchUpload {
*
* @return Date
*/
public Date getDate() {
public Instant getDate() {
return date;
}
@@ -190,14 +186,12 @@ public class BatchUpload {
}
/**
* Get formatted date (DD/MM/YY)
* Get formatted date (YYYY-MM-DDThh:mm:ssZ)
*
* @return date as string
*/
public String getDateFormatted() {
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy - HH:mm");
return df.format(date);
return DateTimeFormatter.ISO_INSTANT.format(date);
}
/**

View File

@@ -14,8 +14,9 @@ import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.sql.SQLException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -156,7 +157,7 @@ public class ItemImport extends DSpaceRunnable<ItemImportScriptConfiguration> {
return;
}
Date startTime = new Date();
Instant startTime = Instant.now();
Context context = new Context(Context.Mode.BATCH_EDIT);
setMapFile();
@@ -254,12 +255,12 @@ public class ItemImport extends DSpaceRunnable<ItemImportScriptConfiguration> {
}
}
Date endTime = new Date();
handler.logInfo("Started: " + startTime.getTime());
handler.logInfo("Ended: " + endTime.getTime());
Instant endTime = Instant.now();
handler.logInfo("Started: " + DateTimeFormatter.ISO_INSTANT.format(startTime));
handler.logInfo("Ended: " + DateTimeFormatter.ISO_INSTANT.format(endTime));
handler.logInfo(
"Elapsed time: " + ((endTime.getTime() - startTime.getTime()) / 1000) + " secs (" + (endTime
.getTime() - startTime.getTime()) + " msecs)");
"Elapsed time: " + ((endTime.toEpochMilli() - startTime.toEpochMilli()) / 1000) + " secs (" +
(endTime.toEpochMilli() - startTime.toEpochMilli()) + " msecs)");
}
}

View File

@@ -29,13 +29,15 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.nio.file.Path;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -47,7 +49,6 @@ import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath;
@@ -67,6 +68,7 @@ import org.apache.logging.log4j.Logger;
import org.dspace.app.itemimport.service.ItemImportService;
import org.dspace.app.util.LocalSchemaFilenameFilter;
import org.dspace.app.util.RelationshipUtils;
import org.dspace.app.util.XMLUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
@@ -179,6 +181,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
@Autowired(required = true)
protected MetadataValueService metadataValueService;
protected DocumentBuilder builder;
protected String tempWorkDir;
protected boolean isTest = false;
@@ -742,15 +746,22 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
myitem = wi.getItem();
}
// normalize and validate path to make sure itemname doesn't contain path traversal
Path itemPath = new File(path + File.separatorChar + itemname + File.separatorChar)
.toPath().normalize();
if (!itemPath.startsWith(path)) {
throw new IOException("Illegal item metadata path: '" + itemPath);
}
// Normalization chops off the last separator, and we need to put it back
String itemPathDir = itemPath.toString() + File.separatorChar;
// now fill out dublin core for item
loadMetadata(c, myitem, path + File.separatorChar + itemname
+ File.separatorChar);
loadMetadata(c, myitem, itemPathDir);
// and the bitstreams from the contents file
// process contents file, add bistreams and bundles, return any
// non-standard permissions
List<String> options = processContentsFile(c, myitem, path
+ File.separatorChar + itemname, "contents");
List<String> options = processContentsFile(c, myitem, itemPathDir, "contents");
if (useWorkflow) {
// don't process handle file
@@ -768,8 +779,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
}
} else {
// only process handle file if not using workflow system
String myhandle = processHandleFile(c, myitem, path
+ File.separatorChar + itemname, "handle");
String myhandle = processHandleFile(c, myitem, itemPathDir, "handle");
// put item in system
if (!isTest) {
@@ -1001,6 +1011,34 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
}
}
/**
* Ensures a file path does not attempt to access files outside the designated parent directory.
*
* @param parentDir The absolute path to the parent directory that should contain the file
* @param fileName The name or path of the file to validate
* @throws IOException If an error occurs while resolving canonical paths, or the file path attempts
* to access a location outside the parent directory
*/
private void validateFilePath(String parentDir, String fileName) throws IOException {
File parent = new File(parentDir);
File file = new File(fileName);
// If the fileName is not an absolute path, we resolve it against the parentDir
if (!file.isAbsolute()) {
file = new File(parent, fileName);
}
String parentCanonicalPath = parent.getCanonicalPath();
String fileCanonicalPath = file.getCanonicalPath();
if (!fileCanonicalPath.startsWith(parentCanonicalPath)) {
log.error("File path outside of canonical root requested: fileCanonicalPath={} does not begin " +
"with parentCanonicalPath={}", fileCanonicalPath, parentCanonicalPath);
throw new IOException("Illegal file path '" + fileName + "' encountered. This references a path " +
"outside of the import package. Please see the system logs for more details.");
}
}
/**
* Read the collections file inside the item directory. If there
* is one and it is not empty return a list of collections in
@@ -1201,6 +1239,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
sDescription = sDescription.replaceFirst("description:", "");
}
validateFilePath(path, sFilePath);
registerBitstream(c, i, iAssetstore, sFilePath, sBundle, sDescription);
logInfo("\tRegistering Bitstream: " + sFilePath
+ "\tAssetstore: " + iAssetstore
@@ -1414,6 +1453,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
return;
}
validateFilePath(path, fileName);
String fullpath = path + File.separatorChar + fileName;
// get an input stream
@@ -1425,11 +1465,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
if (bundleName == null) {
// is it license.txt?
if ("license.txt".equals(fileName)) {
newBundleName = "LICENSE";
if (Constants.LICENSE_BITSTREAM_NAME.equals(fileName)) {
newBundleName = Constants.LICENSE_BUNDLE_NAME;
} else {
// call it ORIGINAL
newBundleName = "ORIGINAL";
newBundleName = Constants.CONTENT_BUNDLE_NAME;
}
}
@@ -1493,11 +1533,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
if (StringUtils.isBlank(bundleName)) {
// is it license.txt?
if (bitstreamPath.endsWith("license.txt")) {
newBundleName = "LICENSE";
if (bitstreamPath.endsWith(Constants.LICENSE_BITSTREAM_NAME)) {
newBundleName = Constants.LICENSE_BUNDLE_NAME;
} else {
// call it ORIGINAL
newBundleName = "ORIGINAL";
newBundleName = Constants.CONTENT_BUNDLE_NAME;
}
}
@@ -1888,9 +1928,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
*/
protected Document loadXML(String filename) throws IOException,
ParserConfigurationException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
DocumentBuilder builder = XMLUtils.getDocumentBuilder();
return builder.parse(new File(filename));
}
@@ -2039,8 +2077,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
*/
protected String generateRandomFilename(boolean hidden) {
String filename = String.format("%s", RandomStringUtils.randomAlphanumeric(8));
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmm");
String datePart = sdf.format(new Date());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm");
String datePart = formatter.format(LocalDateTime.now(ZoneOffset.UTC));
filename = datePart + "_" + filename;
return filename;
@@ -2102,8 +2140,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
"org.dspace.app.batchitemimport.work.dir") + File.separator + "batchuploads" + File.separator
+ context
.getCurrentUser()
.getID() + File.separator + (isResume ? theResumeDir : (new GregorianCalendar())
.getTimeInMillis());
.getID() + File.separator + (isResume ? theResumeDir : Instant.now().toEpochMilli());
File importDirFile = new File(importDir);
if (!importDirFile.exists()) {
boolean success = importDirFile.mkdirs();

View File

@@ -27,6 +27,7 @@ import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.InstallItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
@@ -138,10 +139,10 @@ public class AddBitstreamsAction extends UpdateBitstreamsAction {
String newBundleName = ce.bundlename;
if (ce.bundlename == null) { // should be required but default convention established
if (ce.filename.equals("license.txt")) {
newBundleName = "LICENSE";
if (ce.filename.equals(Constants.LICENSE_BITSTREAM_NAME)) {
newBundleName = Constants.LICENSE_BUNDLE_NAME;
} else {
newBundleName = "ORIGINAL";
newBundleName = Constants.CONTENT_BUNDLE_NAME;
}
}
ItemUpdate.pr(" Bitstream " + ce.filename + " to be added to bundle: " + newBundleName);

View File

@@ -23,8 +23,6 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
@@ -33,6 +31,7 @@ import javax.xml.transform.TransformerFactory;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.LocalSchemaFilenameFilter;
import org.dspace.app.util.XMLUtils;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
@@ -52,7 +51,6 @@ public class ItemArchive {
public static final String DUBLIN_CORE_XML = "dublin_core.xml";
protected static DocumentBuilder builder = null;
protected Transformer transformer = null;
protected List<DtoMetadata> dtomList = null;
@@ -95,14 +93,14 @@ public class ItemArchive {
InputStream is = null;
try {
is = new FileInputStream(new File(dir, DUBLIN_CORE_XML));
itarch.dtomList = MetadataUtilities.loadDublinCore(getDocumentBuilder(), is);
itarch.dtomList = MetadataUtilities.loadDublinCore(XMLUtils.getDocumentBuilder(), is);
//The code to search for local schema files was copied from org.dspace.app.itemimport
// .ItemImportServiceImpl.java
File file[] = dir.listFiles(new LocalSchemaFilenameFilter());
for (int i = 0; i < file.length; i++) {
is = new FileInputStream(file[i]);
itarch.dtomList.addAll(MetadataUtilities.loadDublinCore(getDocumentBuilder(), is));
itarch.dtomList.addAll(MetadataUtilities.loadDublinCore(XMLUtils.getDocumentBuilder(), is));
}
} finally {
if (is != null) {
@@ -126,14 +124,6 @@ public class ItemArchive {
return itarch;
}
protected static DocumentBuilder getDocumentBuilder()
throws ParserConfigurationException {
if (builder == null) {
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
return builder;
}
/**
* Getter for Transformer
*
@@ -318,7 +308,7 @@ public class ItemArchive {
try {
out = new FileOutputStream(new File(dir, "dublin_core.xml"));
Document doc = MetadataUtilities.writeDublinCore(getDocumentBuilder(), undoDtomList);
Document doc = MetadataUtilities.writeDublinCore(XMLUtils.getDocumentBuilder(), undoDtomList);
MetadataUtilities.writeDocument(doc, getTransformer(), out);
// if undo has delete bitstream

View File

@@ -14,9 +14,9 @@ import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -332,7 +332,7 @@ public class ItemUpdate {
}
}
pr("ItemUpdate - initializing run on " + (new Date()).toString());
pr("ItemUpdate - initializing run on " + (Instant.now()).toString());
context = new Context(Context.Mode.BATCH_EDIT);
iu.setEPerson(context, iu.eperson);

View File

@@ -19,6 +19,7 @@ import java.util.TreeMap;
import org.apache.commons.cli.ParseException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.util.XMLUtils;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.DSpaceRunnable.StepResult;
@@ -314,7 +315,7 @@ public class ScriptLauncher {
String config = kernelImpl.getConfigurationService().getProperty("dspace.dir") +
System.getProperty("file.separator") + "config" +
System.getProperty("file.separator") + "launcher.xml";
SAXBuilder saxBuilder = new SAXBuilder();
SAXBuilder saxBuilder = XMLUtils.getSAXBuilder();
Document doc = null;
try {
doc = saxBuilder.build(config);

View File

@@ -11,9 +11,9 @@ import static java.lang.String.format;
import java.io.IOException;
import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -21,7 +21,6 @@ import java.util.Optional;
import java.util.UUID;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@@ -48,6 +47,10 @@ import org.dspace.event.Event;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace;
import org.dspace.versioning.Version;
import org.dspace.versioning.VersionHistory;
import org.dspace.versioning.factory.VersionServiceFactory;
import org.dspace.versioning.service.VersionHistoryService;
import org.dspace.web.ContextUtil;
/**
@@ -63,6 +66,9 @@ public class LDNMessageConsumer implements Consumer {
private ConfigurationService configurationService;
private ItemService itemService;
private BitstreamService bitstreamService;
private final String RESUBMISSION_SUFFIX = "-resubmission";
private final String ENDORSEMENT_PATTERN = "request-endorsement";
private final String REVIEW_PATTERN = "request-review";
@Override
public void initialize() throws Exception {
@@ -83,6 +89,9 @@ public class LDNMessageConsumer implements Consumer {
}
Item item = (Item) event.getSubject(context);
if (item == null) {
return;
}
createManualLDNMessages(context, item);
createAutomaticLDNMessages(context, item);
}
@@ -90,10 +99,24 @@ public class LDNMessageConsumer implements Consumer {
private void createManualLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException {
List<NotifyPatternToTrigger> patternsToTrigger =
notifyPatternToTriggerService.findByItem(context, item);
// Note that multiple patterns can be submitted and not all support resubmission
// 1. Extract all patterns that accept resubmissions, i.e. endorsement and review
List<Integer> patternsSupportingResubmission = patternsToTrigger.stream()
.filter(p -> p.getPattern().equals(REVIEW_PATTERN) || p.getPattern().equals(ENDORSEMENT_PATTERN))
.map(NotifyPatternToTrigger::getID).toList();
String resubmissionReplyToID = null;
for (NotifyPatternToTrigger patternToTrigger : patternsToTrigger) {
// Only try to fetch resubmission ID if the pattern support resubmission
if (patternsSupportingResubmission.contains(patternToTrigger.getID())) {
resubmissionReplyToID = findResubmissionReplyToUUID(context, item, patternToTrigger.getNotifyService());
}
createLDNMessage(context,patternToTrigger.getItem(),
patternToTrigger.getNotifyService(), patternToTrigger.getPattern());
patternToTrigger.getNotifyService(),
patternToTrigger.getPattern(),
resubmissionReplyToID);
}
}
@@ -104,11 +127,33 @@ public class LDNMessageConsumer implements Consumer {
for (NotifyServiceInboundPattern inboundPattern : inboundPatterns) {
if (StringUtils.isEmpty(inboundPattern.getConstraint()) ||
evaluateFilter(context, item, inboundPattern.getConstraint())) {
createLDNMessage(context, item, inboundPattern.getNotifyService(), inboundPattern.getPattern());
createLDNMessage(context, item, inboundPattern.getNotifyService(),
inboundPattern.getPattern(), null);
}
}
}
private String findResubmissionReplyToUUID(Context context, Item item, NotifyServiceEntity service)
throws SQLException {
// 1.1 Check whether this is a new version submission
VersionHistoryService versionHistoryService = VersionServiceFactory.getInstance()
.getVersionHistoryService();
VersionHistory versionHistory = versionHistoryService.findByItem(context, item);
if (versionHistory != null) {
Version currentVersion = versionHistoryService.getVersion(context, versionHistory, item);
Version previousVersion = versionHistoryService.getPrevious(context, versionHistory, currentVersion);
if (previousVersion != null) {
// 1.2 and a TentativeReject notification, matching the current pattern's service, was received for the
// previous item version
return ldnMessageService.findEndorsementOrReviewResubmissionIdByItem(context,
previousVersion.getItem(), service);
}
}
// New submission (new item, or previous version with a tentativeReject notification not found)
return null;
}
private boolean evaluateFilter(Context context, Item item, String constraint) {
LogicalStatement filter =
new DSpace().getServiceManager().getServiceByName(constraint, LogicalStatement.class);
@@ -116,19 +161,37 @@ public class LDNMessageConsumer implements Consumer {
return filter != null && filter.getResult(context, item);
}
private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern)
throws SQLException, JsonMappingException, JsonProcessingException {
LDN ldn = getLDNMessage(pattern);
private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern,
String resubmissionID)
throws SQLException, JsonProcessingException {
// Amend current pattern name to trigger
// Endorsement or Review offer resubmissions: append '-resubmission' to pattern name to choose the correct
// LDN message template: e.g. request-endorsement-resubmission or request-review-resubmission
LDN ldn = (resubmissionID != null)
? getLDNMessage(pattern + RESUBMISSION_SUFFIX) : getLDNMessage(pattern);
LDNMessageEntity ldnMessage =
ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID()));
ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID()));
ldnMessage.setObject(item);
ldnMessage.setTarget(service);
ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
ldnMessage.setQueueTimeout(new Date());
ldnMessage.setQueueTimeout(Instant.now());
appendGeneratedMessage(ldn, ldnMessage, pattern);
String actorID = null;
if (service.isUsesActorEmailId()) {
// If the service has been configured to use actorEmailId, we use the submitter's email and name
if (item.getSubmitter() != null) {
actorID = item.getSubmitter().getEmail();
} else {
// Use configured fallback email (defaults to mail.admin property)
actorID = configurationService.getProperty("ldn.notification.email.submitter.fallback");
}
}
appendGeneratedMessage(ldn,
ldnMessage,
actorID,
(actorID != null && item.getSubmitter() != null) ? item.getSubmitter().getFullName() : null,
resubmissionID);
ObjectMapper mapper = new ObjectMapper();
Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class);
@@ -139,6 +202,10 @@ public class LDNMessageConsumer implements Consumer {
Collections.sort(notificationTypeArrayList);
ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1));
// If a resubmission, set inReplyTo
if (resubmissionID != null) {
ldnMessage.setInReplyTo(ldnMessageService.find(context, resubmissionID));
}
ldnMessageService.update(context, ldnMessage);
}
@@ -151,11 +218,16 @@ public class LDNMessageConsumer implements Consumer {
}
}
private void appendGeneratedMessage(LDN ldn, LDNMessageEntity ldnMessage, String pattern) {
private void appendGeneratedMessage(LDN ldn, LDNMessageEntity ldnMessage, String actorID, String actorName,
String resubmissionId) {
Item item = (Item) ldnMessage.getObject();
ldn.addArgument(getUiUrl());
if (actorID != null) {
ldn.addArgument("mailto:" + actorID);
} else {
ldn.addArgument(getUiUrl());
}
ldn.addArgument(configurationService.getProperty("ldn.notify.inbox"));
ldn.addArgument(configurationService.getProperty("dspace.name"));
ldn.addArgument(actorName != null ? actorName : configurationService.getProperty("dspace.name"));
ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getUrl(), ""));
ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getLdnUrl(), ""));
ldn.addArgument(getUiUrl() + "/handle/" + ldnMessage.getObject().getHandle());
@@ -166,6 +238,17 @@ public class LDNMessageConsumer implements Consumer {
ldn.addArgument(getRelationUri(item));
ldn.addArgument("http://purl.org/vocab/frbr/core#supplement");
ldn.addArgument(format("urn:uuid:%s", UUID.randomUUID()));
if (actorID != null) {
ldn.addArgument("Person");
} else {
ldn.addArgument("Service");
}
// Param 14: UI URL, LDN message origin
ldn.addArgument(getUiUrl());
// Param 15: inReplyTo ID, used in endorsement resubmission notifications
if (resubmissionId != null) {
ldn.addArgument(String.format("\"inReplyTo\": \"%s\",", resubmissionId));
}
ldnMessage.setMessage(ldn.generateLDNMessage());
}

View File

@@ -8,7 +8,7 @@
package org.dspace.app.ldn;
import java.lang.reflect.Field;
import java.util.Date;
import java.time.Instant;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -16,10 +16,10 @@ import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import org.dspace.content.DSpaceObject;
import org.dspace.core.ReloadableEntity;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* Class representing ldnMessages stored in the DSpace system and, when locally resolvable,
@@ -100,13 +100,11 @@ public class LDNMessageEntity implements ReloadableEntity<String> {
@Column(name = "queue_attempts")
private Integer queueAttempts = 0;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "queue_last_start_time")
private Date queueLastStartTime = null;
private Instant queueLastStartTime = null;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "queue_timeout")
private Date queueTimeout = null;
private Instant queueTimeout = null;
@ManyToOne
@JoinColumn(name = "origin", referencedColumnName = "id")
@@ -259,19 +257,19 @@ public class LDNMessageEntity implements ReloadableEntity<String> {
this.queueAttempts = queueAttempts;
}
public Date getQueueLastStartTime() {
public Instant getQueueLastStartTime() {
return queueLastStartTime;
}
public void setQueueLastStartTime(Date queueLastStartTime) {
public void setQueueLastStartTime(Instant queueLastStartTime) {
this.queueLastStartTime = queueLastStartTime;
}
public Date getQueueTimeout() {
public Instant getQueueTimeout() {
return queueTimeout;
}
public void setQueueTimeout(Date queueTimeout) {
public void setQueueTimeout(Instant queueTimeout) {
this.queueTimeout = queueTimeout;
}
@@ -289,7 +287,11 @@ public class LDNMessageEntity implements ReloadableEntity<String> {
}
public static String getNotificationType(LDNMessageEntity ldnMessage) {
if (ldnMessage.getInReplyTo() != null || ldnMessage.getOrigin() != null) {
// Resubmission outgoing notifications have the inReplyTo, therefore it cannot be used to determine
// whether a notification is incoming
ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
if (ldnMessage.getOrigin() != null && !ldnMessage.getOrigin().getLdnUrl()
.contains(configurationService.getProperty("dspace.ui.url"))) {
return TYPE_INCOMING;
}
return TYPE_OUTGOING;

View File

@@ -54,6 +54,9 @@ public class NotifyServiceEntity implements ReloadableEntity<Integer> {
@Column(name = "enabled")
private boolean enabled = false;
@Column(name = "uses_actor_email_id")
private boolean usesActorEmailId = false;
@Column(name = "score")
private BigDecimal score;
@@ -129,6 +132,14 @@ public class NotifyServiceEntity implements ReloadableEntity<Integer> {
this.enabled = enabled;
}
public boolean isUsesActorEmailId() {
return usesActorEmailId;
}
public void setUsesActorEmailId(boolean usesActorEmailId) {
this.usesActorEmailId = usesActorEmailId;
}
public BigDecimal getScore() {
return score;
}

View File

@@ -9,7 +9,7 @@ package org.dspace.app.ldn.action;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Date;
import java.time.Instant;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.LogManager;
@@ -73,8 +73,7 @@ public class LDNCorrectionAction implements LDNAction {
qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE,
handleService.findHandle(context, item), item.getID().toString(), itemName,
this.getQaEventTopic(), doubleScoreValue,
mapper.writeValueAsString(message),
new Date());
mapper.writeValueAsString(message), Instant.now());
qaEventService.store(context, qaEvent);
result = LDNActionStatus.CONTINUE;
}

View File

@@ -9,9 +9,8 @@ package org.dspace.app.ldn.action;
import static java.lang.String.format;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -34,8 +33,6 @@ public class LDNEmailAction implements LDNAction {
private static final Logger log = LogManager.getLogger(LDNEmailAction.class);
private final static String DATE_PATTERN = "dd-MM-yyyy HH:mm:ss";
@Autowired
private ConfigurationService configurationService;
@@ -80,7 +77,7 @@ public class LDNEmailAction implements LDNAction {
email.addRecipient(recipient);
}
String date = new SimpleDateFormat(DATE_PATTERN).format(Calendar.getInstance().getTime());
String date = Instant.now().toString();
email.addArgument(notification.getActor().getName());
email.addArgument(item.getName());
@@ -139,7 +136,13 @@ public class LDNEmailAction implements LDNAction {
List<String> recipients = new LinkedList<String>();
if (actionSendFilter.startsWith("SUBMITTER")) {
recipients.add(item.getSubmitter().getEmail());
if (item.getSubmitter() != null) {
recipients.add(item.getSubmitter().getEmail());
} else {
// Fallback configured option
recipients.add(configurationService.getProperty("ldn.notification.email.submitter.fallback"));
}
} else if (actionSendFilter.startsWith("GROUP:")) {
String groupName = actionSendFilter.replace("GROUP:", "");
String property = format("email.%s.list", groupName);

View File

@@ -9,7 +9,7 @@ package org.dspace.app.ldn.action;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Date;
import java.time.Instant;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -75,8 +75,7 @@ public class LDNRelationCorrectionAction implements LDNAction {
qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE,
handleService.findHandle(context, item), item.getID().toString(), itemName,
this.getQaEventTopic(), doubleScoreValue,
mapper.writeValueAsString(message),
new Date());
mapper.writeValueAsString(message), Instant.now());
qaEventService.store(context, qaEvent);
result = LDNActionStatus.CONTINUE;
}

View File

@@ -18,9 +18,9 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.client.DSpaceHttpClientFactory;
import org.dspace.app.ldn.model.Notification;
import org.dspace.content.Item;
import org.dspace.core.Context;
@@ -34,21 +34,13 @@ public class SendLDNMessageAction implements LDNAction {
private static final Logger log = LogManager.getLogger(SendLDNMessageAction.class);
private CloseableHttpClient client = null;
private CloseableHttpClient client;
public SendLDNMessageAction() {
HttpClientBuilder builder = HttpClientBuilder.create();
client = builder
.disableAutomaticRetries()
.setMaxConnTotal(5)
.build();
}
public SendLDNMessageAction(CloseableHttpClient client) {
this();
if (client != null) {
this.client = client;
}
this.client = client;
}
@Override
@@ -66,9 +58,10 @@ public class SendLDNMessageAction implements LDNAction {
// NOTE: Github believes there is a "Potential server-side request forgery due to a user-provided value"
// This is a false positive because the LDN Service URL is configured by the user from DSpace.
// See the frontend configuration at [dspace.ui.url]/admin/ldn/services
try (
CloseableHttpResponse response = client.execute(httpPost);
) {
if (client == null) {
client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5);
}
try (CloseableHttpResponse response = client.execute(httpPost)) {
if (isSuccessful(response.getStatusLine().getStatusCode())) {
result = LDNActionStatus.CONTINUE;
} else if (isRedirect(response.getStatusLine().getStatusCode())) {
@@ -77,6 +70,7 @@ public class SendLDNMessageAction implements LDNAction {
} catch (Exception e) {
log.error(e);
}
client.close();
return result;
}
@@ -91,9 +85,9 @@ public class SendLDNMessageAction implements LDNAction {
statusCode == HttpStatus.SC_TEMPORARY_REDIRECT;
}
private LDNActionStatus handleRedirect(CloseableHttpResponse oldresponse,
private LDNActionStatus handleRedirect(CloseableHttpResponse oldResponse,
HttpPost request) throws HttpException {
Header[] urls = oldresponse.getHeaders(HttpHeaders.LOCATION);
Header[] urls = oldResponse.getHeaders(HttpHeaders.LOCATION);
String url = urls.length > 0 && urls[0] != null ? urls[0].getValue() : null;
if (url == null) {
throw new HttpException("Error following redirect, unable to reach"
@@ -102,17 +96,14 @@ public class SendLDNMessageAction implements LDNAction {
LDNActionStatus result = LDNActionStatus.ABORT;
try {
request.setURI(new URI(url));
try (
CloseableHttpResponse response = client.execute(request);
) {
try (CloseableHttpResponse response = client.execute(request)) {
if (isSuccessful(response.getStatusLine().getStatusCode())) {
return LDNActionStatus.CONTINUE;
result = LDNActionStatus.CONTINUE;
}
}
} catch (Exception e) {
log.error("Error following redirect:", e);
}
return LDNActionStatus.ABORT;
return result;
}
}

View File

@@ -8,8 +8,8 @@
package org.dspace.app.ldn.dao.impl;
import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -47,7 +47,7 @@ public class LDNMessageDaoImpl extends AbstractHibernateDAO<LDNMessageEntity> im
andPredicates
.add(criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_QUEUED));
andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueAttempts), max_attempts));
andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), new Date()));
andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), Instant.now()));
criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
List<Order> orderList = new LinkedList<>();
orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
@@ -94,7 +94,7 @@ public class LDNMessageDaoImpl extends AbstractHibernateDAO<LDNMessageEntity> im
andPredicates.add(
criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSING));
andPredicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(LDNMessageEntity_.queueAttempts), max_attempts));
andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), new Date()));
andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), Instant.now()));
criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
List<Order> orderList = new LinkedList<>();
orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
@@ -149,8 +149,11 @@ public class LDNMessageDaoImpl extends AbstractHibernateDAO<LDNMessageEntity> im
Predicate activityPredicate = null;
andPredicates.add(
criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED));
// Added OR with object or context - inbound notifications make use of the context item to provide information
// about the repository item the notification refers to
andPredicates.add(
criteriaBuilder.equal(root.get(LDNMessageEntity_.object), item));
criteriaBuilder.or(criteriaBuilder.equal(root.get(LDNMessageEntity_.object), item),
criteriaBuilder.equal(root.get(LDNMessageEntity_.context), item)));
if (activities != null && activities.length > 0) {
activityPredicate = root.get(LDNMessageEntity_.activityStreamType).in(activities);
andPredicates.add(activityPredicate);

View File

@@ -8,11 +8,12 @@
package org.dspace.app.ldn.model;
/**
* REQUESTED means acknowledgements not received yet
* ACCEPTED means acknowledgements of "Accept" type received
* REJECTED means ack of "TentativeReject" type received
* ACCEPTED means acknowledgements of "Accept" or "TentativeAccept" type received
* REJECTED means ack of "Reject" type received
* TENTATIVE_REJECT means ack of "TentativeReject" type received
*
* @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
*/
public enum NotifyRequestStatusEnum {
REJECTED, ACCEPTED, REQUESTED
REJECTED, ACCEPTED, REQUESTED, TENTATIVE_REJECT
}

View File

@@ -20,6 +20,7 @@ import org.apache.http.HttpStatus;
import org.apache.http.client.HttpResponseException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.ldn.LDNMessageEntity;
import org.dspace.app.ldn.action.LDNAction;
import org.dspace.app.ldn.action.LDNActionStatus;
import org.dspace.app.ldn.model.Notification;
@@ -59,6 +60,8 @@ public class LDNMetadataProcessor implements LDNProcessor {
"Announce",
"TentativeReject",
"Accept",
"TentativeAccept",
"Reject",
"coar-notify:ReviewAction",
"coar-notify:IngestAction",
"coar-notify:EndorsementAction");
@@ -168,7 +171,22 @@ public class LDNMetadataProcessor implements LDNProcessor {
String url = null;
if (CONTEXT_ID_ITEM_TYPES.containsAll(notification.getType())) {
url = notification.getContext().getId();
if (notification.getContext() != null) {
url = notification.getContext().getId();
} else if (notification.getInReplyTo() != null) {
// Find context information (the item this notification relates to) via the inReplyTo notification ID
LDNMessageEntity inReplyToldnMessageEntity =
ldnMessageService.find(context, notification.getInReplyTo());
if (inReplyToldnMessageEntity != null) {
String dspaceUrl = configurationService.getProperty("dspace.ui.url")
+ "/handle/";
url = dspaceUrl + inReplyToldnMessageEntity.getObject().getHandle();
// Set context based on the inReplyTo and update in DB
LDNMessageEntity ldnMessageEntity = ldnMessageService.find(context, notification.getId());
ldnMessageEntity.setContext(inReplyToldnMessageEntity.getObject());
ldnMessageService.update(context, ldnMessageEntity);
}
}
} else if (OBJECT_ID_ITEM_TYPES.containsAll(notification.getType())) {
url = notification.getObject().getId();
} else if (OBJECT_SUBJECT_ITEM_TYPES.containsAll(notification.getType())) {

View File

@@ -130,6 +130,17 @@ public interface LDNMessageService {
*/
public NotifyRequestStatus findRequestsByItem(Context context, Item item) throws SQLException;
/**
* find the UUID of a previous tentativeReject notification associated with a new resubmission (Endorsement or
* Review patterns only)
*
* @param context the context
* @param item the previousVersion item associated with a potential resubmission
* @return the UUID of a previous tentativeReject notification associated with a potential resubmission if found
*/
public String findEndorsementOrReviewResubmissionIdByItem(Context context, Item item, NotifyServiceEntity service)
throws SQLException;
/**
* delete the provided ldn message
*

View File

@@ -10,10 +10,11 @@ package org.dspace.app.ldn.service.impl;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -22,7 +23,6 @@ import java.util.UUID;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonSyntaxException;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.ldn.LDNMessageEntity;
@@ -145,6 +145,12 @@ public class LDNMessageServiceImpl implements LDNMessageService {
ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
if (notificationTypeArrayList.size() > 1) {
ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1));
} else {
// The Notification's Type array does not include the CoarNotifyType information, e.g. ack notifications
// Attempt to find it via the inReplyTo if present
if (ldnMessage.getInReplyTo() != null) {
ldnMessage.setCoarNotifyType(ldnMessage.getInReplyTo().getCoarNotifyType());
}
}
ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
ldnMessage.setSourceIp(sourceIp);
@@ -157,7 +163,7 @@ public class LDNMessageServiceImpl implements LDNMessageService {
ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED_IP);
}
}
ldnMessage.setQueueTimeout(new Date());
ldnMessage.setQueueTimeout(Instant.now());
update(context, ldnMessage);
return ldnMessage;
@@ -282,9 +288,9 @@ public class LDNMessageServiceImpl implements LDNMessageService {
msg.setQueueAttempts(msg.getQueueAttempts() + 1);
update(context, msg);
} else {
msg.setQueueLastStartTime(new Date());
msg.setQueueLastStartTime(Instant.now());
msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSING);
msg.setQueueTimeout(DateUtils.addMinutes(new Date(), timeoutInMinutes));
msg.setQueueTimeout(Instant.now().plus(timeoutInMinutes, ChronoUnit.MINUTES));
update(context, msg);
ObjectMapper mapper = new ObjectMapper();
Notification notification = mapper.readValue(msg.getMessage(), Notification.class);
@@ -368,18 +374,23 @@ public class LDNMessageServiceImpl implements LDNMessageService {
offer.setServiceUrl(nse == null ? "" : nse.getUrl());
offer.setOfferType(LDNUtils.getNotifyType(msg.getCoarNotifyType()));
List<LDNMessageEntity> acks = ldnMessageDao.findAllRelatedMessagesByItem(
context, msg, item, "Accept", "TentativeReject", "TentativeAccept", "Announce");
context, msg, item, "Accept", "Reject", "TentativeReject", "TentativeAccept",
"Announce");
if (acks == null || acks.isEmpty()) {
offer.setStatus(NotifyRequestStatusEnum.REQUESTED);
} else if (acks.stream()
.filter(c -> (c.getActivityStreamType().equalsIgnoreCase("TentativeReject")))
.findAny().isPresent()) {
offer.setStatus(NotifyRequestStatusEnum.TENTATIVE_REJECT);
} else if (acks.stream()
.filter(c -> (c.getActivityStreamType().equalsIgnoreCase("Reject")))
.findAny().isPresent()) {
offer.setStatus(NotifyRequestStatusEnum.REJECTED);
} else if (acks.stream()
.filter(c -> (c.getActivityStreamType().equalsIgnoreCase("TentativeAccept") ||
c.getActivityStreamType().equalsIgnoreCase("Accept")))
.findAny().isPresent()) {
offer.setStatus(NotifyRequestStatusEnum.ACCEPTED);
} else if (acks.stream()
.filter(c -> c.getActivityStreamType().equalsIgnoreCase("TentativeReject"))
.findAny().isPresent()) {
offer.setStatus(NotifyRequestStatusEnum.REJECTED);
}
if (acks.stream().filter(
c -> c.getActivityStreamType().equalsIgnoreCase("Announce"))
@@ -391,6 +402,32 @@ public class LDNMessageServiceImpl implements LDNMessageService {
return result;
}
@Override
public String findEndorsementOrReviewResubmissionIdByItem(Context context, Item item, NotifyServiceEntity service)
throws SQLException {
List<LDNMessageEntity> msgs = ldnMessageDao.findAllMessagesByItem(
context, item, "TentativeReject");
if (msgs != null && !msgs.isEmpty()) {
for (LDNMessageEntity msg : msgs) {
// Review and Endorsement are the only patterns supporting resubmissions at present
if (msg.getCoarNotifyType().contains("EndorsementAction")
|| msg.getCoarNotifyType().contains("ReviewAction")) {
// Only provide the resubmissionReplyTo UUID if the pattern supports resubmission
// Add an extra check to ensure that this is a resubmission: current notification service
// matches the service associated with a previous tentativeReject response. This is to avoid a
// case where a previous version of the item received a tentativeReject from one service
// and the author decides to submit the version to a different service, instead of a resubmission
if (msg.getOrigin() != null && msg.getOrigin().getID().equals(service.getID())) {
// Return the first ID found that will be used in the inReplyTo for a resubmission notification
return msg.getID();
}
}
}
}
return null;
}
public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException {
ldnMessageDao.delete(context, ldnMessage);
}

View File

@@ -13,7 +13,6 @@ import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -128,7 +127,7 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
Iterator<Item> itemIterator =
itemService.findByLastModifiedSince(
context,
Date.from(fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant()
);
while (itemIterator.hasNext() && processed < max2Process) {
applyFiltersItem(context, itemIterator.next());

View File

@@ -38,6 +38,8 @@ import org.xml.sax.SAXException;
public class TikaTextExtractionFilter
extends MediaFilter {
private final static Logger log = LogManager.getLogger();
private static final int DEFAULT_MAX_CHARS = 100_000;
private static final int DEFAULT_MAX_ARRAY = 1_000_000;
@Override
public String getFilteredName(String oldFilename) {
@@ -71,15 +73,16 @@ public class TikaTextExtractionFilter
}
// Not using temporary file. We'll use Tika's default in-memory parsing.
// Get maximum characters to extract. Default is 100,000 chars, which is also Tika's default setting.
String extractedText;
int maxChars = configurationService.getIntProperty("textextractor.max-chars", 100_000);
// Get maximum characters to extract. Default is 100,000 chars, which is also Tika's default setting.
int maxChars = configurationService.getIntProperty("textextractor.max-chars", DEFAULT_MAX_CHARS);
// Get maximum size of structure that Tika will try to buffer.
int maxArray = configurationService.getIntProperty("textextractor.max-array", DEFAULT_MAX_ARRAY);
IOUtils.setByteArrayMaxOverride(maxArray);
try {
// Use Tika to extract text from input. Tika will automatically detect the file type.
Tika tika = new Tika();
tika.setMaxStringLength(maxChars); // Tell Tika the maximum number of characters to extract
IOUtils.setByteArrayMaxOverride(
configurationService.getIntProperty("textextractor.max-array", 100_000_000));
extractedText = tika.parseToString(source);
} catch (IOException e) {
System.err.format("Unable to extract text from bitstream in Item %s%n", currentItem.getID().toString());
@@ -141,7 +144,7 @@ public class TikaTextExtractionFilter
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
try {
writer.append(new String(ch), start, length);
writer.append(new String(ch, start, length));
} catch (IOException e) {
String errorMsg = String.format("Could not append to temporary file at %s " +
"when performing text extraction",
@@ -159,7 +162,7 @@ public class TikaTextExtractionFilter
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
try {
writer.append(new String(ch), start, length);
writer.append(new String(ch, start, length));
} catch (IOException e) {
String errorMsg = String.format("Could not append to temporary file at %s " +
"when performing text extraction",
@@ -170,6 +173,10 @@ public class TikaTextExtractionFilter
}
});
ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
int maxArray = configurationService.getIntProperty("textextractor.max-array", DEFAULT_MAX_ARRAY);
IOUtils.setByteArrayMaxOverride(maxArray);
AutoDetectParser parser = new AutoDetectParser();
Metadata metadata = new Metadata();
// parse our source InputStream using the above custom handler

View File

@@ -7,7 +7,7 @@
*/
package org.dspace.app.requestitem;
import java.util.Date;
import java.time.Instant;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -19,8 +19,6 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import org.dspace.content.Bitstream;
import org.dspace.content.Item;
import org.dspace.core.Context;
@@ -63,20 +61,23 @@ public class RequestItem implements ReloadableEntity<Integer> {
private boolean allfiles;
@Column(name = "decision_date")
@Temporal(TemporalType.TIMESTAMP)
private Date decision_date = null;
private Instant decision_date = null;
@Column(name = "expires")
@Temporal(TemporalType.TIMESTAMP)
private Date expires = null;
private Instant expires = null;
@Column(name = "request_date")
@Temporal(TemporalType.TIMESTAMP)
private Date request_date = null;
private Instant request_date = null;
@Column(name = "accept_request")
private boolean accept_request;
@Column(name = "access_token", unique = true, length = 48)
private String access_token = null;
@Column(name = "access_expiry")
private Instant access_expiry = null;
/**
* Protected constructor, create object using:
* {@link org.dspace.app.requestitem.service.RequestItemService#createRequest(
@@ -90,7 +91,7 @@ public class RequestItem implements ReloadableEntity<Integer> {
return requestitem_id;
}
void setAllfiles(boolean allfiles) {
public void setAllfiles(boolean allfiles) {
this.allfiles = allfiles;
}
@@ -139,7 +140,8 @@ public class RequestItem implements ReloadableEntity<Integer> {
}
/**
* @return a unique request identifier which can be emailed.
* @return a unique request identifier which can be emailed to the *approver* of the request.
* This is not the same as the access token, which is used by the requester to access the item after approval.
*/
public String getToken() {
return token;
@@ -161,11 +163,11 @@ public class RequestItem implements ReloadableEntity<Integer> {
return bitstream;
}
public Date getDecision_date() {
public Instant getDecision_date() {
return decision_date;
}
public void setDecision_date(Date decision_date) {
public void setDecision_date(Instant decision_date) {
this.decision_date = decision_date;
}
@@ -177,19 +179,53 @@ public class RequestItem implements ReloadableEntity<Integer> {
this.accept_request = accept_request;
}
public Date getExpires() {
public Instant getExpires() {
return expires;
}
void setExpires(Date expires) {
void setExpires(Instant expires) {
this.expires = expires;
}
public Date getRequest_date() {
public Instant getRequest_date() {
return request_date;
}
void setRequest_date(Date request_date) {
void setRequest_date(Instant request_date) {
this.request_date = request_date;
}
/**
* @return A unique token to be used by the requester when granted access to the resource, which
* can be emailed upon approval
*/
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
/**
* @return The date and time when the access token expires.
*/
public Instant getAccess_expiry() {
return access_expiry;
}
public void setAccess_expiry(Instant access_expiry) {
this.access_expiry = access_expiry;
}
/**
* Sanitize personal information and the approval token, to be used when returning a RequestItem
* to Angular, especially for users clicking on the secure link
*/
public void sanitizePersonalData() {
setReqEmail("sanitized");
setReqName("sanitized");
setReqMessage("sanitized");
// Even though [approval] token is not a name, it can be used to access the original object
setToken("sanitized");
}
}

View File

@@ -10,6 +10,8 @@ package org.dspace.app.requestitem;
import java.io.IOException;
import java.sql.SQLException;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import jakarta.annotation.ManagedBean;
@@ -28,6 +30,7 @@ import org.dspace.core.Context;
import org.dspace.core.Email;
import org.dspace.core.I18nUtil;
import org.dspace.core.LogHelper;
import org.dspace.core.Utils;
import org.dspace.eperson.EPerson;
import org.dspace.handle.service.HandleService;
import org.dspace.services.ConfigurationService;
@@ -174,9 +177,23 @@ public class RequestItemEmailNotifier {
grantorAddress = grantor.getEmail();
}
// Set date format for access expiry date
String accessExpiryFormat = configurationService.getProperty("request.item.grant.link.dateformat",
"yyyy-MM-dd");
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(accessExpiryFormat)
.withZone(ZoneId.of("UTC"));
Email email;
// If this item has a secure access token, send the template with that link instead of attaching files
if (ri.isAccept_request() && ri.getAccess_token() != null) {
email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(),
"request_item.granted_token"));
} else {
email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(),
ri.isAccept_request() ? "request_item.granted" : "request_item.rejected"));
}
// Build an email back to the requester.
Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(),
ri.isAccept_request() ? "request_item.granted" : "request_item.rejected"));
email.addArgument(ri.getReqName()); // {0} requestor's name
email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); // {1} URL of the requested Item
email.addArgument(ri.getItem().getName()); // {2} title of the requested Item
@@ -188,34 +205,47 @@ public class RequestItemEmailNotifier {
// Attach bitstreams.
try {
if (ri.isAccept_request()) {
if (ri.isAllfiles()) {
Item item = ri.getItem();
List<Bundle> bundles = item.getBundles("ORIGINAL");
for (Bundle bundle : bundles) {
List<Bitstream> bitstreams = bundle.getBitstreams();
for (Bitstream bitstream : bitstreams) {
if (!bitstream.getFormat(context).isInternal() &&
requestItemService.isRestricted(context,
bitstream)) {
// #8636 Anyone receiving the email can respond to the
// request without authenticating into DSpace
context.turnOffAuthorisationSystem();
email.addAttachment(
bitstreamService.retrieve(context, bitstream),
bitstream.getName(),
bitstream.getFormat(context).getMIMEType());
context.restoreAuthSystemState();
}
}
if (ri.getAccess_token() != null) {
// {6} secure access link
email.addArgument(configurationService.getProperty("dspace.ui.url")
+ "/items/" + ri.getItem().getID()
+ "?accessToken=" + ri.getAccess_token());
// {7} access end date, but only add formatted date string if it is set and not "forever"
if (ri.getAccess_expiry() != null && !ri.getAccess_expiry().equals(Utils.getMaxTimestamp())) {
email.addArgument(dateTimeFormatter.format(ri.getAccess_expiry()));
} else {
email.addArgument(null);
}
} else {
Bitstream bitstream = ri.getBitstream();
// #8636 Anyone receiving the email can respond to the request without authenticating into DSpace
context.turnOffAuthorisationSystem();
email.addAttachment(bitstreamService.retrieve(context, bitstream),
bitstream.getName(),
bitstream.getFormat(context).getMIMEType());
context.restoreAuthSystemState();
if (ri.isAllfiles()) {
Item item = ri.getItem();
List<Bundle> bundles = item.getBundles("ORIGINAL");
for (Bundle bundle : bundles) {
List<Bitstream> bitstreams = bundle.getBitstreams();
for (Bitstream bitstream : bitstreams) {
if (!bitstream.getFormat(context).isInternal() &&
requestItemService.isRestricted(context,
bitstream)) {
// #8636 Anyone receiving the email can respond to the
// request without authenticating into DSpace
context.turnOffAuthorisationSystem();
email.addAttachment(
bitstreamService.retrieve(context, bitstream),
bitstream.getName(),
bitstream.getFormat(context).getMIMEType());
context.restoreAuthSystemState();
}
}
}
} else {
Bitstream bitstream = ri.getBitstream();
//#8636 Anyone receiving the email can respond to the request without authenticating into DSpace
context.turnOffAuthorisationSystem();
email.addAttachment(bitstreamService.retrieve(context, bitstream),
bitstream.getName(),
bitstream.getFormat(context).getMIMEType());
context.restoreAuthSystemState();
}
}
email.send();
} else {

View File

@@ -7,25 +7,40 @@
*/
package org.dspace.app.requestitem;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.Date;
import java.text.ParseException;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import org.apache.http.client.utils.URIBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.requestitem.dao.RequestItemDAO;
import org.dspace.app.requestitem.service.RequestItemService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogHelper;
import org.dspace.core.Utils;
import org.dspace.services.ConfigurationService;
import org.dspace.util.DateMathParser;
import org.dspace.util.MultiFormatDateParser;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -35,6 +50,7 @@ import org.springframework.beans.factory.annotation.Autowired;
* This class should never be accessed directly.
*
* @author kevinvandevelde at atmire.com
* @author Kim Shepherd
*/
public class RequestItemServiceImpl implements RequestItemService {
@@ -49,16 +65,43 @@ public class RequestItemServiceImpl implements RequestItemService {
@Autowired(required = true)
protected ResourcePolicyService resourcePolicyService;
@Autowired
protected ConfigurationService configurationService;
/**
* Always set UTC for dateMathParser for consistent database date handling
*/
static DateMathParser dateMathParser = new DateMathParser(TimeZone.getTimeZone("UTC"));
private static final int DEFAULT_MINIMUM_FILE_SIZE = 20;
protected RequestItemServiceImpl() {
}
/**
* Create a new request-a-copy item request.
*
* @param context The relevant DSpace Context.
* @param bitstream The requested bitstream
* @param item The requested item
* @param allFiles true indicates that all bitstreams of this item are requested
* @param reqEmail email
* Requester email
* @param reqName Requester name
* @param reqMessage Request message text
* @return token to be used to approver for grant/deny
* @throws SQLException
*/
@Override
public String createRequest(Context context, Bitstream bitstream, Item item,
boolean allFiles, String reqEmail, String reqName, String reqMessage)
throws SQLException {
// Create an empty request item
RequestItem requestItem = requestItemDAO.create(context, new RequestItem());
// Set values of the request item based on supplied parameters
requestItem.setToken(Utils.generateHexKey());
requestItem.setBitstream(bitstream);
requestItem.setItem(item);
@@ -66,12 +109,58 @@ public class RequestItemServiceImpl implements RequestItemService {
requestItem.setReqEmail(reqEmail);
requestItem.setReqName(reqName);
requestItem.setReqMessage(reqMessage);
requestItem.setRequest_date(new Date());
requestItem.setRequest_date(Instant.now());
// If the 'link' feature is enabled and the filesize threshold is met, pre-generate access token now
// so it can be previewed by approver and so Angular and REST services can use the existence of this token
// as an indication of which delivery method to use.
// Access period will be created upon actual approval.
if (configurationService.getBooleanProperty("request.item.grant.link", false)) {
// The 'send link' feature is enabled, is the file(s) requested over the size threshold (megabytes as int)?
// Default is 20MB minimum. For inspection purposes we convert to bytes.
long minimumSize = configurationService.getLongProperty(
"request.item.grant.link.filesize", DEFAULT_MINIMUM_FILE_SIZE) * 1024 * 1024;
// If we have a single bitstream, we will initialise the "minimum threshold reached" correctly
boolean minimumSizeThresholdReached = (null != bitstream && bitstream.getSizeBytes() >= minimumSize);
// If all files (and presumably no min reached since bitstream should be null), we look for ANY >= min size
if (!minimumSizeThresholdReached && allFiles) {
// Iterate bitstream and inspect file sizes. At each loop iteration we will break out if the min
// was already reached.
String[] bundleNames = configurationService.getArrayProperty("request.item.grant.link.bundles",
new String[]{"ORIGINAL"});
for (String bundleName : bundleNames) {
if (!minimumSizeThresholdReached) {
for (Bundle bundle : item.getBundles(bundleName)) {
if (null != bundle && !minimumSizeThresholdReached) {
for (Bitstream bitstreamToCheck : bundle.getBitstreams()) {
if (bitstreamToCheck.getSizeBytes() >= minimumSize) {
minimumSizeThresholdReached = true;
break;
}
}
}
}
}
}
}
// Now, only generate and set an access token if the minimum file size threshold was reached.
// Otherwise, an email attachment will still be used.
// From now on, the existence of an access token in the RequestItem indicates that a web link should be
// sent instead of attaching file(s) as an attachment.
if (minimumSizeThresholdReached) {
requestItem.setAccess_token(Utils.generateHexKey());
}
}
// Save the request item
requestItemDAO.save(context, requestItem);
log.debug("Created RequestItem with ID {} and token {}",
requestItem::getID, requestItem::getToken);
log.debug("Created RequestItem with ID {}, approval token {}, access token {}, access expiry {}",
requestItem::getID, requestItem::getToken, requestItem::getAccess_token, requestItem::getAccess_expiry);
// Return the approver token
return requestItem.getToken();
}
@@ -128,4 +217,186 @@ public class RequestItemServiceImpl implements RequestItemService {
}
return true;
}
/**
* Find a request item by its access token. This is the token that a requester would use
* to authenticate themselves as a granted requester.
* It is up to the RequestItemRepository to check validity of the item, access granted, data sanitization, etc.
*
* @param context current DSpace session.
* @param accessToken the token identifying the request to be temporarily accessed
* @return request item data
*/
@Override
public RequestItem findByAccessToken(Context context, String accessToken) {
try {
return requestItemDAO.findByAccessToken(context, accessToken);
} catch (SQLException e) {
log.error(e.getMessage());
return null;
}
}
/**
* Set the access expiry date for the request item.
* @param requestItem the request item to update
* @param accessExpiry the expiry date to set
*/
@Override
public void setAccessExpiry(RequestItem requestItem, Instant accessExpiry) {
requestItem.setAccess_expiry(accessExpiry);
}
/**
* Take a string either as a formatted date, or in the "math" format expected by
* the DateMathParser, e.g. +7DAYS or +10MONTHS, and set the access expiry date accordingly.
* There are no special checks here to check that the date is in the future, or after the
* 'decision date', as there may be legitimate reasons to set past dates.
* If past dates are not allowed by some interface, then the caller should check this.
*
* @param requestItem the request item to update
* @param dateOrDelta the delta as a string in format expected by the DateMathParser
*/
@Override
public void setAccessExpiry(RequestItem requestItem, String dateOrDelta) {
try {
setAccessExpiry(requestItem, parseDateOrDelta(dateOrDelta, requestItem.getDecision_date()));
} catch (ParseException e) {
log.error("Error parsing access expiry or duration: {}", e.getMessage());
}
}
/**
* Taking into account 'accepted' flag, bitstream id or allfiles flag, decision date and access period,
* either return cleanly or throw an AuthorizeException
*
* @param context the DSpace context
* @param requestItem the request item containing request and approval data
* @param bitstream the bitstream to which access is requested
* @param accessToken the access token supplied by the user (e.g. to REST controller)
* @throws AuthorizeException
*/
@Override
public void authorizeAccessByAccessToken(Context context, RequestItem requestItem, Bitstream bitstream,
String accessToken) throws AuthorizeException {
if (requestItem == null || bitstream == null || context == null || accessToken == null) {
throw new AuthorizeException("Null resources provided, not authorized");
}
// 1. Request is accepted
if (requestItem.isAccept_request()
// 2. Request access token is not null and matches supplied string
&& (requestItem.getAccess_token() != null && requestItem.getAccess_token().equals(accessToken))
// 3. Request is 'allfiles' or for this bitstream ID
&& (requestItem.isAllfiles() || bitstream.equals(requestItem.getBitstream()))
// 4. access expiry timestamp is null (forever), or is *after* the current time
&& (requestItem.getAccess_expiry() == null || requestItem.getAccess_expiry().isAfter(Instant.now()))
) {
log.info("Authorizing access to bitstream {} by access token", bitstream.getID());
return;
}
// Default, throw authorize exception
throw new AuthorizeException("Unauthorized access to bitstream by access token for bitstream ID "
+ bitstream.getID());
}
/**
* Taking into account 'accepted' flag, bitstream id or allfiles flag, decision date and access period,
* either return cleanly or throw an AuthorizeException
*
* @param context the DSpace context
* @param bitstream the bitstream to which access is requested
* @param accessToken the access token supplied by the user (e.g. to REST controller)
* @throws AuthorizeException
*/
@Override
public void authorizeAccessByAccessToken(Context context, Bitstream bitstream, String accessToken)
throws AuthorizeException {
if (bitstream == null || context == null || accessToken == null) {
throw new AuthorizeException("Null resources provided, not authorized");
}
// get request item from access token
RequestItem requestItem = findByAccessToken(context, accessToken);
if (requestItem == null) {
throw new AuthorizeException("Null item request provided, not authorized");
}
// Continue with authorization check
authorizeAccessByAccessToken(context, requestItem, bitstream, accessToken);
}
/**
* Generate a link back to DSpace, to act on a request.
*
* @param token identifies the request.
* @return URL to the item request API, with the token as request parameter
* "token".
* @throws URISyntaxException passed through.
* @throws MalformedURLException passed through.
*/
@Override
public String getLinkTokenEmail(String token)
throws URISyntaxException, MalformedURLException {
final String base = configurationService.getProperty("dspace.ui.url");
URIBuilder uriBuilder = new URIBuilder(base);
String currentPath = uriBuilder.getPath();
String newPath = (currentPath == null || currentPath.isEmpty() || currentPath.equals("/"))
? "/request-a-copy/" + token
: currentPath + "/request-a-copy/" + token;
URI uri = uriBuilder.setPath(newPath).build();
return uri.toURL().toExternalForm();
}
/**
* Sanitize a RequestItem. The following values in the referenced RequestItem
* are nullified:
* - approver token (aka token)
* - requester name
* - requester email
* - requester message
*
* These properties contain personal information, or can be used to access personal information
* and are not needed except for sending the original request and grant/deny emails
*
* @param requestItem
*/
@Override
public void sanitizeRequestItem(Context context, RequestItem requestItem) {
if (null == requestItem) {
log.error("Null request item passed for sanitization, skipping");
return;
}
// Sanitized referenced data (strips requester name, email, message, and the approver token)
requestItem.sanitizePersonalData();
}
/**
* Parse a date or delta string into an Instant. Kept here as a static method for use in unit tests
* and other areas that might not have access to the full spring service
*
* @param dateOrDelta
* @param decisionDate
* @return parsed date as instant
* @throws ParseException
*/
public static Instant parseDateOrDelta(String dateOrDelta, Instant decisionDate)
throws ParseException, DateTimeException {
// First, if dateOrDelta is a null string or "FOREVER", we will set the expiry
// date to a very distant date in the future.
if (dateOrDelta == null || dateOrDelta.equals("FOREVER")) {
return Utils.getMaxTimestamp();
}
// Next, try parsing as a straight date using the multiple format parser
ZonedDateTime parsedExpiryDate = MultiFormatDateParser.parse(dateOrDelta);
if (parsedExpiryDate == null) {
// That did not work, so try parsing as a delta
// Set the 'now' date to the decision date of the request item
dateMathParser.setNow(LocalDateTime.ofInstant(decisionDate, ZoneOffset.UTC));
// Parse the delta (e.g. +7DAYS) and set the new access expiry date
return dateMathParser.parseMath(dateOrDelta).toInstant(ZoneOffset.UTC);
} else {
// The expiry date was a valid formatted date string, so set the access expiry date
return parsedExpiryDate.toInstant();
}
}
}

View File

@@ -26,7 +26,7 @@ import org.dspace.core.GenericDAO;
*/
public interface RequestItemDAO extends GenericDAO<RequestItem> {
/**
* Fetch a request named by its unique token (passed in emails).
* Fetch a request named by its unique approval token (passed in emails).
*
* @param context the current DSpace context.
* @param token uniquely identifies the request.
@@ -35,5 +35,18 @@ public interface RequestItemDAO extends GenericDAO<RequestItem> {
*/
public RequestItem findByToken(Context context, String token) throws SQLException;
/**
* Fetch a request named by its unique access token (passed in emails).
* Note this is the token used by the requester to access an approved resource, not the token
* used by the item submitter or helpdesk to grant the access.
*
* @param context the current DSpace context.
* @param accessToken uniquely identifies the request
* @return the found request or {@code null}
* @throws SQLException passed through.
*/
public RequestItem findByAccessToken(Context context, String accessToken) throws SQLException;
public Iterator<RequestItem> findByItem(Context context, Item item) throws SQLException;
}

View File

@@ -42,6 +42,17 @@ public class RequestItemDAOImpl extends AbstractHibernateDAO<RequestItem> implem
criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.token), token));
return uniqueResult(context, criteriaQuery, false, RequestItem.class);
}
@Override
public RequestItem findByAccessToken(Context context, String accessToken) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RequestItem.class);
Root<RequestItem> requestItemRoot = criteriaQuery.from(RequestItem.class);
criteriaQuery.select(requestItemRoot);
criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.access_token), accessToken));
return uniqueResult(context, criteriaQuery, true, RequestItem.class);
}
@Override
public Iterator<RequestItem> findByItem(Context context, Item item) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);

View File

@@ -8,13 +8,19 @@
/**
* Feature for conveying a request that materials forbidden to the requester
* by resource policy be made available by other means. The request will be
* e-mailed to a responsible party for consideration and action. Find details
* in the user documentation under the rubric "Request a Copy".
* by resource policy be made available by other means.
*
* There are several methods of making the resource(s) available to the requester:
* 1. The request will be e-mailed to a responsible party for consideration and action.
* Find details in the user documentation under the rubric "Request a Copy".
*
* <p>Mailing is handled by {@link RequestItemEmailNotifier}. Responsible
* parties are represented by {@link RequestItemAuthor}
*
* 2. A unique 48-char token will be generated and included in a special weblink emailed to the requester.
* This link will provide access to the requester as though they had READ policy access while the access period
* has not expired, or forever if the access period is null.
*
* <p>This package includes several "strategy" classes which discover
* responsible parties in various ways. See
* {@link RequestItemSubmitterStrategy} and the classes which extend it, and

View File

@@ -7,11 +7,15 @@
*/
package org.dspace.app.requestitem.service;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.time.Instant;
import java.util.Iterator;
import java.util.List;
import org.dspace.app.requestitem.RequestItem;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
@@ -23,6 +27,7 @@ import org.dspace.core.Context;
* for the RequestItem object and is autowired by Spring.
*
* @author kevinvandevelde at atmire.com
* @author Kim Shepherd
*/
public interface RequestItemService {
@@ -40,7 +45,7 @@ public interface RequestItemService {
* @return the token of the request item
* @throws SQLException if database error
*/
public String createRequest(Context context, Bitstream bitstream, Item item,
String createRequest(Context context, Bitstream bitstream, Item item,
boolean allFiles, String reqEmail, String reqName, String reqMessage)
throws SQLException;
@@ -49,35 +54,46 @@ public interface RequestItemService {
*
* @param context current DSpace session.
* @return all item requests.
* @throws java.sql.SQLException passed through.
* @throws SQLException passed through.
*/
public List<RequestItem> findAll(Context context)
List<RequestItem> findAll(Context context)
throws SQLException;
/**
* Retrieve a request by its token.
* Retrieve a request by its approver token.
*
* @param context current DSpace session.
* @param token the token identifying the request.
* @param token the token identifying the request to be approved.
* @return the matching request, or null if not found.
*/
public RequestItem findByToken(Context context, String token);
RequestItem findByToken(Context context, String token);
/**
* Retrieve a request by its access token, for use by the requester
*
* @param context current DSpace session.
* @param token the token identifying the request to be temporarily accessed
* @return the matching request, or null if not found.
*/
RequestItem findByAccessToken(Context context, String token);
/**
* Retrieve a request based on the item.
* @param context current DSpace session.
* @param item the item to find requests for.
* @return the matching requests, or null if not found.
*/
public Iterator<RequestItem> findByItem(Context context, Item item) throws SQLException;
Iterator<RequestItem> findByItem(Context context, Item item) throws SQLException;
/**
* Save updates to the record. Only accept_request, and decision_date are set-able.
* Save updates to the record. Only accept_request, decision_date, access_period are settable.
*
* Note: the "is settable" rules mentioned here are enforced in RequestItemRest with annotations meaning that
* these JSON properties are considered READ-ONLY by the core DSpaceRestRepository methods
*
* @param context The relevant DSpace Context.
* @param requestItem requested item
*/
public void update(Context context, RequestItem requestItem);
void update(Context context, RequestItem requestItem);
/**
* Remove the record from the database.
@@ -85,7 +101,7 @@ public interface RequestItemService {
* @param context current DSpace context.
* @param request record to be removed.
*/
public void delete(Context context, RequestItem request);
void delete(Context context, RequestItem request);
/**
* Is there at least one valid READ resource policy for this object?
@@ -94,6 +110,77 @@ public interface RequestItemService {
* @return true if a READ policy applies.
* @throws SQLException passed through.
*/
public boolean isRestricted(Context context, DSpaceObject o)
boolean isRestricted(Context context, DSpaceObject o)
throws SQLException;
/**
* Set the access expiry timestamp for a request item. After this date, the
* bitstream(s) will no longer be available for download even with a token.
* @param requestItem the request item
* @param accessExpiry the expiry timestamp
*/
void setAccessExpiry(RequestItem requestItem, Instant accessExpiry);
/**
* Set the access expiry timestamp for a request item by delta string.
* After this date, the bitstream(s) will no longer be available for download
* even with a token.
* @param requestItem the request item
* @param delta the delta to calculate the expiry timestamp, from the decision date
*/
void setAccessExpiry(RequestItem requestItem, String delta);
/**
* Taking into account 'accepted' flag, bitstream id or allfiles flag, decision date and access period,
* either return cleanly or throw an AuthorizeException
*
* @param context the DSpace context
* @param requestItem the request item containing request and approval data
* @param bitstream the bitstream to which access is requested
* @param accessToken the access token supplied by the user (e.g. to REST controller)
* @throws AuthorizeException
*/
void authorizeAccessByAccessToken(Context context, RequestItem requestItem, Bitstream bitstream,
String accessToken)
throws AuthorizeException;
/**
* Taking into account 'accepted' flag, bitstream id or allfiles flag, decision date and access period,
* either return cleanly or throw an AuthorizeException
*
* @param context the DSpace context
* @param bitstream the bitstream to which access is requested
* @param accessToken the access token supplied by the user (e.g. to REST controller)
* @throws AuthorizeException
*/
void authorizeAccessByAccessToken(Context context, Bitstream bitstream, String accessToken)
throws AuthorizeException;
/**
* Generate a link back to DSpace, to act on a request.
*
* @param token identifies the request.
* @return URL to the item request API, with the token as request parameter
* "token".
* @throws URISyntaxException passed through.
* @throws MalformedURLException passed through.
*/
String getLinkTokenEmail(String token)
throws URISyntaxException, MalformedURLException;
/**
* Sanitize a RequestItem depending on the current session user. If the current user is not
* the approver, an administrator or other privileged group, the following values in the return object
* are nullified:
* - approver token (aka token)
* - requester name
* - requester email
* - requester message
*
* These properties contain personal information, or can be used to access personal information
* and are not needed except for sending the original request and grant/deny emails
*
* @param requestItem
*/
void sanitizeRequestItem(Context context, RequestItem requestItem);
}

View File

@@ -18,6 +18,7 @@ import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.sfx.service.SFXFileReaderService;
import org.dspace.app.util.XMLUtils;
import org.dspace.content.DCPersonName;
import org.dspace.content.Item;
import org.dspace.content.MetadataValue;
@@ -79,9 +80,9 @@ public class SFXFileReaderServiceImpl implements SFXFileReaderService {
log.info("Parsing XML file... " + fileName);
DocumentBuilder docBuilder;
Document doc = null;
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setIgnoringElementContentWhitespace(true);
try {
DocumentBuilderFactory docBuilderFactory = XMLUtils.getDocumentBuilderFactory();
docBuilderFactory.setIgnoringElementContentWhitespace(true);
docBuilder = docBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
log.error("Wrong parser configuration: " + e.getMessage());

View File

@@ -17,15 +17,15 @@ import jakarta.annotation.PostConstruct;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.client.DSpaceHttpClientFactory;
import org.dspace.app.sherpa.v2.SHERPAPublisherResponse;
import org.dspace.app.sherpa.v2.SHERPAResponse;
import org.dspace.app.sherpa.v2.SHERPAUtils;
@@ -45,8 +45,6 @@ import org.springframework.cache.annotation.Cacheable;
*/
public class SHERPAService {
private CloseableHttpClient client = null;
private int maxNumberOfTries;
private long sleepBetweenTimeouts;
private int timeout = 5000;
@@ -59,19 +57,6 @@ public class SHERPAService {
@Autowired
ConfigurationService configurationService;
/**
* Create a new HTTP builder with sensible defaults in constructor
*/
public SHERPAService() {
HttpClientBuilder builder = HttpClientBuilder.create();
// httpclient 4.3+ doesn't appear to have any sensible defaults any more. Setting conservative defaults as
// not to hammer the SHERPA service too much.
client = builder
.disableAutomaticRetries()
.setMaxConnTotal(5)
.build();
}
/**
* Complete initialization of the Bean.
*/
@@ -132,46 +117,47 @@ public class SHERPAService {
timeout,
sleepBetweenTimeouts));
try {
try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5)) {
Thread.sleep(sleepBetweenTimeouts);
// Construct a default HTTP method (first result)
method = constructHttpGet(type, field, predicate, value, start, limit);
// Execute the method
HttpResponse response = client.execute(method);
int statusCode = response.getStatusLine().getStatusCode();
try (CloseableHttpResponse response = client.execute(method)) {
int statusCode = response.getStatusLine().getStatusCode();
log.debug(response.getStatusLine().getStatusCode() + ": "
+ response.getStatusLine().getReasonPhrase());
log.debug(response.getStatusLine().getStatusCode() + ": "
+ response.getStatusLine().getReasonPhrase());
if (statusCode != HttpStatus.SC_OK) {
sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO return not OK status: "
+ statusCode);
String errorBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
log.error("Error from SHERPA HTTP request: " + errorBody);
}
HttpEntity responseBody = response.getEntity();
// If the response body is valid, pass to SHERPAResponse for parsing as JSON
if (null != responseBody) {
log.debug("Non-null SHERPA response received for query of " + value);
InputStream content = null;
try {
content = responseBody.getContent();
sherpaResponse =
new SHERPAPublisherResponse(content, SHERPAPublisherResponse.SHERPAFormat.JSON);
} catch (IOException e) {
log.error("Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(), e);
} finally {
if (content != null) {
content.close();
}
if (statusCode != HttpStatus.SC_OK) {
sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO return not OK status: "
+ statusCode);
String errorBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
log.error("Error from SHERPA HTTP request: " + errorBody);
}
HttpEntity responseBody = response.getEntity();
// If the response body is valid, pass to SHERPAResponse for parsing as JSON
if (null != responseBody) {
log.debug("Non-null SHERPA response received for query of " + value);
InputStream content = null;
try {
content = responseBody.getContent();
sherpaResponse =
new SHERPAPublisherResponse(content, SHERPAPublisherResponse.SHERPAFormat.JSON);
} catch (IOException e) {
log.error("Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(), e);
} finally {
if (content != null) {
content.close();
}
}
} else {
log.debug("Empty SHERPA response body for query on " + value);
sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO returned no response");
}
} else {
log.debug("Empty SHERPA response body for query on " + value);
sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO returned no response");
}
} catch (URISyntaxException e) {
String errorMessage = "Error building SHERPA v2 API URI: " + e.getMessage();
@@ -235,45 +221,46 @@ public class SHERPAService {
timeout,
sleepBetweenTimeouts));
try {
try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5)) {
Thread.sleep(sleepBetweenTimeouts);
// Construct a default HTTP method (first result)
method = constructHttpGet(type, field, predicate, value, start, limit);
// Execute the method
HttpResponse response = client.execute(method);
int statusCode = response.getStatusLine().getStatusCode();
try (CloseableHttpResponse response = client.execute(method)) {
int statusCode = response.getStatusLine().getStatusCode();
log.debug(response.getStatusLine().getStatusCode() + ": "
+ response.getStatusLine().getReasonPhrase());
log.debug(response.getStatusLine().getStatusCode() + ": "
+ response.getStatusLine().getReasonPhrase());
if (statusCode != HttpStatus.SC_OK) {
sherpaResponse = new SHERPAResponse("SHERPA/RoMEO return not OK status: "
+ statusCode);
String errorBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
log.error("Error from SHERPA HTTP request: " + errorBody);
}
HttpEntity responseBody = response.getEntity();
// If the response body is valid, pass to SHERPAResponse for parsing as JSON
if (null != responseBody) {
log.debug("Non-null SHERPA response received for query of " + value);
InputStream content = null;
try {
content = responseBody.getContent();
sherpaResponse = new SHERPAResponse(content, SHERPAResponse.SHERPAFormat.JSON);
} catch (IOException e) {
log.error("Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(), e);
} finally {
if (content != null) {
content.close();
}
if (statusCode != HttpStatus.SC_OK) {
sherpaResponse = new SHERPAResponse("SHERPA/RoMEO return not OK status: "
+ statusCode);
String errorBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
log.error("Error from SHERPA HTTP request: " + errorBody);
}
HttpEntity responseBody = response.getEntity();
// If the response body is valid, pass to SHERPAResponse for parsing as JSON
if (null != responseBody) {
log.debug("Non-null SHERPA response received for query of " + value);
InputStream content = null;
try {
content = responseBody.getContent();
sherpaResponse = new SHERPAResponse(content, SHERPAResponse.SHERPAFormat.JSON);
} catch (IOException e) {
log.error("Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage(), e);
} finally {
if (content != null) {
content.close();
}
}
} else {
log.debug("Empty SHERPA response body for query on " + value);
sherpaResponse = new SHERPAResponse("SHERPA/RoMEO returned no response");
}
} else {
log.debug("Empty SHERPA response body for query on " + value);
sherpaResponse = new SHERPAResponse("SHERPA/RoMEO returned no response");
}
} catch (URISyntaxException e) {
String errorMessage = "Error building SHERPA v2 API URI: " + e.getMessage();
@@ -283,7 +270,7 @@ public class SHERPAService {
String errorMessage = "Encountered exception while contacting SHERPA/RoMEO: " + e.getMessage();
log.error(errorMessage, e);
sherpaResponse = new SHERPAResponse(errorMessage);
} catch (InterruptedException e) {
} catch (InterruptedException e) {
String errorMessage = "Encountered exception while sleeping thread: " + e.getMessage();
log.error(errorMessage, e);
sherpaResponse = new SHERPAResponse(errorMessage);

View File

@@ -12,8 +12,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -59,7 +59,7 @@ public class SHERPAResponse implements Serializable {
private String uri;
@JsonIgnore
private Date retrievalTime = new Date();
private Instant retrievalTime = Instant.now();
// Format enum - currently only JSON is supported
public enum SHERPAFormat {
@@ -563,7 +563,7 @@ public class SHERPAResponse implements Serializable {
return metadata;
}
public Date getRetrievalTime() {
public Instant getRetrievalTime() {
return retrievalTime;
}
}

View File

@@ -12,7 +12,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Date;
import java.time.Instant;
import java.util.zip.GZIPOutputStream;
/**
@@ -110,7 +110,7 @@ public abstract class AbstractGenerator {
* @throws IOException if IO error
* if an error occurs writing
*/
public void addURL(String url, Date lastMod) throws IOException {
public void addURL(String url, Instant lastMod) throws IOException {
// Kick things off if this is the first call
if (currentOutput == null) {
startNewFile();
@@ -143,7 +143,7 @@ public abstract class AbstractGenerator {
/**
* Complete writing sitemap files and write the index files. This is invoked
* when all calls to {@link AbstractGenerator#addURL(String, Date)} have
* when all calls to {@link AbstractGenerator#addURL(String, Instant)} have
* been completed, and invalidates the generator.
*
* @return number of sitemap files written.
@@ -177,7 +177,7 @@ public abstract class AbstractGenerator {
* applicable
* @return the mark-up to include
*/
public abstract String getURLText(String url, Date lastMod);
public abstract String getURLText(String url, Instant lastMod);
/**
* Return the boilerplate at the top of a sitemap file.

View File

@@ -7,10 +7,11 @@
*/
package org.dspace.app.sitemap;
import static org.dspace.discovery.SearchUtils.RESOURCE_TYPE_FIELD;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import org.apache.commons.cli.CommandLine;
@@ -189,7 +190,8 @@ public class GenerateSitemaps {
try {
DiscoverQuery discoveryQuery = new DiscoverQuery();
discoveryQuery.setMaxResults(PAGE_SIZE);
discoveryQuery.setQuery("search.resourcetype:Community");
discoveryQuery.setQuery("*:*");
discoveryQuery.addFilterQueries(RESOURCE_TYPE_FIELD + ":Community");
do {
discoveryQuery.setStart(offset);
DiscoverResult discoverResult = searchService.search(c, discoveryQuery);
@@ -213,7 +215,8 @@ public class GenerateSitemaps {
offset = 0;
discoveryQuery = new DiscoverQuery();
discoveryQuery.setMaxResults(PAGE_SIZE);
discoveryQuery.setQuery("search.resourcetype:Collection");
discoveryQuery.setQuery("*:*");
discoveryQuery.addFilterQueries(RESOURCE_TYPE_FIELD + ":Collection");
do {
discoveryQuery.setStart(offset);
DiscoverResult discoverResult = searchService.search(c, discoveryQuery);
@@ -237,7 +240,8 @@ public class GenerateSitemaps {
offset = 0;
discoveryQuery = new DiscoverQuery();
discoveryQuery.setMaxResults(PAGE_SIZE);
discoveryQuery.setQuery("search.resourcetype:Item");
discoveryQuery.setQuery("*:*");
discoveryQuery.addFilterQueries(RESOURCE_TYPE_FIELD + ":Item");
discoveryQuery.addSearchField("search.entitytype");
do {
@@ -256,7 +260,6 @@ public class GenerateSitemaps {
} else {
url = uiURLStem + "items/" + doc.getID();
}
Date lastMod = doc.getLastModified();
c.uncacheEntity(doc.getIndexedObject());
if (makeHTMLMap) {

View File

@@ -10,7 +10,7 @@ package org.dspace.app.sitemap;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;
import java.time.Instant;
/**
* Class for generating HTML "sitemaps" which contain links to various pages in
@@ -77,7 +77,7 @@ public class HTMLSitemapGenerator extends AbstractGenerator {
}
@Override
public String getURLText(String url, Date lastMod) {
public String getURLText(String url, Instant lastMod) {
StringBuffer urlText = new StringBuffer();
urlText.append("<li><a href=\"").append(url).append("\">").append(url)

View File

@@ -10,9 +10,8 @@ package org.dspace.app.sitemap;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
/**
* Class for generating <a href="http://sitemaps.org/">Sitemaps</a> to improve
@@ -36,8 +35,7 @@ public class SitemapsOrgGenerator extends AbstractGenerator {
/**
* The correct date format
*/
protected DateFormat w3dtfFormat = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss'Z'");
protected DateTimeFormatter w3dtfFormat = DateTimeFormatter.ISO_INSTANT;
/**
* Construct a sitemaps.org protocol sitemap generator, writing files to the
@@ -85,7 +83,7 @@ public class SitemapsOrgGenerator extends AbstractGenerator {
}
@Override
public String getURLText(String url, Date lastMod) {
public String getURLText(String url, Instant lastMod) {
StringBuilder urlText = new StringBuilder();
urlText.append("<url><loc>").append(url).append("</loc>");
@@ -111,7 +109,7 @@ public class SitemapsOrgGenerator extends AbstractGenerator {
@Override
public void writeIndex(PrintStream output, int sitemapCount)
throws IOException {
String now = w3dtfFormat.format(new Date());
String now = w3dtfFormat.format(Instant.now());
output.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
output

View File

@@ -12,7 +12,8 @@ import static org.dspace.discovery.indexobject.ItemIndexFactoryImpl.STATUS_FIELD
import java.io.IOException;
import java.sql.SQLException;
import java.util.Calendar;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -98,7 +99,8 @@ public class SolrDatabaseResyncCli extends DSpaceRunnable<SolrDatabaseResyncCliS
private void performStatusUpdate(Context context) throws SearchServiceException, SolrServerException, IOException {
SolrQuery solrQuery = new SolrQuery();
solrQuery.setQuery(STATUS_FIELD + ":" + STATUS_FIELD_PREDB);
solrQuery.setQuery("*:*");
solrQuery.addFilterQuery(STATUS_FIELD + ":" + STATUS_FIELD_PREDB);
solrQuery.addFilterQuery(SearchUtils.RESOURCE_TYPE_FIELD + ":" + IndexableItem.TYPE);
String dateRangeFilter = SearchUtils.LAST_INDEXED_FIELD + ":[* TO " + maxTime + "]";
logDebugAndOut("Date range filter used; " + dateRangeFilter);
@@ -166,11 +168,11 @@ public class SolrDatabaseResyncCli extends DSpaceRunnable<SolrDatabaseResyncCliS
}
private String getMaxTime() {
Calendar cal = Calendar.getInstance();
Instant now = Instant.now();
if (timeUntilReindex > 0) {
cal.add(Calendar.MILLISECOND, -timeUntilReindex);
now = now.minus(timeUntilReindex, ChronoUnit.MILLIS);
}
return SolrUtils.getDateFormatter().format(cal.getTime());
return SolrUtils.getDateFormatter().format(now);
}
private int getTimeUntilReindex() {

View File

@@ -7,11 +7,13 @@
*/
package org.dspace.app.statistics;
import static java.time.temporal.TemporalAdjusters.firstDayOfMonth;
import static java.time.temporal.TemporalAdjusters.lastDayOfMonth;
import java.io.File;
import java.io.FileInputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Properties;
import org.apache.commons.cli.CommandLine;
@@ -35,14 +37,14 @@ import org.dspace.services.factory.DSpaceServicesFactory;
public class CreateStatReport {
/**
* Current date and time
* Current date
*/
private static Calendar calendar = null;
private static LocalDate calendar = null;
/**
* Reporting start date and time
* Reporting start date
*/
private static Calendar reportStartDate = null;
private static LocalDate reportStartDate = null;
/**
* Path of log directory
@@ -84,22 +86,22 @@ public class CreateStatReport {
FileInputStream fis = new java.io.FileInputStream(new File(configFile));
Properties config = new Properties();
config.load(fis);
int startMonth = 0;
int startMonth = 1;
int startYear = 2005;
try {
startYear = Integer.parseInt(config.getProperty("start.year", "1").trim());
startYear = Integer.parseInt(config.getProperty("start.year", "2005").trim());
} catch (NumberFormatException nfe) {
System.err.println("start.year is incorrectly set in dstat.cfg. Must be a number (e.g. 2005).");
System.exit(0);
}
try {
startMonth = Integer.parseInt(config.getProperty("start.month", "2005").trim());
startMonth = Integer.parseInt(config.getProperty("start.month", "1").trim());
} catch (NumberFormatException nfe) {
System.err.println("start.month is incorrectly set in dstat.cfg. Must be a number between 1 and 12.");
System.exit(0);
}
reportStartDate = new GregorianCalendar(startYear, startMonth - 1, 1);
calendar = new GregorianCalendar();
reportStartDate = LocalDate.of(startYear, startMonth, 1);
calendar = LocalDate.now();
// create context as super user
context = new Context();
@@ -168,25 +170,18 @@ public class CreateStatReport {
String myConfigFile = null;
boolean myLookUp = false;
Calendar start = new GregorianCalendar(calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
Date myStartDate = start.getTime();
Calendar end = new GregorianCalendar(calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
Date myEndDate = end.getTime();
LocalDate start = calendar.with(firstDayOfMonth());
LocalDate end = calendar.with(lastDayOfMonth());
StringBuilder myOutFile = new StringBuilder(outputLogDirectory);
myOutFile.append(outputPrefix);
myOutFile.append(calendar.get(Calendar.YEAR));
myOutFile.append(calendar.getYear());
myOutFile.append("-");
myOutFile.append(calendar.get(Calendar.MONTH) + 1);
myOutFile.append(calendar.getMonth());
myOutFile.append(outputSuffix);
LogAnalyser
.processLogs(context, myLogDir, myFileTemplate, myConfigFile, myOutFile.toString(), myStartDate, myEndDate,
.processLogs(context, myLogDir, myFileTemplate, myConfigFile, myOutFile.toString(), start, end,
myLookUp);
}
@@ -204,21 +199,19 @@ public class CreateStatReport {
String myLogDir = null;
String myFileTemplate = null;
String myConfigFile = null;
Date myStartDate = null;
Date myEndDate = null;
boolean myLookUp = false;
StringBuilder myOutFile = new StringBuilder(outputLogDirectory);
myOutFile.append(outputPrefix);
myOutFile.append(calendar.get(Calendar.YEAR));
myOutFile.append(calendar.getYear());
myOutFile.append("-");
myOutFile.append(calendar.get(Calendar.MONTH) + 1);
myOutFile.append(calendar.getMonth());
myOutFile.append("-");
myOutFile.append(calendar.get(Calendar.DAY_OF_MONTH));
myOutFile.append(calendar.getDayOfMonth());
myOutFile.append(outputSuffix);
LogAnalyser
.processLogs(context, myLogDir, myFileTemplate, myConfigFile, myOutFile.toString(), myStartDate, myEndDate,
.processLogs(context, myLogDir, myFileTemplate, myConfigFile, myOutFile.toString(), null, null,
myLookUp);
}
@@ -239,34 +232,25 @@ public class CreateStatReport {
String myConfigFile = null;
boolean myLookUp = false;
Calendar reportEndDate = new GregorianCalendar(calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
LocalDate reportEndDate = calendar.with(lastDayOfMonth());
Calendar currentMonth = (Calendar) reportStartDate.clone();
while (currentMonth.before(reportEndDate)) {
LocalDate currentMonth = reportStartDate;
while (currentMonth.isBefore(reportEndDate)) {
Calendar start = new GregorianCalendar(currentMonth.get(Calendar.YEAR),
currentMonth.get(Calendar.MONTH),
currentMonth.getActualMinimum(Calendar.DAY_OF_MONTH));
Date myStartDate = start.getTime();
Calendar end = new GregorianCalendar(currentMonth.get(Calendar.YEAR),
currentMonth.get(Calendar.MONTH),
currentMonth.getActualMaximum(Calendar.DAY_OF_MONTH));
Date myEndDate = end.getTime();
LocalDate start = currentMonth.with(firstDayOfMonth());
LocalDate end = currentMonth.with(lastDayOfMonth());
StringBuilder myOutFile = new StringBuilder(outputLogDirectory);
myOutFile.append(outputPrefix);
myOutFile.append(currentMonth.get(Calendar.YEAR));
myOutFile.append(currentMonth.getYear());
myOutFile.append("-");
myOutFile.append(currentMonth.get(Calendar.MONTH) + 1);
myOutFile.append(currentMonth.getMonth());
myOutFile.append(outputSuffix);
LogAnalyser.processLogs(context, myLogDir, myFileTemplate, myConfigFile, myOutFile.toString(), myStartDate,
myEndDate, myLookUp);
LogAnalyser.processLogs(context, myLogDir, myFileTemplate, myConfigFile, myOutFile.toString(), start,
end, myLookUp);
currentMonth.add(Calendar.MONTH, 1);
currentMonth = currentMonth.plus(1, ChronoUnit.MONTHS);
}
}
@@ -286,20 +270,20 @@ public class CreateStatReport {
StringBuilder myInput = new StringBuilder(outputLogDirectory);
myInput.append(inputPrefix);
myInput.append(calendar.get(Calendar.YEAR));
myInput.append(calendar.getYear());
myInput.append("-");
myInput.append(calendar.get(Calendar.MONTH) + 1);
myInput.append(calendar.getMonth());
myInput.append("-");
myInput.append(calendar.get(Calendar.DAY_OF_MONTH));
myInput.append(calendar.getDayOfMonth());
myInput.append(outputSuffix);
StringBuilder myOutput = new StringBuilder(outputReportDirectory);
myOutput.append(outputPrefix);
myOutput.append(calendar.get(Calendar.YEAR));
myOutput.append(calendar.getYear());
myOutput.append("-");
myOutput.append(calendar.get(Calendar.MONTH) + 1);
myOutput.append(calendar.getMonth());
myOutput.append("-");
myOutput.append(calendar.get(Calendar.DAY_OF_MONTH));
myOutput.append(calendar.getDayOfMonth());
myOutput.append(".");
myOutput.append(myFormat);
@@ -321,32 +305,30 @@ public class CreateStatReport {
String myFormat = "html";
String myMap = null;
Calendar reportEndDate = new GregorianCalendar(calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
LocalDate reportEndDate = calendar.with(lastDayOfMonth());
Calendar currentMonth = (Calendar) reportStartDate.clone();
LocalDate currentMonth = reportStartDate;
while (currentMonth.before(reportEndDate)) {
while (currentMonth.isBefore(reportEndDate)) {
StringBuilder myInput = new StringBuilder(outputLogDirectory);
myInput.append(inputPrefix);
myInput.append(currentMonth.get(Calendar.YEAR));
myInput.append(currentMonth.getYear());
myInput.append("-");
myInput.append(currentMonth.get(Calendar.MONTH) + 1);
myInput.append(currentMonth.getMonth());
myInput.append(outputSuffix);
StringBuilder myOutput = new StringBuilder(outputReportDirectory);
myOutput.append(outputPrefix);
myOutput.append(currentMonth.get(Calendar.YEAR));
myOutput.append(currentMonth.getYear());
myOutput.append("-");
myOutput.append(currentMonth.get(Calendar.MONTH) + 1);
myOutput.append(currentMonth.getMonth());
myOutput.append(".");
myOutput.append(myFormat);
ReportGenerator.processReport(context, myFormat, myInput.toString(), myOutput.toString(), myMap);
currentMonth.add(Calendar.MONTH, 1);
currentMonth = currentMonth.plus(1, ChronoUnit.MONTHS);
}
}
@@ -365,16 +347,16 @@ public class CreateStatReport {
StringBuilder myInput = new StringBuilder(outputLogDirectory);
myInput.append(inputPrefix);
myInput.append(calendar.get(Calendar.YEAR));
myInput.append(calendar.getYear());
myInput.append("-");
myInput.append(calendar.get(Calendar.MONTH) + 1);
myInput.append(calendar.getMonth());
myInput.append(outputSuffix);
StringBuilder myOutput = new StringBuilder(outputReportDirectory);
myOutput.append(outputPrefix);
myOutput.append(calendar.get(Calendar.YEAR));
myOutput.append(calendar.getYear());
myOutput.append("-");
myOutput.append(calendar.get(Calendar.MONTH) + 1);
myOutput.append(calendar.getMonth());
myOutput.append(".");
myOutput.append(myFormat);

View File

@@ -13,8 +13,8 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
@@ -50,12 +50,12 @@ public class HTMLReport implements Report {
/**
* start date for report
*/
private Date start = null;
private LocalDate start = null;
/**
* end date for report
*/
private Date end = null;
private LocalDate end = null;
/**
* the output file to which to write aggregation data
@@ -190,8 +190,8 @@ public class HTMLReport implements Report {
* @param start the start date for the report
*/
@Override
public void setStartDate(Date start) {
this.start = (start == null ? null : new Date(start.getTime()));
public void setStartDate(LocalDate start) {
this.start = start;
}
@@ -201,8 +201,8 @@ public class HTMLReport implements Report {
* @param end the end date for the report
*/
@Override
public void setEndDate(Date end) {
this.end = (end == null ? null : new Date(end.getTime()));
public void setEndDate(LocalDate end) {
this.end = end;
}

View File

@@ -14,18 +14,16 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -265,7 +263,7 @@ public class LogAnalyser {
/**
* process timing clock
*/
private static Calendar startTime = null;
private static Instant startTime = null;
/////////////////////////
// command line options
@@ -298,22 +296,22 @@ public class LogAnalyser {
/**
* the starting date of the report
*/
private static Date startDate = null;
private static LocalDate startDate = null;
/**
* the end date of the report
*/
private static Date endDate = null;
private static LocalDate endDate = null;
/**
* the starting date of the report as obtained from the log files
*/
private static Date logStartDate = null;
private static LocalDate logStartDate = null;
/**
* the end date of the report as obtained from the log files
*/
private static Date logEndDate = null;
private static LocalDate logEndDate = null;
/**
* Default constructor
@@ -331,7 +329,7 @@ public class LogAnalyser {
public static void main(String[] argv)
throws Exception, SQLException {
// first, start the processing clock
startTime = new GregorianCalendar();
startTime = Instant.now();
// create context as super user
Context context = new Context();
@@ -342,8 +340,8 @@ public class LogAnalyser {
String myFileTemplate = null;
String myConfigFile = null;
String myOutFile = null;
Date myStartDate = null;
Date myEndDate = null;
LocalDate myStartDate = null;
LocalDate myEndDate = null;
boolean myLookUp = false;
// Define command line options.
@@ -434,14 +432,14 @@ public class LogAnalyser {
*/
public static String processLogs(Context context, String myLogDir,
String myFileTemplate, String myConfigFile,
String myOutFile, Date myStartDate,
Date myEndDate, boolean myLookUp)
String myOutFile, LocalDate myStartDate,
LocalDate myEndDate, boolean myLookUp)
throws IOException, SQLException, SearchServiceException {
// FIXME: perhaps we should have all parameters and aggregators put
// together in a single aggregating object
// if the timer has not yet been started, then start it
startTime = new GregorianCalendar();
startTime = Instant.now();
//instantiate aggregators
actionAggregator = new HashMap<>();
@@ -658,7 +656,7 @@ public class LogAnalyser {
*/
public static void setParameters(String myLogDir, String myFileTemplate,
String myConfigFile, String myOutFile,
Date myStartDate, Date myEndDate,
LocalDate myStartDate, LocalDate myEndDate,
boolean myLookUp) {
if (myLogDir != null) {
@@ -676,11 +674,11 @@ public class LogAnalyser {
}
if (myStartDate != null) {
startDate = new Date(myStartDate.getTime());
startDate = myStartDate;
}
if (myEndDate != null) {
endDate = new Date(myEndDate.getTime());
endDate = myEndDate;
}
if (myOutFile != null) {
@@ -722,18 +720,17 @@ public class LogAnalyser {
summary.append("service_name=").append(name).append("\n");
// output the date information if necessary
SimpleDateFormat sdf = new SimpleDateFormat("dd'/'MM'/'yyyy");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd'/'MM'/'yyyy");
if (startDate != null) {
summary.append("start_date=").append(sdf.format(startDate)).append("\n");
summary.append("start_date=").append(formatter.format(startDate)).append("\n");
} else if (logStartDate != null) {
summary.append("start_date=").append(sdf.format(logStartDate)).append("\n");
summary.append("start_date=").append(formatter.format(logStartDate)).append("\n");
}
if (endDate != null) {
summary.append("end_date=").append(sdf.format(endDate)).append("\n");
summary.append("end_date=").append(formatter.format(endDate)).append("\n");
} else if (logEndDate != null) {
summary.append("end_date=").append(sdf.format(logEndDate)).append("\n");
summary.append("end_date=").append(formatter.format(logEndDate)).append("\n");
}
// write out the archive stats
@@ -813,8 +810,7 @@ public class LogAnalyser {
}
// insert the analysis processing time information
Calendar endTime = new GregorianCalendar();
long timeInMillis = (endTime.getTimeInMillis() - startTime.getTimeInMillis());
long timeInMillis = Instant.now().toEpochMilli() - startTime.toEpochMilli();
summary.append("analysis_process_time=")
.append(Long.toString(timeInMillis / 1000)).append("\n");
@@ -1072,13 +1068,13 @@ public class LogAnalyser {
* @return a date object containing the date, with the time set to
* 00:00:00
*/
public static Date parseDate(String date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy'-'MM'-'dd");
Date parsedDate = null;
public static LocalDate parseDate(String date) {
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
LocalDate parsedDate = null;
try {
parsedDate = sdf.parse(date);
} catch (ParseException e) {
parsedDate = LocalDate.parse(date, formatter);
} catch (DateTimeParseException e) {
System.out.println("The date is not in the correct format");
System.exit(0);
}
@@ -1092,11 +1088,8 @@ public class LogAnalyser {
* @param date the date to be converted
* @return A string of the form YYYY-MM-DD
*/
public static String unParseDate(Date date) {
// Use SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy'-'MM'-'dd'T'hh:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
return sdf.format(date);
public static String unParseDate(LocalDate date) {
return DateTimeFormatter.ISO_LOCAL_DATE.format(date);
}
@@ -1238,8 +1231,8 @@ public class LogAnalyser {
}
accessionedQuery.append("]");
discoverQuery.addFilterQueries(accessionedQuery.toString());
discoverQuery.addFilterQueries("withdrawn: false");
discoverQuery.addFilterQueries("archived: true");
discoverQuery.addFilterQueries("withdrawn:false");
discoverQuery.addFilterQueries("archived:true");
return (int) SearchUtils.getSearchService().search(context, discoverQuery).getTotalSearchResults();
}

View File

@@ -7,7 +7,7 @@
*/
package org.dspace.app.statistics;
import java.util.Date;
import java.time.LocalDate;
/**
* This class represents a single log file line and the operations that can be
@@ -22,7 +22,7 @@ public class LogLine {
/**
* the date of the log file line
*/
private Date date = null;
private LocalDate date = null;
/**
* the level of the log line type
@@ -47,7 +47,7 @@ public class LogLine {
/**
* constructor to create new statistic
*/
LogLine(Date date, String level, String user, String action, String params) {
LogLine(LocalDate date, String level, String user, String action, String params) {
this.date = date;
this.level = level;
this.user = user;
@@ -60,8 +60,8 @@ public class LogLine {
*
* @return the date of this log line
*/
public Date getDate() {
return this.date == null ? null : new Date(this.date.getTime());
public LocalDate getDate() {
return this.date;
}
@@ -108,12 +108,12 @@ public class LogLine {
/**
* find out if this log file line is before the given date
*
* @param date the date to be compared to
* @param compareDate the date to be compared to
* @return true if the line is before the given date, false if not
*/
public boolean beforeDate(Date date) {
if (date != null) {
return (date.compareTo(this.date) >= 0);
public boolean beforeDate(LocalDate compareDate) {
if (compareDate != null) {
return this.date.isBefore(compareDate);
}
return false;
}
@@ -122,12 +122,12 @@ public class LogLine {
/**
* find out if this log file line is after the given date
*
* @param date the date to be compared to
* @param compareDate the date to be compared to
* @return true if the line is after the given date, false if not
*/
public boolean afterDate(Date date) {
if (date != null) {
return (date.compareTo(this.date) <= 0);
public boolean afterDate(LocalDate compareDate) {
if (compareDate != null) {
return this.date.isAfter(compareDate);
}
return false;
}

View File

@@ -7,7 +7,7 @@
*/
package org.dspace.app.statistics;
import java.util.Date;
import java.time.LocalDate;
/**
* Sn interface to a generic report generating
@@ -120,12 +120,12 @@ public interface Report {
*
* @param start the start date for the report
*/
public abstract void setStartDate(Date start);
public abstract void setStartDate(LocalDate start);
/**
* set the end date for the report
*
* @param end the end date for the report
*/
public abstract void setEndDate(Date end);
public abstract void setEndDate(LocalDate end);
}

View File

@@ -13,12 +13,11 @@ import java.io.FileReader;
import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -132,12 +131,12 @@ public class ReportGenerator {
/**
* start date of this report
*/
private static Date startDate = null;
private static LocalDate startDate = null;
/**
* end date of this report
*/
private static Date endDate = null;
private static LocalDate endDate = null;
/**
* the time taken to build the aggregation file from the log
@@ -175,7 +174,7 @@ public class ReportGenerator {
/**
* process timing clock
*/
private static Calendar startTime = null;
private static Instant startTime = null;
/**
* a map from log file action to human readable action
@@ -326,7 +325,7 @@ public class ReportGenerator {
public static void processReport(Context context, Report report,
String myInput)
throws Exception, SQLException {
startTime = new GregorianCalendar();
startTime = Instant.now();
/** instantiate aggregators */
actionAggregator = new HashMap<>();
@@ -492,8 +491,7 @@ public class ReportGenerator {
report.addBlock(levels);
// get the display processing time information
Calendar endTime = new GregorianCalendar();
long timeInMillis = (endTime.getTimeInMillis() - startTime.getTimeInMillis());
long timeInMillis = Instant.now().toEpochMilli() - startTime.toEpochMilli();
int outputProcessTime = (int) (timeInMillis / 1000);
// prepare the processing information statistics
@@ -666,7 +664,7 @@ public class ReportGenerator {
// first initialise a date format object to do our date processing
// if necessary
SimpleDateFormat sdf = new SimpleDateFormat("dd'/'MM'/'yyyy");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd'/'MM'/'yyyy");
// FIXME: although this works, it is not very elegant
// loop through the aggregator file and read in the values
@@ -738,9 +736,9 @@ public class ReportGenerator {
} else if ("service_name".equals(section)) {
name = value;
} else if ("start_date".equals(section)) {
startDate = sdf.parse(value);
startDate = LocalDate.parse(value, formatter);
} else if ("end_date".equals(section)) {
endDate = sdf.parse(value);
endDate = LocalDate.parse(value, formatter);
} else if ("analysis_process_time".equals(section)) {
processTime = Integer.parseInt(value);
} else if ("general_summary".equals(section)) {

View File

@@ -9,19 +9,20 @@ package org.dspace.app.statistics;
import java.io.File;
import java.io.FilenameFilter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.time.DateUtils;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
@@ -35,7 +36,7 @@ public class StatisticsLoader {
private static StatsFile generalAnalysis = null;
private static StatsFile generalReport = null;
private static Date lastLoaded = null;
private static Instant lastLoaded = null;
private static int fileCount = 0;
private static Pattern analysisMonthlyPattern;
@@ -43,8 +44,8 @@ public class StatisticsLoader {
private static Pattern reportMonthlyPattern;
private static Pattern reportGeneralPattern;
private static ThreadLocal<DateFormat> monthlySDF;
private static ThreadLocal<DateFormat> generalSDF;
private static ThreadLocal<DateTimeFormatter> monthlySDF;
private static ThreadLocal<DateTimeFormatter> generalSDF;
// one time initialisation of the regex patterns and formatters we will use
static {
@@ -53,17 +54,17 @@ public class StatisticsLoader {
reportMonthlyPattern = Pattern.compile("report-([0-9][0-9][0-9][0-9]-[0-9]+)\\.html");
reportGeneralPattern = Pattern.compile("report-general-([0-9]+-[0-9]+-[0-9]+)\\.html");
monthlySDF = new ThreadLocal<DateFormat>() {
monthlySDF = new ThreadLocal<DateTimeFormatter>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy'-'M");
protected DateTimeFormatter initialValue() {
return DateTimeFormatter.ofPattern("yyyy'-'M");
}
};
generalSDF = new ThreadLocal<DateFormat>() {
generalSDF = new ThreadLocal<DateTimeFormatter>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy'-'M'-'dd");
protected DateTimeFormatter initialValue() {
return DateTimeFormatter.ofPattern("yyyy'-'M'-'dd");
}
};
}
@@ -78,7 +79,7 @@ public class StatisticsLoader {
*
* @return array of dates
*/
public static Date[] getMonthlyReportDates() {
public static LocalDate[] getMonthlyReportDates() {
return sortDatesDescending(getDatesFromMap(monthlyReports));
}
@@ -87,7 +88,7 @@ public class StatisticsLoader {
*
* @return array of dates
*/
public static Date[] getMonthlyAnalysisDates() {
public static LocalDate[] getMonthlyAnalysisDates() {
return sortDatesDescending(getDatesFromMap(monthlyAnalysis));
}
@@ -97,15 +98,15 @@ public class StatisticsLoader {
* @param monthlyMap map
* @return array of dates
*/
protected static Date[] getDatesFromMap(Map<String, StatsFile> monthlyMap) {
protected static LocalDate[] getDatesFromMap(Map<String, StatsFile> monthlyMap) {
Set<String> keys = monthlyMap.keySet();
Date[] dates = new Date[keys.size()];
LocalDate[] dates = new LocalDate[keys.size()];
int i = 0;
for (String date : keys) {
try {
dates[i] = monthlySDF.get().parse(date);
} catch (ParseException pe) {
// ignore
dates[i] = YearMonth.parse(date, monthlySDF.get()).atDay(1);
} catch (DateTimeParseException pe) {
//ignore
}
i++;
@@ -120,19 +121,19 @@ public class StatisticsLoader {
* @param dates array of dates
* @return sorted dates.
*/
protected static Date[] sortDatesDescending(Date[] dates) {
Arrays.sort(dates, new Comparator<Date>() {
protected static LocalDate[] sortDatesDescending(LocalDate[] dates) {
Arrays.sort(dates, new Comparator<LocalDate>() {
@Override
public int compare(Date d1, Date d2) {
public int compare(LocalDate d1, LocalDate d2) {
if (d1 == null && d2 == null) {
return 0;
} else if (d1 == null) {
return -1;
} else if (d2 == null) {
return 1;
} else if (d1.before(d2)) {
} else if (d1.isBefore(d2)) {
return 1;
} else if (d2.before(d1)) {
} else if (d2.isBefore(d1)) {
return -1;
}
@@ -203,7 +204,7 @@ public class StatisticsLoader {
StatisticsLoader.loadFileList(fileList);
} else if (lastLoaded == null) {
StatisticsLoader.loadFileList(fileList);
} else if (DateUtils.addHours(lastLoaded, 1).before(new Date())) {
} else if (Instant.now().isAfter(lastLoaded.plus(1, ChronoUnit.HOURS))) {
StatisticsLoader.loadFileList(fileList);
}
}
@@ -256,7 +257,7 @@ public class StatisticsLoader {
statsFile = makeStatsFile(thisFile, analysisGeneralPattern, generalSDF.get());
if (statsFile != null) {
// If it is, ensure that we are pointing to the most recent file
if (newGeneralAnalysis == null || statsFile.date.after(newGeneralAnalysis.date)) {
if (newGeneralAnalysis == null || statsFile.date.isAfter(newGeneralAnalysis.date)) {
newGeneralAnalysis = statsFile;
}
}
@@ -268,7 +269,7 @@ public class StatisticsLoader {
statsFile = makeStatsFile(thisFile, reportGeneralPattern, generalSDF.get());
if (statsFile != null) {
// If it is, ensure that we are pointing to the most recent file
if (newGeneralReport == null || statsFile.date.after(newGeneralReport.date)) {
if (newGeneralReport == null || statsFile.date.isAfter(newGeneralReport.date)) {
newGeneralReport = statsFile;
}
}
@@ -281,7 +282,7 @@ public class StatisticsLoader {
monthlyReports = newMonthlyReports;
generalAnalysis = newGeneralAnalysis;
generalReport = newGeneralReport;
lastLoaded = new Date();
lastLoaded = Instant.now();
}
/**
@@ -292,10 +293,10 @@ public class StatisticsLoader {
*
* @param thisFile file
* @param thisPattern pattern
* @param sdf date format
* @param formatter date formatter
* @return StatsFile
*/
private static StatsFile makeStatsFile(File thisFile, Pattern thisPattern, DateFormat sdf) {
private static StatsFile makeStatsFile(File thisFile, Pattern thisPattern, DateTimeFormatter formatter) {
Matcher matcher = thisPattern.matcher(thisFile.getName());
if (matcher.matches()) {
StatsFile sf = new StatsFile();
@@ -304,8 +305,8 @@ public class StatisticsLoader {
sf.dateStr = matcher.group(1).trim();
try {
sf.date = sdf.parse(sf.dateStr);
} catch (ParseException e) {
sf.date = LocalDate.parse(sf.dateStr, formatter);
} catch (DateTimeParseException e) {
// ignore
}
@@ -333,7 +334,7 @@ public class StatisticsLoader {
private static class StatsFile {
File file;
String path;
Date date;
LocalDate date;
String dateStr;
}

View File

@@ -13,6 +13,7 @@ import java.util.UUID;
import org.apache.logging.log4j.Logger;
import org.apache.solr.client.solrj.SolrServerException;
import org.dspace.content.Item;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.external.model.ExternalDataObject;
@@ -137,4 +138,16 @@ public abstract class SolrSuggestionProvider implements SuggestionProvider {
*/
protected abstract boolean isExternalDataObjectPotentiallySuggested(Context context,
ExternalDataObject externalDataObject);
/**
* Save a List of ImportRecord into Solr.
* ImportRecord will be translate into a SolrDocument by the method translateImportRecordToSolrDocument.
*
* @param context the DSpace Context
* @param item a DSpace Item
* @throws SolrServerException
* @throws IOException
*/
public abstract void importRecords(Context context, Item item) throws Exception;
}

View File

@@ -7,8 +7,10 @@
*/
package org.dspace.app.suggestion;
import org.dspace.app.suggestion.scorer.EvidenceScorer;
/**
* This DTO class is returned by an {@link org.dspace.app.suggestion.openaire.EvidenceScorer} to model the concept of
* This DTO class is returned by an {@link EvidenceScorer} to model the concept of
* an evidence / fact that has been used to evaluate the precision of a suggestion increasing or decreasing the score
* of the suggestion.
*

View File

@@ -5,7 +5,7 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openaire;
package org.dspace.app.suggestion.loader;
import static org.dspace.app.suggestion.SuggestionUtils.getAllEntriesByMetadatum;
import static org.dspace.app.suggestion.SuggestionUtils.getFirstEntryByMetadatum;
@@ -19,6 +19,8 @@ import org.apache.solr.client.solrj.SolrServerException;
import org.dspace.app.suggestion.SolrSuggestionProvider;
import org.dspace.app.suggestion.Suggestion;
import org.dspace.app.suggestion.SuggestionEvidence;
import org.dspace.app.suggestion.scorer.AuthorNamesScorer;
import org.dspace.app.suggestion.scorer.EvidenceScorer;
import org.dspace.content.Item;
import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.core.Context;
@@ -28,23 +30,23 @@ import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Class responsible to load and manage ImportRecords from OpenAIRE
* Class responsible to load and manage ImportRecords from external sources
*
* @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
*
*/
public class PublicationLoader extends SolrSuggestionProvider {
private List<String> names;
protected List<String> names;
private ExternalDataProvider primaryProvider;
protected ExternalDataProvider primaryProvider;
private List<ExternalDataProvider> otherProviders;
protected List<ExternalDataProvider> otherProviders;
@Autowired
private ConfigurationService configurationService;
protected ConfigurationService configurationService;
private List<EvidenceScorer> pipeline;
protected List<EvidenceScorer> pipeline;
public void setPrimaryProvider(ExternalDataProvider primaryProvider) {
this.primaryProvider = primaryProvider;
@@ -65,8 +67,8 @@ public class PublicationLoader extends SolrSuggestionProvider {
/**
* This method filter a list of ImportRecords using a pipeline of AuthorNamesApprover
* and return a filtered list of ImportRecords.
*
* @see org.dspace.app.suggestion.openaire.AuthorNamesScorer
*
* @see AuthorNamesScorer
* @param researcher the researcher Item
* @param importRecords List of import record
* @return a list of filtered import records
@@ -103,8 +105,9 @@ public class PublicationLoader extends SolrSuggestionProvider {
* @throws SolrServerException
* @throws IOException
*/
public void importAuthorRecords(Context context, Item researcher)
throws SolrServerException, IOException {
@Override
public void importRecords(Context context, Item researcher)
throws Exception {
int offset = 0;
int limit = 10;
int loaded = limit;
@@ -132,18 +135,20 @@ public class PublicationLoader extends SolrSuggestionProvider {
* @return Suggestion
*/
private Suggestion translateImportRecordToSuggestion(Item item, ExternalDataObject record) {
String openAireId = record.getId();
Suggestion suggestion = new Suggestion(getSourceName(), item, openAireId);
String recordId = record.getId();
Suggestion suggestion = new Suggestion(getSourceName(), item, recordId);
suggestion.setDisplay(getFirstEntryByMetadatum(record, "dc", "title", null));
suggestion.getMetadata().add(
new MetadataValueDTO("dc", "title", null, null, getFirstEntryByMetadatum(record, "dc", "title", null)));
new MetadataValueDTO("dc", "title", null, null, getFirstEntryByMetadatum(record, "dc", "title", null)));
suggestion.getMetadata().add(new MetadataValueDTO("dc", "date", "issued", null,
getFirstEntryByMetadatum(record, "dc", "date", "issued")));
getFirstEntryByMetadatum(record, "dc", "date", "issued")));
suggestion.getMetadata().add(new MetadataValueDTO("dc", "description", "abstract", null,
getFirstEntryByMetadatum(record, "dc", "description", "abstract")));
getFirstEntryByMetadatum(record, "dc", "description",
"abstract")));
suggestion.setExternalSourceUri(configurationService.getProperty("dspace.server.url")
+ "/api/integration/externalsources/" + primaryProvider.getSourceIdentifier() + "/entryValues/"
+ openAireId);
+ "/api/integration/externalsources/" +
primaryProvider.getSourceIdentifier() + "/entryValues/"
+ recordId);
for (String o : getAllEntriesByMetadatum(record, "dc", "source", null)) {
suggestion.getMetadata().add(new MetadataValueDTO("dc", "source", null, null, o));
}
@@ -162,10 +167,10 @@ public class PublicationLoader extends SolrSuggestionProvider {
}
/**
* Load metadata from OpenAIRE using the import service. The service use the value
* get from metadata key defined in class level variable names as author to query OpenAIRE.
*
* @see org.dspace.importer.external.openaire.service.OpenAireImportMetadataSourceServiceImpl
* Load metadata from external source using the import service. The service use the value
* get from metadata key defined in class level variable names as author to query external source.
*
* @see org.dspace.importer.external.service.AbstractImportMetadataSourceService
* @param searchValues query
* @param researcher item to extract metadata from
* @param limit for pagination purpose
@@ -173,7 +178,7 @@ public class PublicationLoader extends SolrSuggestionProvider {
* @return list of ImportRecord
*/
private List<ExternalDataObject> getImportRecords(List<String> searchValues,
Item researcher, int offset, int limit) {
Item researcher, int offset, int limit) {
List<ExternalDataObject> matchingRecords = new ArrayList<>();
for (String searchValue : searchValues) {
matchingRecords.addAll(
@@ -205,7 +210,7 @@ public class PublicationLoader extends SolrSuggestionProvider {
/**
* Check if the ImportRecord is already present in the list.
* The comparison is made on the value of metadatum with key 'dc.identifier.other'
*
*
* @param dto An importRecord instance
* @param importRecords a list of importRecord
* @return true if dto is already present in importRecords, false otherwise
@@ -230,7 +235,7 @@ public class PublicationLoader extends SolrSuggestionProvider {
* @param researcher DSpace item
* @return list of metadata values
*/
private List<String> searchMetadataValues(Item researcher) {
public List<String> searchMetadataValues(Item researcher) {
List<String> authors = new ArrayList<String>();
for (String name : names) {
String value = itemService.getMetadata(researcher, name);
@@ -247,7 +252,8 @@ public class PublicationLoader extends SolrSuggestionProvider {
return true;
} else if (otherProviders != null) {
return otherProviders.stream()
.anyMatch(x -> StringUtils.equals(externalDataObject.getSource(), x.getSourceIdentifier()));
.anyMatch(
x -> StringUtils.equals(externalDataObject.getSource(), x.getSourceIdentifier()));
} else {
return false;
}

View File

@@ -1,115 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openaire;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.cli.ParseException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.content.Item;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.discovery.SearchUtils;
import org.dspace.discovery.utils.DiscoverQueryBuilder;
import org.dspace.discovery.utils.parameter.QueryBuilderSearchFilter;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.sort.SortOption;
import org.dspace.utils.DSpace;
/**
* Runner responsible to import metadata about authors from OpenAIRE to Solr.
* This runner works in two ways:
* If -s parameter with a valid UUID is received, then the specific researcher
* with this UUID will be used.
* Invocation without any parameter results in massive import, processing all
* authors registered in DSpace.
*
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
*/
public class PublicationLoaderRunnable
extends DSpaceRunnable<PublicationLoaderScriptConfiguration<PublicationLoaderRunnable>> {
private static final Logger LOGGER = LogManager.getLogger();
private PublicationLoader oairePublicationLoader = null;
protected Context context;
protected String profile;
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public PublicationLoaderScriptConfiguration<PublicationLoaderRunnable> getScriptConfiguration() {
PublicationLoaderScriptConfiguration configuration = new DSpace().getServiceManager()
.getServiceByName("import-openaire-suggestions", PublicationLoaderScriptConfiguration.class);
return configuration;
}
@Override
public void setup() throws ParseException {
oairePublicationLoader = new DSpace().getServiceManager().getServiceByName(
"OpenairePublicationLoader", PublicationLoader.class);
profile = commandLine.getOptionValue("s");
if (profile == null) {
LOGGER.info("No argument for -s, process all profiles");
} else {
LOGGER.info("Process eperson item with UUID {}", profile);
}
}
@Override
public void internalRun() throws Exception {
context = new Context();
Iterator<Item> researchers = getResearchers(profile);
while (researchers.hasNext()) {
Item researcher = researchers.next();
oairePublicationLoader.importAuthorRecords(context, researcher);
}
}
/**
* Get the Item(s) which map a researcher from Solr. If the uuid is specified,
* the researcher with this UUID will be chosen. If the uuid doesn't match any
* researcher, the method returns an empty array list. If uuid is null, all
* research will be return.
*
* @param profileUUID uuid of the researcher. If null, all researcher will be
* returned.
* @return the researcher with specified UUID or all researchers
*/
@SuppressWarnings("rawtypes")
private Iterator<Item> getResearchers(String profileUUID) {
SearchService searchService = new DSpace().getSingletonService(SearchService.class);
DiscoverQueryBuilder queryBuilder = SearchUtils.getQueryBuilder();
List<QueryBuilderSearchFilter> filters = new ArrayList<>();
String query = "*:*";
if (profileUUID != null) {
query = "search.resourceid:" + profileUUID;
}
try {
DiscoverQuery discoverQuery = queryBuilder.buildQuery(context, null,
SearchUtils.getDiscoveryConfigurationByName("person"),
query, filters,
"Item", 10, Long.getLong("0"), null, SortOption.DESCENDING);
return searchService.iteratorSearch(context, null, discoverQuery);
} catch (SearchServiceException e) {
LOGGER.error("Unable to read researcher on solr", e);
}
return null;
}
}

View File

@@ -1,36 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openaire;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.ParseException;
import org.dspace.utils.DSpace;
public class PublicationLoaderRunnableCli extends PublicationLoaderRunnable {
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public PublicationLoaderCliScriptConfiguration getScriptConfiguration() {
PublicationLoaderCliScriptConfiguration configuration = new DSpace().getServiceManager()
.getServiceByName("import-openaire-suggestions", PublicationLoaderCliScriptConfiguration.class);
return configuration;
}
@Override
public void setup() throws ParseException {
super.setup();
// in case of CLI we show the help prompt
if (commandLine.hasOption('h')) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("Import Researchers Suggestions", getScriptConfiguration().getOptions());
System.exit(0);
}
}
}

View File

@@ -1,56 +0,0 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openaire;
import org.apache.commons.cli.Options;
import org.dspace.scripts.configuration.ScriptConfiguration;
public class PublicationLoaderScriptConfiguration<T extends PublicationLoaderRunnable>
extends ScriptConfiguration<T> {
private Class<T> dspaceRunnableClass;
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;
}
/**
* Generic setter for the dspaceRunnableClass
* @param dspaceRunnableClass The dspaceRunnableClass to be set on this PublicationLoaderScriptConfiguration
*/
@Override
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
this.dspaceRunnableClass = dspaceRunnableClass;
}
/*
@Override
public boolean isAllowedToExecute(Context context) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
*/
@Override
public Options getOptions() {
if (options == null) {
Options options = new Options();
options.addOption("s", "single-researcher", true, "Single researcher UUID");
options.getOption("s").setType(String.class);
super.options = options;
}
return options;
}
}

View File

@@ -0,0 +1,59 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openalex;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.suggestion.loader.PublicationLoader;
import org.dspace.content.Item;
/**
* Implementation of {@link PublicationLoader} that retrieves metadata values
* from an OpenAlex external source.
*
* @author Adamo Fapohunda (adamo.fapohunda at 4science.com)
**/
public class OpenAlexPublicationLoader extends PublicationLoader {
/**
* Searches for metadata values related to a given researcher item.
* It first checks for "dc.identifier" metadata and builds the filter accordingly.
* If not found, it collects available metadata values to be used in the search query.
*
* @param researcher The researcher item from which metadata values are extracted.
* @return A list of search query parameters for OpenAlex.
*/
@Override
public List<String> searchMetadataValues(Item researcher) {
List<String> names = getNames();
// First, check for "dc.identifier.openalex" and build the filter if present
List<String> authorIds = names.stream()
.filter("dc.identifier.openalex"::equals)
.map(name -> itemService.getMetadata(researcher, name))
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (!authorIds.isEmpty()) {
return Collections.singletonList(StringUtils.join(authorIds, "|"));
}
// Otherwise, collect all available metadata values
return names.stream()
.map(name -> itemService.getMetadata(researcher, name))
.filter(Objects::nonNull)
.map(i -> "search_by_author=" + i)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,203 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.runnable;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.suggestion.SolrSuggestionProvider;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DCDate;
import org.dspace.content.Item;
import org.dspace.content.MetadataFieldName;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResultItemIterator;
import org.dspace.discovery.indexobject.IndexableItem;
import org.dspace.scripts.DSpaceRunnable;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.util.UUIDUtils;
import org.dspace.utils.DSpace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Runner responsible to import metadata about authors from loader to Solr. This
* runner works in two ways: If -s parameter with a valid UUID is received, then
* the specific researcher with this UUID will be used. Invocation without any
* parameter results in massive import, processing all authors registered in
* DSpace.
*
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
**/
public class PublicationLoaderRunnable
extends DSpaceRunnable<ScriptConfiguration<?>> {
private static final Logger LOGGER = LoggerFactory.getLogger(PublicationLoaderRunnable.class);
protected Context context;
protected String profile;
protected String loader;
protected String filterQuery;
private SolrSuggestionProvider publicationLoader = null;
private ConfigurationService configurationService;
private ItemService itemService;
private Integer itemLimit;
private List<SolrSuggestionProvider> providers;
/**
* Retrieves the script configuration for this runnable.
* The configuration is fetched from the DSpace service manager.
*
* @return The {@link ScriptConfiguration} instance for the import-loader-suggestions script.
*/
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public ScriptConfiguration<?> getScriptConfiguration() {
return new DSpace().getServiceManager()
.getServiceByName("import-loader-suggestions", ScriptConfiguration.class);
}
/**
* Initializes the script execution environment by setting up necessary services,
* retrieving command-line arguments, and setting default values where necessary.
*
* @throws ParseException If there is an error parsing command-line options.
*/
@Override
public void setup() throws ParseException {
configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
itemService = ContentServiceFactory.getInstance().getItemService();
providers = new DSpace().getServiceManager().getServicesByType(SolrSuggestionProvider.class);
loader = commandLine.getOptionValue("l");
profile = commandLine.getOptionValue("s");
filterQuery = !commandLine.hasOption("f") ? "" : commandLine.getOptionValue("f");
if (profile == null) {
LOGGER.info("No argument for -s, process all profile");
} else {
LOGGER.info("Process eperson item with UUID " + profile);
}
if (commandLine.hasOption("m")) {
this.itemLimit = Integer.valueOf(commandLine.getOptionValue("m"));
} else {
this.itemLimit = getDefaultLimit();
}
}
/**
* Main execution method for the script.
* It validates input parameters, retrieves researcher items, and triggers the publication import process.
*
* @throws Exception If an error occurs during execution.
*/
@Override
public void internalRun() throws Exception {
if (loader == null) {
throw new NullPointerException("loader can't be null");
}
if (profile != null && UUIDUtils.fromString(profile) == null) {
throw new IllegalArgumentException("The provided argument -s is not a valid uuid");
}
publicationLoader = getPublicationLoader(loader);
try {
context = new Context();
context.turnOffAuthorisationSystem();
DiscoverResultItemIterator researchers = findResearchers();
while (researchers.hasNext()) {
Item researcher = researchers.next();
researcher = context.reloadEntity(researcher);
publicationLoader.importRecords(context, researcher);
setLastImportMetadataValue(researcher);
context.commit();
context.uncacheEntity(researcher);
}
} finally {
context.restoreAuthSystemState();
context.complete();
}
}
/**
* Retrieves the {@link SolrSuggestionProvider} based on the provided loader name.
*
* @param loader The name of the publication loader.
* @return The corresponding {@link SolrSuggestionProvider}.
* @throws IllegalArgumentException If no provider matching the loader name is found.
*/
private SolrSuggestionProvider getPublicationLoader(String loader) {
return providers
.stream()
.filter(provider -> StringUtils.equals(provider.getSourceName(), loader))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("IllegalArgumentException: " +
"Provider for: " + loader + " couldn't be found"));
}
/**
* Get the Item(s) which map a researcher from Solr. If the uuid is specified,
* the researcher with this UUID will be chosen. If the uuid doesn't match any
* researcher, the method returns an empty array list. If uuid is null, all
* research will be return.
*
* @return the researcher with specified UUID or all researchers
*/
private DiscoverResultItemIterator findResearchers() {
DiscoverQuery discoverQuery = new DiscoverQuery();
discoverQuery.setDSpaceObjectFilter(IndexableItem.TYPE);
if (org.apache.commons.lang3.StringUtils.isNotBlank(profile)) {
discoverQuery.setQuery("search.resourceid:" + profile);
}
discoverQuery.addFilterQueries("search.resourcetype:Item");
discoverQuery.addFilterQueries("dspace.entity.type:Person");
discoverQuery.setSortField("lastModified", DiscoverQuery.SORT_ORDER.asc);
if (!filterQuery.isEmpty()) {
discoverQuery.addFilterQueries(filterQuery);
}
DiscoverResultItemIterator iterator = new DiscoverResultItemIterator(context, discoverQuery, itemLimit);
return iterator;
}
/**
* Updates the researcher's item metadata to store the last import timestamp.
*
* @param item The researcher item whose metadata should be updated.
*/
private void setLastImportMetadataValue(Item item) {
try {
item = context.reloadEntity(item);
String metadataField = String.format("dspace.%s.lastimport", loader);
String currentDate = DCDate.getCurrent().toString();
itemService.setMetadataSingleValue(context, item, new MetadataFieldName(metadataField), null, currentDate);
itemService.update(context, item);
} catch (SQLException | AuthorizeException e) {
throw new RuntimeException(e);
}
}
/**
* Retrieves the default item limit for the publication loader.
*
* @return The default item limit, as defined in the DSpace configuration.
*/
private Integer getDefaultLimit() {
return configurationService.getIntProperty("suggestion.publication-loader.max", -1);
}
}

View File

@@ -0,0 +1,57 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.runnable;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.ParseException;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.dspace.utils.DSpace;
/**
* CLI implementation of the {@link PublicationLoaderRunnable} script.
* This class extends {@link PublicationLoaderRunnable} and provides additional
* functionality specific to command-line execution.
*
* @author Adamo Fapohunda (adamo.fapohunda at 4science.com)
*/
public class PublicationLoaderRunnableCli extends PublicationLoaderRunnable {
/**
* Retrieves the script configuration associated with this CLI script.
* This method fetches the configuration from the DSpace service manager.
*
* @return The {@link ScriptConfiguration} instance for the import-loader-suggestions script.
*/
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public ScriptConfiguration<?> getScriptConfiguration() {
return new DSpace().getServiceManager()
.getServiceByName("import-loader-suggestions", ScriptConfiguration.class);
}
/**
* Sets up the script execution environment.
* This method also checks for the presence of the help option and, if found,
* displays usage instructions before terminating the script.
*
* @throws ParseException If there is an error parsing the command-line options.
*/
@Override
public void setup() throws ParseException {
super.setup();
// in case of CLI we show the help prompt
if (commandLine.hasOption('h')) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("Imports suggestions from external providers for publication claim",
getScriptConfiguration().getOptions());
System.exit(0);
}
}
}

View File

@@ -5,7 +5,7 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openaire;
package org.dspace.app.suggestion.scorer;
import static org.dspace.app.suggestion.SuggestionUtils.getAllEntriesByMetadatum;
@@ -137,6 +137,8 @@ public class AuthorNamesScorer implements EvidenceScorer {
* */
private String normalize(String value) {
String norm = Normalizer.normalize(value, Normalizer.NFD);
// Removes diacritical marks
norm = norm.replaceAll("\\p{M}", "");
CharsetDetector cd = new CharsetDetector();
cd.setText(value.getBytes());
CharsetMatch detect = cd.detect();

View File

@@ -5,12 +5,10 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openaire;
package org.dspace.app.suggestion.scorer;
import java.util.Calendar;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import org.dspace.app.suggestion.SuggestionEvidence;
@@ -202,11 +200,9 @@ public class DateScorer implements EvidenceScorer {
private int getYear(String birthDateStr) {
int birthDateYear = -1;
if (birthDateStr != null) {
Date birthDate = MultiFormatDateParser.parse(birthDateStr);
LocalDateTime birthDate = MultiFormatDateParser.parse(birthDateStr).toLocalDateTime();
if (birthDate != null) {
Calendar calendar = new GregorianCalendar();
calendar.setTime(birthDate);
birthDateYear = calendar.get(Calendar.YEAR);
birthDateYear = birthDate.getYear();
}
}
return birthDateYear;

View File

@@ -5,7 +5,7 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openaire;
package org.dspace.app.suggestion.scorer;
import org.dspace.app.suggestion.SuggestionEvidence;
import org.dspace.content.Item;
@@ -32,6 +32,6 @@ public interface EvidenceScorer {
* @return the generated suggestion evidence or null if the record should be
* discarded
*/
public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord);
SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord);
}

View File

@@ -5,18 +5,27 @@
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.openaire;
package org.dspace.app.suggestion.script;
import org.apache.commons.cli.Options;
import org.dspace.app.suggestion.runnable.PublicationLoaderRunnable;
/**
* Extension of {@link PublicationLoaderScriptConfiguration} for CLI.
* Extension of {@link org.dspace.app.suggestion.openaire.PublicationLoaderScriptConfiguration} for CLI.
*
* @author Alessandro Martelli (alessandro.martelli at 4science.it)
*/
public class PublicationLoaderCliScriptConfiguration<T extends PublicationLoaderRunnable>
extends PublicationLoaderScriptConfiguration<T> {
/**
* Retrieves the command-line options available for this script.
* In addition to the options provided by {@link PublicationLoaderScriptConfiguration},
* this method adds a "help" option.
*
* @return The configured {@link Options} object containing all available command-line parameters.
*/
@Override
public Options getOptions() {
Options options = super.getOptions();

View File

@@ -0,0 +1,106 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.suggestion.script;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.cli.Options;
import org.dspace.app.suggestion.runnable.PublicationLoaderRunnable;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceCommandLineParameter;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Configuration class for the PublicationLoader script.
* This class extends {@link ScriptConfiguration} to provide configuration settings
* and execution permissions for the {@link PublicationLoaderRunnable} script.
*
* @param <T> The specific type of {@link PublicationLoaderRunnable} that this configuration supports.
* @author Adamo Fapohunda (adamo.fapohunda at 4science.com)
*/
public class PublicationLoaderScriptConfiguration<T extends PublicationLoaderRunnable>
extends ScriptConfiguration<T> {
@Autowired
private AuthorizeService authorizeService;
private Class<T> dspaceRunnableClass;
/**
* Retrieves the class type of the DSpace runnable script.
*
* @return the class of type {@link T} representing the script to execute.
*/
@Override
public Class<T> getDspaceRunnableClass() {
return dspaceRunnableClass;
}
/**
* Sets the class type of the DSpace runnable script.
*
* @param dspaceRunnableClass The class of the script to be set.
*/
@Override
public void setDspaceRunnableClass(Class<T> dspaceRunnableClass) {
this.dspaceRunnableClass = dspaceRunnableClass;
}
/**
* Determines whether the current user is authorized to execute the script.
* This check is based on whether the user has administrator privileges.
*
* @param context The DSpace context.
* @param commandLineParameters The list of command line parameters provided.
* @return true if the user has administrative privileges, false otherwise.
* @throws RuntimeException if an SQL exception occurs while checking authorization.
*/
@Override
public boolean isAllowedToExecute(Context context, List<DSpaceCommandLineParameter> commandLineParameters) {
try {
return authorizeService.isAdmin(context);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred when checking if the current user is an admin", e);
}
}
/**
* Defines the command-line options available for the script.
* These options allow customization of the script execution parameters.
*
* @return an {@link Options} object containing the available script options.
*/
@Override
public Options getOptions() {
if (options == null) {
Options options = new Options();
options.addOption("s", "single-researcher", true, "Single researcher UUID");
options.getOption("s").setType(String.class);
options.addOption("l", "loader", true, "Publication loader to be used");
options.getOption("l").setType(String.class);
options.getOption("l").setRequired(true);
options.addOption("f", "solrfilter", true, "The additional SOLR filter to better refine results");
options.getOption("f").setType(String.class);
options.addOption("m", "max", true,
"The maximum number of researcher profiles to process. If no maximum is provided, then " +
"the configured default will be used.");
options.getOption("m").setType(Integer.class);
super.options = options;
}
return options;
}
}

View File

@@ -9,8 +9,7 @@
package org.dspace.app.util;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
import java.time.Instant;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -35,7 +34,7 @@ abstract public class AbstractDSpaceWebapp
protected String kind;
protected Date started;
protected Instant started;
protected String url;
@@ -55,7 +54,7 @@ abstract public class AbstractDSpaceWebapp
public AbstractDSpaceWebapp(String kind) {
this.kind = kind;
started = new Date();
started = Instant.now();
ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService();
@@ -70,10 +69,9 @@ abstract public class AbstractDSpaceWebapp
*/
public void register() {
// Create the database entry
Timestamp now = new Timestamp(started.getTime());
try {
Context context = new Context();
webApp = webAppService.create(context, kind, url, now, isUI() ? 1 : 0);
webApp = webAppService.create(context, kind, url, started, isUI() ? 1 : 0);
context.complete();
} catch (SQLException e) {
log.error("Failed to record startup in Webapp table.", e);

View File

@@ -15,7 +15,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import org.apache.commons.lang3.StringUtils;
@@ -118,15 +117,17 @@ public class DCInputsReader {
formDefns = new HashMap<String, List<List<Map<String, String>>>>();
valuePairs = new HashMap<String, List<String>>();
String uri = "file:" + new File(fileName).getAbsolutePath();
File inputFile = new File(fileName);
String inputFileDir = inputFile.toPath().normalize().getParent().toString();
String uri = "file:" + inputFile.getAbsolutePath();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
DocumentBuilder db = factory.newDocumentBuilder();
// This document builder will *not* disable external
// entities as they can be useful in managing large forms, but
// it will restrict them to be within the directory that the
// current input form XML file exists (or a sub-directory)
DocumentBuilder db = XMLUtils.getTrustedDocumentBuilder(inputFileDir);
Document doc = db.parse(uri);
doNodes(doc);
checkValues();
@@ -379,7 +380,7 @@ public class DCInputsReader {
}
// sanity check number of fields
if (fields.size() < 1) {
throw new DCInputsReaderException("Form " + formName + "row " + rowIdx + " has no fields");
throw new DCInputsReaderException("Form " + formName + ", row " + rowIdx + " has no fields");
}
}

View File

@@ -11,7 +11,6 @@ import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.cli.CommandLine;
@@ -64,20 +63,36 @@ public class InitializeEntities {
*/
public static void main(String[] argv) throws SQLException, AuthorizeException, ParseException {
InitializeEntities initializeEntities = new InitializeEntities();
// Set up command-line options and parse arguments
CommandLineParser parser = new DefaultParser();
Options options = createCommandLineOptions();
CommandLine line = parser.parse(options,argv);
String fileLocation = getFileLocationFromCommandLine(line);
// First of all, check if the help option was entered or a required argument is missing
checkHelpEntered(options, line);
// Get the file location from the command line
String fileLocation = getFileLocationFromCommandLine(line);
// Run the script
initializeEntities.run(fileLocation);
}
/**
* Check if the help option was entered or a required argument is missing. If so, print help and exit.
* @param options the defined command-line options
* @param line the parsed command-line arguments
*/
private static void checkHelpEntered(Options options, CommandLine line) {
if (line.hasOption("h")) {
if (line.hasOption("h") || !line.hasOption("f")) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("Initialize Entities", options);
System.exit(0);
}
}
/**
* Get the file path from the command-line argument. Exits with exit code 1 if no file argument was entered.
* @param line the parsed command-line arguments
* @return the file path
*/
private static String getFileLocationFromCommandLine(CommandLine line) {
String query = line.getOptionValue("f");
if (StringUtils.isEmpty(query)) {
@@ -88,13 +103,25 @@ public class InitializeEntities {
return query;
}
/**
* Create the command-line options
* @return the command-line options
*/
protected static Options createCommandLineOptions() {
Options options = new Options();
options.addOption("f", "file", true, "the location for the file containing the xml data");
options.addOption("f", "file", true, "the path to the file containing the " +
"relationship definitions (e.g. ${dspace.dir}/config/entities/relationship-types.xml)");
options.addOption("h", "help", false, "print this message");
return options;
}
/**
* Run the script for the given file location
* @param fileLocation the file location
* @throws SQLException If something goes wrong initializing context or inserting relationship types
* @throws AuthorizeException If the script user fails to authorize while inserting relationship types
*/
private void run(String fileLocation) throws SQLException, AuthorizeException {
Context context = new Context();
context.turnOffAuthorisationSystem();
@@ -102,11 +129,18 @@ public class InitializeEntities {
context.complete();
}
/**
* Parse the XML file at fileLocation to create relationship types in the database
* @param context DSpace context
* @param fileLocation the full or relative file path to the relationship types XML
* @throws AuthorizeException If the script user fails to authorize while inserting relationship types
*/
private void parseXMLToRelations(Context context, String fileLocation) throws AuthorizeException {
try {
File fXmlFile = new File(fileLocation);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
// This XML builder will allow external entities, so the relationship types XML should
// be considered trusted by administrators
DocumentBuilder dBuilder = XMLUtils.getTrustedDocumentBuilder();
Document doc = dBuilder.parse(fXmlFile);
doc.getDocumentElement().normalize();
@@ -158,15 +192,15 @@ public class InitializeEntities {
for (int j = 0; j < leftCardinalityList.getLength(); j++) {
Node node = leftCardinalityList.item(j);
leftCardinalityMin = getString(leftCardinalityMin,(Element) node, "min");
leftCardinalityMax = getString(leftCardinalityMax,(Element) node, "max");
leftCardinalityMin = getCardinalityMinString(leftCardinalityMin,(Element) node, "min");
leftCardinalityMax = getCardinalityMinString(leftCardinalityMax,(Element) node, "max");
}
for (int j = 0; j < rightCardinalityList.getLength(); j++) {
Node node = rightCardinalityList.item(j);
rightCardinalityMin = getString(rightCardinalityMin,(Element) node, "min");
rightCardinalityMax = getString(rightCardinalityMax,(Element) node, "max");
rightCardinalityMin = getCardinalityMinString(rightCardinalityMin,(Element) node, "min");
rightCardinalityMax = getCardinalityMinString(rightCardinalityMax,(Element) node, "max");
}
populateRelationshipType(context, leftType, rightType, leftwardType, rightwardType,
@@ -182,13 +216,39 @@ public class InitializeEntities {
}
}
private String getString(String leftCardinalityMin,Element node, String minOrMax) {
/**
* Extract the min or max value for the left or right cardinality from the node text content
* @param leftCardinalityMin current left cardinality min
* @param node node to extract the min or max value from
* @param minOrMax element tag name to parse
* @return final left cardinality min
*/
private String getCardinalityMinString(String leftCardinalityMin, Element node, String minOrMax) {
if (node.getElementsByTagName(minOrMax).getLength() > 0) {
leftCardinalityMin = node.getElementsByTagName(minOrMax).item(0).getTextContent();
}
return leftCardinalityMin;
}
/**
* Populate the relationship type based on values parsed from the XML relationship types configuration
*
* @param context DSpace context
* @param leftType left relationship type (e.g. "Publication").
* @param rightType right relationship type (e.g. "Journal").
* @param leftwardType leftward relationship type (e.g. "isAuthorOfPublication").
* @param rightwardType rightward relationship type (e.g. "isPublicationOfAuthor").
* @param leftCardinalityMin left cardinality min
* @param leftCardinalityMax left cardinality max
* @param rightCardinalityMin right cardinality min
* @param rightCardinalityMax right cardinality max
* @param copyToLeft copy metadata values to left if right side is deleted
* @param copyToRight copy metadata values to right if left side is deleted
* @param tilted set a tilted relationship side (left or right) if there are many relationships going one way
* to help performance (e.g. authors with 1000s of publications)
* @throws SQLException if database error occurs while saving the relationship type
* @throws AuthorizeException if authorization error occurs while saving the relationship type
*/
private void populateRelationshipType(Context context, String leftType, String rightType, String leftwardType,
String rightwardType, String leftCardinalityMin, String leftCardinalityMax,
String rightCardinalityMin, String rightCardinalityMax,

View File

@@ -100,6 +100,14 @@ public class OpenSearchServiceImpl implements OpenSearchService {
configurationService.getProperty("websvc.opensearch.uicontext");
}
/**
* Get base search UI URL (websvc.opensearch.max_num_of_items_per_request)
*/
public int getMaxNumOfItemsPerRequest() {
return configurationService.getIntProperty(
"websvc.opensearch.max_num_of_items_per_request", 100);
}
@Override
public String getContentType(String format) {
return "html".equals(format) ? "text/html" :

View File

@@ -16,7 +16,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import org.apache.commons.lang3.StringUtils;
@@ -170,13 +169,10 @@ public class SubmissionConfigReader {
String uri = "file:" + new File(fileName).getAbsolutePath();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
DocumentBuilder db = factory.newDocumentBuilder();
// This document builder factory will *not* disable external
// entities as they can be useful in managing large forms, but
// it will restrict them to the config dir containing submission definitions
DocumentBuilder db = XMLUtils.getTrustedDocumentBuilder(configDir);
Document doc = db.parse(uri);
doNodes(doc);
} catch (FactoryConfigurationError fe) {
@@ -732,4 +728,4 @@ public class SubmissionConfigReader {
}
return results;
}
}
}

Some files were not shown because too many files have changed in this diff Show More