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/ */target/
dspace/modules/*/target/ dspace/modules/*/target/
Dockerfile.* Dockerfile.*
dspace/src/main/docker/dspace-postgres-pgcrypto dspace/src/main/docker/dspace-postgres-loadsql
dspace/src/main/docker/dspace-postgres-pgcrypto-curl
dspace/src/main/docker/README.md dspace/src/main/docker/README.md
dspace/src/main/docker-compose/ dspace/src/main/docker-compose/

187
.github/dependabot.yml vendored
View File

@@ -13,6 +13,7 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "weekly"
time: "02:00"
# Allow up to 10 open PRs for dependencies # Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10 open-pull-requests-limit: 10
# Group together some upgrades in a single PR # Group together some upgrades in a single PR
@@ -28,7 +29,7 @@ updates:
- "com.google.code.findbugs:*" - "com.google.code.findbugs:*"
- "com.google.errorprone:*" - "com.google.errorprone:*"
- "com.puppycrawl.tools:checkstyle" - "com.puppycrawl.tools:checkstyle"
- "org.sonatype.plugins:*" - "org.sonatype.*:*"
exclude-patterns: exclude-patterns:
# Exclude anything from Spring, as that is in a separate group # Exclude anything from Spring, as that is in a separate group
- "org.springframework.*:*" - "org.springframework.*:*"
@@ -43,9 +44,11 @@ updates:
- "com.h2database:*" - "com.h2database:*"
- "io.findify:s3mock*" - "io.findify:s3mock*"
- "io.netty:*" - "io.netty:*"
- "org.apache.httpcomponents.client5:*"
- "org.hamcrest:*" - "org.hamcrest:*"
- "org.mock-server:*" - "org.mock-server:*"
- "org.mockito:*" - "org.mockito:*"
- "org.xmlunit:*"
update-types: update-types:
- "minor" - "minor"
- "patch" - "patch"
@@ -73,7 +76,6 @@ updates:
patterns: patterns:
- "org.hibernate.*:*" - "org.hibernate.*:*"
update-types: update-types:
- "minor"
- "patch" - "patch"
# Group together all Jakarta deps in a single PR # Group together all Jakarta deps in a single PR
jakarta: jakarta:
@@ -103,14 +105,149 @@ updates:
update-types: update-types:
- "minor" - "minor"
- "patch" - "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: ignore:
# Don't try to auto-update any DSpace dependencies # Don't try to auto-update any DSpace dependencies
- dependency-name: "org.dspace:*" - dependency-name: "org.dspace:*"
- 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. # Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*" - dependency-name: "*"
update-types: ["version-update:semver-major"] update-types: ["version-update:semver-major"]
###################### ######################
## dspace-9_x branch
######################
- package-ecosystem: "maven"
directory: "/"
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
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.apache.httpcomponents.client5:*"
- "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 Jakarta deps in a single PR
jakarta:
applies-to: version-updates
patterns:
- "jakarta.*:*"
- "org.eclipse.angus:jakarta.mail"
- "org.glassfish.jaxb:jaxb-runtime"
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-8_x branch ## dspace-8_x branch
###################### ######################
- package-ecosystem: "maven" - package-ecosystem: "maven"
@@ -118,6 +255,7 @@ updates:
target-branch: dspace-8_x target-branch: dspace-8_x
schedule: schedule:
interval: "weekly" interval: "weekly"
time: "02:00"
# Allow up to 10 open PRs for dependencies # Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10 open-pull-requests-limit: 10
# Group together some upgrades in a single PR # Group together some upgrades in a single PR
@@ -133,7 +271,7 @@ updates:
- "com.google.code.findbugs:*" - "com.google.code.findbugs:*"
- "com.google.errorprone:*" - "com.google.errorprone:*"
- "com.puppycrawl.tools:checkstyle" - "com.puppycrawl.tools:checkstyle"
- "org.sonatype.plugins:*" - "org.sonatype.*:*"
exclude-patterns: exclude-patterns:
# Exclude anything from Spring, as that is in a separate group # Exclude anything from Spring, as that is in a separate group
- "org.springframework.*:*" - "org.springframework.*:*"
@@ -148,9 +286,11 @@ updates:
- "com.h2database:*" - "com.h2database:*"
- "io.findify:s3mock*" - "io.findify:s3mock*"
- "io.netty:*" - "io.netty:*"
- "org.apache.httpcomponents.client5:*"
- "org.hamcrest:*" - "org.hamcrest:*"
- "org.mock-server:*" - "org.mock-server:*"
- "org.mockito:*" - "org.mockito:*"
- "org.xmlunit:*"
update-types: update-types:
- "minor" - "minor"
- "patch" - "patch"
@@ -178,7 +318,6 @@ updates:
patterns: patterns:
- "org.hibernate.*:*" - "org.hibernate.*:*"
update-types: update-types:
- "minor"
- "patch" - "patch"
# Group together all Jakarta deps in a single PR # Group together all Jakarta deps in a single PR
jakarta: jakarta:
@@ -208,10 +347,24 @@ updates:
update-types: update-types:
- "minor" - "minor"
- "patch" - "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: ignore:
# Don't try to auto-update any DSpace dependencies # Don't try to auto-update any DSpace dependencies
- dependency-name: "org.dspace:*" - dependency-name: "org.dspace:*"
- 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. # Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*" - dependency-name: "*"
update-types: [ "version-update:semver-major" ] update-types: [ "version-update:semver-major" ]
@@ -223,6 +376,7 @@ updates:
target-branch: dspace-7_x target-branch: dspace-7_x
schedule: schedule:
interval: "weekly" interval: "weekly"
time: "02:00"
# Allow up to 10 open PRs for dependencies # Allow up to 10 open PRs for dependencies
open-pull-requests-limit: 10 open-pull-requests-limit: 10
# Group together some upgrades in a single PR # Group together some upgrades in a single PR
@@ -238,7 +392,7 @@ updates:
- "com.google.code.findbugs:*" - "com.google.code.findbugs:*"
- "com.google.errorprone:*" - "com.google.errorprone:*"
- "com.puppycrawl.tools:checkstyle" - "com.puppycrawl.tools:checkstyle"
- "org.sonatype.plugins:*" - "org.sonatype.*:*"
exclude-patterns: exclude-patterns:
# Exclude anything from Spring, as that is in a separate group # Exclude anything from Spring, as that is in a separate group
- "org.springframework.*:*" - "org.springframework.*:*"
@@ -256,6 +410,7 @@ updates:
- "org.hamcrest:*" - "org.hamcrest:*"
- "org.mock-server:*" - "org.mock-server:*"
- "org.mockito:*" - "org.mockito:*"
- "org.xmlunit:*"
update-types: update-types:
- "minor" - "minor"
- "patch" - "patch"
@@ -283,14 +438,14 @@ updates:
patterns: patterns:
- "org.hibernate.*:*" - "org.hibernate.*:*"
update-types: update-types:
- "minor"
- "patch" - "patch"
# Group together all Jakarta deps in a single PR # 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: jakarta:
applies-to: version-updates applies-to: version-updates
patterns: patterns:
- "jakarta.*:*" - "javax.*:*"
- "org.eclipse.angus:jakarta.mail" - "*:javax.mail"
- "org.glassfish.jaxb:jaxb-runtime" - "org.glassfish.jaxb:jaxb-runtime"
update-types: update-types:
- "minor" - "minor"
@@ -325,6 +480,17 @@ updates:
update-types: update-types:
- "minor" - "minor"
- "patch" - "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: ignore:
# Don't try to auto-update any DSpace dependencies # Don't try to auto-update any DSpace dependencies
- dependency-name: "org.dspace:*" - dependency-name: "org.dspace:*"
@@ -336,6 +502,9 @@ updates:
# See https://github.com/DSpace/DSpace/pull/9888#issuecomment-2408165545 # See https://github.com/DSpace/DSpace/pull/9888#issuecomment-2408165545
- dependency-name: "org.springframework.security:*" - dependency-name: "org.springframework.security:*"
versions: [">=5.8.0"] 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. # Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
- dependency-name: "*" - dependency-name: "*"
update-types: [ "version-update:semver-major" ] update-types: [ "version-update:semver-major" ]

View File

@@ -113,39 +113,19 @@ jobs:
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_SOLR_URL }} REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_SOLR_URL }}
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_SOLR_URL }} REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_SOLR_URL }}
########################################################### ########################################################
# Build/Push the 'dspace/dspace-postgres-pgcrypto' image # Build/Push the 'dspace/dspace-postgres-loadsql' image
########################################################### ########################################################
dspace-postgres-pgcrypto: dspace-postgres-loadsql:
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace' # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
if: github.repository == 'dspace/dspace' if: github.repository == 'dspace/dspace'
uses: ./.github/workflows/reusable-docker-build.yml uses: ./.github/workflows/reusable-docker-build.yml
with: with:
build_id: dspace-postgres-pgcrypto-prod build_id: dspace-postgres-loadsql
image_name: dspace/dspace-postgres-pgcrypto image_name: dspace/dspace-postgres-loadsql
# Must build out of subdirectory to have access to install script for pgcrypto. # 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 # NOTE: this context will build the image based on the Dockerfile in the specified directory
dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/ dockerfile_context: ./dspace/src/main/docker/dspace-postgres-loadsql/
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
secrets: secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }} DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -158,7 +138,7 @@ jobs:
if: github.repository == 'dspace/dspace' if: github.repository == 'dspace/dspace'
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Must run after all major images are built # 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: env:
# Override defaults dspace.server.url because backend starts at http://127.0.0.1:8080 # 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 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) result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections)
echo "$result" echo "$result"
echo "$result" | grep -oE "\"Dog in Yard\"," 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 # Verify Handle Server can be stared and is working properly
# 1. First generate the "[dspace]/handle-server" folder with the sitebndl.zip # 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) # 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_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }} REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
# Current DSpace branches (and architecture) which are deployed to demo.dspace.org & sandbox.dspace.org respectively # 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_SANDBOX_BRANCH: 'main'
DEPLOY_ARCH: 'linux/amd64' DEPLOY_ARCH: 'linux/amd64'
# Registry used during building of Docker images. (All images are later copied to docker.io registry) # Registry used during building of Docker images. (All images are later copied to docker.io registry)
@@ -86,17 +86,16 @@ jobs:
matrix: matrix:
# Architectures / Platforms for which we will build Docker images # Architectures / Platforms for which we will build Docker images
arch: [ 'linux/amd64', 'linux/arm64' ] arch: [ 'linux/amd64', 'linux/arm64' ]
os: [ ubuntu-latest ]
isPr: isPr:
- ${{ github.event_name == 'pull_request' }} - ${{ 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. # 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. # The below exclude therefore ensures we do NOT build ARM64 for PRs.
exclude: exclude:
- isPr: true - isPr: true
os: ubuntu-latest
arch: linux/arm64 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: steps:
# This step converts the slashes in the "arch" matrix values above into dashes & saves to env.ARCH_NAME # 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 }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} 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 # https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx - name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3

3
.gitignore vendored
View File

@@ -28,6 +28,9 @@ nbdist/
nbactions.xml nbactions.xml
nb-configuration.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) ## 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) ## KEPT FOR BACKWARDS COMPATIBILITY WITH 5.x (build.properties is now replaced with local.cfg)
/*.properties /*.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 ## 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. 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 Code Contribution Checklist
- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests) - [ ] 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 RUN mkdir -p /app/dspace-rdf
ADD --chown=dspace dspace-rdf/pom.xml /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 # 'dspace-server-webapp' module POM
RUN mkdir -p /app/dspace-server-webapp RUN mkdir -p /app/dspace-server-webapp
ADD --chown=dspace dspace-server-webapp/pom.xml /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: Apache Software License, Version 2.0:
* Ant-Contrib Tasks (ant-contrib:ant-contrib:1.0b3 - http://ant-contrib.sourceforge.net) * 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 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.261 - 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.261 - 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.261 - 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) * 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) * 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/) * 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) * 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) * Internet Time Utility (com.ethlo.time:itu:1.7.0 - https://github.com/ethlo/itu)
* Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.16.0 - https://github.com/FasterXML/jackson) * ClassMate (com.fasterxml:classmate:1.5.1 - https://github.com/FasterXML/java-classmate)
* Jackson-core (com.fasterxml.jackson.core:jackson-core:2.16.0 - https://github.com/FasterXML/jackson-core) * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.19.0 - https://github.com/FasterXML/jackson)
* jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.16.0 - https://github.com/FasterXML/jackson) * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.19.0 - https://github.com/FasterXML/jackson-core)
* Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 - http://github.com/FasterXML/jackson-dataformats-binary) * 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: 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-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-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: 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.16.0 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) * 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: 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 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: 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) * 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.0.1 - https://github.com/cowtowncoder/java-uuid-generator) * 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) * 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/) * 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: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) * 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 (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) * 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-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) * 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) * 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) * 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) * 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/) * 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) * Gson (com.google.code.gson:gson:2.13.1 - https://github.com/google/gson)
* error-prone annotations (com.google.errorprone:error_prone_annotations:2.10.0 - https://errorprone.info/error_prone_annotations) * 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 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 (com.google.guava:guava:32.1.3-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 ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture) * 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) * Google Guice - Core Library (com.google.inject:guice:7.0.0 - https://github.com/google/guice/guice)
* 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) * Google Guice - Extensions - AssistedInject (com.google.inject.extensions:guice-assistedinject:7.0.0 - https://github.com/google/guice/extensions-parent/guice-assistedinject)
* 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)
* J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/) * 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/) * 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/) * 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 (com.healthmarketscience.jackcess:jackcess:4.0.8 - https://jackcess.sourceforge.io)
* Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.2 - http://jackcessencrypt.sf.net) * 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 (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) * 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) * 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 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) * 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) * JsonSchemaValidator (com.networknt:json-schema-validator:1.0.76 - https://github.com/networknt/json-schema-validator)
* opencsv (com.opencsv:opencsv:5.9 - http://opencsv.sf.net) * 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) * 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 (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-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) * 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) * 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) * 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) * 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) * 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) * 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) * 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 BeanUtils (commons-beanutils:commons-beanutils:1.10.1 - 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 CLI (commons-cli:commons-cli:1.9.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 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/) * 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/) * 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/) * 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 Logging (commons-logging:commons-logging:1.3.5 - 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 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) * 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) * 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) * 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.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) * 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 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) * 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) * 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) * 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-commons (io.micrometer:micrometer-commons:1.14.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-core (io.micrometer:micrometer-core:1.12.6 - https://github.com/micrometer-metrics/micrometer) * micrometer-commons (io.micrometer:micrometer-commons:1.14.7 - https://github.com/micrometer-metrics/micrometer)
* micrometer-jakarta9 (io.micrometer:micrometer-jakarta9:1.12.6 - https://github.com/micrometer-metrics/micrometer) * micrometer-core (io.micrometer:micrometer-core:1.14.6 - https://github.com/micrometer-metrics/micrometer)
* micrometer-observation (io.micrometer:micrometer-observation:1.12.6 - https://github.com/micrometer-metrics/micrometer) * micrometer-jakarta9 (io.micrometer:micrometer-jakarta9:1.14.6 - https://github.com/micrometer-metrics/micrometer)
* Netty/Buffer (io.netty:netty-buffer:4.1.106.Final - https://netty.io/netty-buffer/) * 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/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 (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/HTTP (io.netty:netty-codec-http:4.1.86.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/Codec/HTTP2 (io.netty:netty-codec-http2:4.1.86.Final - https://netty.io/netty-codec-http2/)
* Netty/Common (io.netty:netty-common:4.1.106.Final - https://netty.io/netty-common/) * 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/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 (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/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 (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/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/) * 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 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-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) * 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) * 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) * 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-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.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-compat-spec-parser) * 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.2 - https://github.com/swagger-api/swagger-core/modules/swagger-core) * 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.2 - https://github.com/swagger-api/swagger-core/modules/swagger-models) * 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.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser) * 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.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations) * 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-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-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-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-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-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: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.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-core) * 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.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter) * 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.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3) * 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 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) * 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) * 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) * 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.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) * 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) * 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/) * "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.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.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) * 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) * 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) * 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 Core (org.apache.ant:ant:1.10.15 - https://ant.apache.org/)
* Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.14 - 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.7.0 - https://commons.apache.org/proper/commons-bcel) * 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 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) * 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 (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 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 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.26.0 - https://commons.apache.org/proper/commons-compress/) * 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.10.1 - https://commons.apache.org/proper/commons-configuration/) * 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.10.0 - https://commons.apache.org/proper/commons-csv/) * 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.11.0 - https://commons.apache.org/dbcp/) * 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.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 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 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 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.10.0 - https://commons.apache.org/proper/commons-text) * 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 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 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) * 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 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 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.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.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.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 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.10 - http://james.apache.org/mime4j/apache-mime4j-core) * 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.11 - http://james.apache.org/mime4j/apache-mime4j-dom) * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.12 - 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/) * jclouds blobstore core (org.apache.jclouds:jclouds-blobstore:2.7.0 - https://jclouds.apache.org/jclouds-blobstore/)
* Apache Jena - ARQ (org.apache.jena:jena-arq:4.9.0 - https://jena.apache.org/jena-arq/) * jclouds Components Core (org.apache.jclouds:jclouds-core:2.7.0 - https://jclouds.apache.org/jclouds-core/)
* Apache Jena - Base (org.apache.jena:jena-base:4.9.0 - https://jena.apache.org/jena-base/) * jclouds filesystem core (org.apache.jclouds.api:filesystem:2.7.0 - https://jclouds.apache.org/filesystem/)
* Apache Jena - Core (org.apache.jena:jena-core:4.9.0 - https://jena.apache.org/jena-core/) * jclouds s3 api (org.apache.jclouds.api:s3:2.7.0 - https://jclouds.apache.org/s3/)
* Apache Jena - DBOE Base (org.apache.jena:jena-dboe-base:4.9.0 - https://jena.apache.org/jena-dboe-base/) * jclouds sts api (org.apache.jclouds.api:sts:2.7.0 - https://jclouds.apache.org/sts/)
* Apache Jena - DBOE Indexes (org.apache.jena:jena-dboe-index:4.9.0 - https://jena.apache.org/jena-dboe-index/) * jclouds Amazon Simple Storage Service (S3) provider (org.apache.jclouds.provider:aws-s3:2.7.0 - https://jclouds.apache.org/aws-s3/)
* Apache Jena - DBOE Storage (org.apache.jena:jena-dboe-storage:4.9.0 - https://jena.apache.org/jena-dboe-storage/) * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:4.10.0 - https://jena.apache.org/apache-jena-libs/)
* 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 - ARQ (org.apache.jena:jena-arq:4.10.0 - https://jena.apache.org/jena-arq/)
* Apache Jena - DBOE Transactions (org.apache.jena:jena-dboe-transaction:4.9.0 - https://jena.apache.org/jena-dboe-transaction/) * Apache Jena - Base (org.apache.jena:jena-base:4.10.0 - https://jena.apache.org/jena-base/)
* Apache Jena - IRI (org.apache.jena:jena-iri:4.9.0 - https://jena.apache.org/jena-iri/) * Apache Jena - Core (org.apache.jena:jena-core:4.10.0 - https://jena.apache.org/jena-core/)
* Apache Jena - RDF Connection (org.apache.jena:jena-rdfconnection:4.9.0 - https://jena.apache.org/jena-rdfconnection/) * Apache Jena - DBOE Base (org.apache.jena:jena-dboe-base:4.10.0 - https://jena.apache.org/jena-dboe-base/)
* Apache Jena - RDF Patch (org.apache.jena:jena-rdfpatch:4.9.0 - https://jena.apache.org/jena-rdfpatch/) * Apache Jena - DBOE Indexes (org.apache.jena:jena-dboe-index:4.10.0 - https://jena.apache.org/jena-dboe-index/)
* Apache Jena - SHACL (org.apache.jena:jena-shacl:4.9.0 - https://jena.apache.org/jena-shacl/) * Apache Jena - DBOE Storage (org.apache.jena:jena-dboe-storage:4.10.0 - https://jena.apache.org/jena-dboe-storage/)
* Apache Jena - ShEx (org.apache.jena:jena-shex:4.9.0 - https://jena.apache.org/jena-shex/) * 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 - TDB1 (Native Triple Store) (org.apache.jena:jena-tdb:4.9.0 - https://jena.apache.org/jena-tdb/) * Apache Jena - DBOE Transactions (org.apache.jena:jena-dboe-transaction:4.10.0 - https://jena.apache.org/jena-dboe-transaction/)
* Apache Jena - TDB2 (Native Triple Store) (org.apache.jena:jena-tdb2:4.9.0 - https://jena.apache.org/jena-tdb2/) * 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 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-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 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) * 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 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.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-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.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/) * 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.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-jul/) * 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 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 Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.17.2 - https://logging.apache.org/log4j/2.x/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/) * 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.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-web/) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-classification) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-codecs) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-core) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-expressions) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-grouping) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-highlighter) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-join) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-memory) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-misc) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-queries) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-queryparser) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-sandbox) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-spatial3d) * 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.3 - https://lucene.apache.org/lucene-parent/lucene-suggest) * 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.31 - http://pdfbox.apache.org/) * 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/) * 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 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 (org.apache.pdfbox:pdfbox:2.0.34 - 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 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.31 - https://www.apache.org/pdfbox-parent/xmpbox/) * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.34 - https://www.apache.org/pdfbox-parent/xmpbox/)
* Apache POI - Common (org.apache.poi:poi:5.2.5 - https://poi.apache.org/) * 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.2.5 - 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.2.5 - 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.2.5 - https://poi.apache.org/) * Apache POI (org.apache.poi:poi-scratchpad:5.4.1 - https://poi.apache.org/)
* Apache Solr Core (org.apache.solr:solr-core:8.11.3 - https://lucene.apache.org/solr-parent/solr-core) * Apache XML Security for Java (org.apache.santuario:xmlsec:2.3.4 - https://santuario.apache.org/)
* Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.3 - https://lucene.apache.org/solr-parent/solr-solrj) * 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 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 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 Thrift (org.apache.thrift:libthrift:0.19.0 - http://thrift.apache.org)
* Apache Tika core (org.apache.tika:tika-core:2.9.2 - https://tika.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.2 - https://tika.apache.org/tika-parser-apple-module/) * 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.2 - https://tika.apache.org/tika-parser-audiovideo-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.2 - https://tika.apache.org/tika-parser-cad-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.2 - https://tika.apache.org/tika-parser-code-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.2 - https://tika.apache.org/tika-parser-crypto-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.2 - https://tika.apache.org/tika-parser-digest-commons/) * 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.2 - https://tika.apache.org/tika-parser-font-module/) * 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.2 - https://tika.apache.org/tika-parser-html-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.2 - https://tika.apache.org/tika-parser-image-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.2 - https://tika.apache.org/tika-parser-mail-commons/) * 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.2 - https://tika.apache.org/tika-parser-mail-module/) * 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.2 - https://tika.apache.org/tika-parser-microsoft-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.2 - https://tika.apache.org/tika-parser-miscoffice-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.2 - https://tika.apache.org/tika-parser-news-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.2 - https://tika.apache.org/tika-parser-ocr-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.2 - https://tika.apache.org/tika-parser-pdf-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.2 - https://tika.apache.org/tika-parser-pkg-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.2 - https://tika.apache.org/tika-parser-text-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.2 - https://tika.apache.org/tika-parser-webarchive-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.2 - https://tika.apache.org/tika-parser-xml-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.2 - https://tika.apache.org/tika-parser-xmp-commons/) * 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.2 - https://tika.apache.org/tika-parser-zip-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.2 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/) * 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.24 - https://tomcat.apache.org/) * 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.24 - 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.24 - 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.3 - http://velocity.apache.org/engine/devel/velocity-engine-core/) * 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.2 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/) * 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 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/) * 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 - 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) * 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) * 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) * 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/) * 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/) * 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) * 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) * 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) * 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) * 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 :: 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.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 :: 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 :: 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.54.v20240208 - https://eclipse.org/jetty/jetty-http) * 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.54.v20240208 - https://eclipse.org/jetty/jetty-io) * 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 :: 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 :: 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 :: 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 :: 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.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 :: 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.54.v20240208 - https://eclipse.org/jetty/jetty-server) * 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.54.v20240208 - https://eclipse.org/jetty/jetty-servlet) * 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.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 :: 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 (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.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax) * 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.54.v20240208 - https://eclipse.org/jetty/jetty-webapp) * 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.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 :: 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 :: 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 :: 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 :: 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.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 :: 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) * 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) * 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-core (org.flywaydb:flyway-core:10.22.0 - https://flywaydb.org/flyway-core)
* flyway-database-postgresql (org.flywaydb:flyway-database-postgresql:10.10.0 - https://flywaydb.org/flyway-database-postgresql) * 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) * 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) * 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 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) * 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) * 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 (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) * 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/) * Javassist (org.javassist:javassist:3.30.2-GA - https://www.javassist.org/)
* JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.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) * 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) * IntelliJ IDEA Annotations (org.jetbrains:annotations:13.0 - http://www.jetbrains.org)
* jtwig-reflection (org.jtwig:jtwig-reflection:5.87.0.RELEASE - http://jtwig.org) * Kotlin Stdlib (org.jetbrains.kotlin:kotlin-stdlib:1.8.21 - https://kotlinlang.org/)
* jtwig-spring (org.jtwig:jtwig-spring:5.87.0.RELEASE - http://jtwig.org) * Kotlin Stdlib Common (org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21 - https://kotlinlang.org/)
* jtwig-spring-boot-starter (org.jtwig:jtwig-spring-boot-starter:5.87.0.RELEASE - http://jtwig.org) * Kotlin Stdlib Jdk7 (org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.21 - https://kotlinlang.org/)
* jtwig-web (org.jtwig:jtwig-web:5.87.0.RELEASE - http://jtwig.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) * 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) * 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 Java Client (org.mock-server:mockserver-client-java:5.15.0 - https://www.mock-server.com)
* MockServer Core (org.mock-server:mockserver-core:5.11.2 - http://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.11.2 - http://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.11.2 - http://www.mock-server.com) * MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.15.0 - https://www.mock-server.com)
* Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty) * jwarc (org.netpreserve:jwarc:0.31.1 - https://github.com/iipc/jwarc)
* 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)
* Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis) * Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis)
* parboiled-core (org.parboiled:parboiled-core:1.3.1 - http://parboiled.org) * OpenSAML :: Core (org.opensaml:opensaml-core:4.3.2 - http://shibboleth.net/opensaml-core/)
* parboiled-java (org.parboiled:parboiled-java:1.3.1 - http://parboiled.org) * OpenSAML :: Messaging API (org.opensaml:opensaml-messaging-api:4.3.2 - http://shibboleth.net/opensaml-messaging-api/)
* org.roaringbitmap:RoaringBitmap (org.roaringbitmap:RoaringBitmap:0.9.45 - https://github.com/RoaringBitmap/RoaringBitmap) * OpenSAML :: Profile API (org.opensaml:opensaml-profile-api:4.3.2 - http://shibboleth.net/opensaml-profile-api/)
* org.roaringbitmap:shims (org.roaringbitmap:shims:0.9.45 - https://github.com/RoaringBitmap/RoaringBitmap) * 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/) * 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 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-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-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-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/) * 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) * 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.11 - http://www.slf4j.org) * 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.1.8 - https://github.com/spring-projects/spring-framework) * Spring AOP (org.springframework:spring-aop:6.2.7 - https://github.com/spring-projects/spring-framework)
* Spring Beans (org.springframework:spring-beans:6.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.1.8 - 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.2.6 - https://spring.io/projects/spring-boot) * 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.2.6 - 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.2.6 - 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.2.6 - 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 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.4.5 - https://spring.io/projects/spring-boot)
* 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.4.5 - 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.4.5 - 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.4.5 - 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.4.5 - 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.4.5 - 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.4.5 - 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.4.5 - 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.4.5 - 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-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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - 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.2.6 - https://spring.io/projects/spring-data) * 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.2.6 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core) * 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.2.6 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc) * 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.2.2 - https://github.com/spring-projects/spring-hateoas) * 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 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-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.2.4 - 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.2.4 - 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-test (org.springframework.security:spring-security-test:6.2.4 - 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-web (org.springframework.security:spring-security-web:6.2.4 - 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) * 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/) * 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.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.9.1 - https://www.xmlunit.org/xmlunit-placeholders/)
* org.xmlunit:xmlunit-placeholders (org.xmlunit:xmlunit-placeholders:2.8.0 - https://www.xmlunit.org/xmlunit-placeholders/) * SnakeYAML (org.yaml:snakeyaml:2.3 - https://bitbucket.org/snakeyaml/snakeyaml)
* 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/)
* Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/) * Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/)
BSD License: 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/) * 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) * 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.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/) * 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) * 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/) * 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/) * 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) * 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) * 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 (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/)
* Hamcrest Core (org.hamcrest:hamcrest-core: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) * JBibTeX (org.jbibtex:jbibtex:1.0.20 - http://www.jbibtex.org)
* asm (org.ow2.asm:asm:8.0.1 - http://asm.ow2.io/) * 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-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-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-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.5 - https://jdbc.postgresql.org)
* PostgreSQL JDBC Driver (org.postgresql:postgresql:42.7.3 - https://jdbc.postgresql.org)
* Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections)
* JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio) * 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/) * XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/)
CC0: 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) * 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 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 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) * 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/) * 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) * 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) * 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) * 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) * 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) * 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.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) * 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.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) * 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) * 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) * 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.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * 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.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * 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) * 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: 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 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/) * 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/) * 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-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.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * 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) * 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) * 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) * 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: Eclipse Public License:
* System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/) * 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 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 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 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.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) * 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) * 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) * 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) * 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/) * 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 :: 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.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 :: 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 :: 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.54.v20240208 - https://eclipse.org/jetty/jetty-http) * 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.54.v20240208 - https://eclipse.org/jetty/jetty-io) * 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 :: 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 :: 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 :: 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 :: 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.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 :: 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.54.v20240208 - https://eclipse.org/jetty/jetty-server) * 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.54.v20240208 - https://eclipse.org/jetty/jetty-servlet) * 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.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 :: 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 (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.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax) * 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.54.v20240208 - https://eclipse.org/jetty/jetty-webapp) * 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.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 :: 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 :: 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 :: 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 :: 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.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 :: 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) * 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) * 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) * 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.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator) * 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.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils) * 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) * 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) * 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.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * 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.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common) * 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.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * 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) * 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: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) * 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) GENERAL PUBLIC LICENSE, version 3 (GPL-3.0):
* Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util)
* juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet)
GNU Lesser General Public License (LGPL): 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) * 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 (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) * 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-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) * 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) * 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) * 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/) * 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) * 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 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-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-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) * 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/) * 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) * XOM (xom:xom:1.3.9 - https://xom.nu)
Go License: 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) * 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) * 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) * 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) * 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) * 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) * 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 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-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.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.78.1 - https://www.bouncycastle.org/java.html) * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.67 - http://www.bouncycastle.org/java.html) * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.80 - https://www.bouncycastle.org/download/bouncy-castle-java/)
* 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)
* org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec) * 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) * Checker Qual (org.checkerframework:checker-qual:3.49.3 - 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-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.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * 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) * 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-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) * 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 API Module (org.slf4j:slf4j-api:2.0.17 - http://www.slf4j.org)
* SLF4J Extensions Module (org.slf4j:slf4j-ext:1.7.28 - http://www.slf4j.org)
* HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org) * HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org)
* toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - 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) * 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) * 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) * 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) * 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) * 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.6.1 - 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: Mozilla Public License:
* juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet) * juniversalchardet (com.github.albfernandez:juniversalchardet:2.5.0 - https://github.com/albfernandez/juniversalchardet)
* H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com) * openpdf (com.github.librepdf:openpdf:2.0.3 - https://github.com/LibrePDF/OpenPDF/openpdf)
* Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/) * H2 Database Engine (com.h2database:h2:2.3.232 - https://h2database.com)
* Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/) * 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) * Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino)
Public Domain: Public Domain:
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * AOP alliance (aopalliance:aopalliance:1.0 - http://aopalliance.sourceforge.net)
* 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-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * 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) * 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) * 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/) * LatencyUtils (org.latencyutils:LatencyUtils:2.0.3 - http://latencyutils.github.io/LatencyUtils/)
* Reflections (org.reflections:reflections:0.9.12 - http://github.com/ronmamo/reflections) * 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: UnRar License:
@@ -703,16 +745,16 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Unicode/ICU License: 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: W3C license:
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * 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.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * 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) * 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: jQuery license:
* jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client) * 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.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2) * 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) * 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/). 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: 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) Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL)
and a servlet container (usually Tomcat) in order to function. 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. 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. 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. 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 ## Contributing

View File

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

View File

@@ -136,5 +136,22 @@ For more information on CheckStyle configurations below, see: http://checkstyle.
<module name="OneStatementPerLine"/> <module name="OneStatementPerLine"/>
<!-- Require that "catch" statements are not empty (must at least contain a comment) --> <!-- Require that "catch" statements are not empty (must at least contain a comment) -->
<module name="EmptyCatchBlock"/> <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>
</module> </module>

View File

@@ -24,6 +24,8 @@ services:
db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace' db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace'
# solr.server: Ensure we are using the 'dspacesolr' image for Solr # solr.server: Ensure we are using the 'dspacesolr' image for Solr
solr__P__server: http://dspacesolr:8983/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 # 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. # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
proxies__P__trusted__P__ipranges: '172.23.0' proxies__P__trusted__P__ipranges: '172.23.0'
@@ -63,13 +65,12 @@ services:
# DSpace PostgreSQL database container # DSpace PostgreSQL database container
dspacedb: dspacedb:
container_name: dspacedb container_name: dspacedb
# Uses a custom Postgres image with pgcrypto installed # Uses the base PostgreSQL image
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}" image: "docker.io/postgres:${POSTGRES_VERSION:-15}"
build:
# Must build out of subdirectory to have access to install script for pgcrypto
context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
environment: environment:
PGDATA: /pgdata PGDATA: /pgdata
POSTGRES_DB: dspace
POSTGRES_USER: dspace
POSTGRES_PASSWORD: dspace POSTGRES_PASSWORD: dspace
networks: networks:
dspacenet: dspacenet:
@@ -91,7 +92,7 @@ services:
additional_contexts: additional_contexts:
solrconfigs: ./dspace/solr/ solrconfigs: ./dspace/solr/
args: args:
SOLR_VERSION: "${SOLR_VER:-8.11}" SOLR_VERSION: "${SOLR_VER:-9.8}"
networks: networks:
dspacenet: dspacenet:
ports: ports:
@@ -103,10 +104,15 @@ services:
volumes: volumes:
# Keep Solr data directory between reboots # Keep Solr data directory between reboots
- solr_data:/var/solr/data - 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: # 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 # * 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: # * 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` # 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: entrypoint:
- /bin/bash - /bin/bash
- '-c' - '-c'
@@ -124,7 +130,8 @@ services:
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
cp -r /opt/solr/server/solr/configsets/suggestion/* 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: volumes:
assetstore: assetstore:
pgdata: pgdata:

View File

@@ -12,7 +12,7 @@
<parent> <parent>
<groupId>org.dspace</groupId> <groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId> <artifactId>dspace-parent</artifactId>
<version>9.0-SNAPSHOT</version> <version>10.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
@@ -102,7 +102,7 @@
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId> <artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version> <version>3.6.1</version>
<executions> <executions>
<execution> <execution>
<phase>validate</phase> <phase>validate</phase>
@@ -177,7 +177,7 @@
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId> <artifactId>jaxb2-maven-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<executions> <executions>
<execution> <execution>
<id>workflow-curation</id> <id>workflow-curation</id>
@@ -500,10 +500,6 @@
<groupId>jakarta.annotation</groupId> <groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId> <artifactId>jakarta.annotation-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>jaxen</groupId> <groupId>jaxen</groupId>
<artifactId>jaxen</artifactId> <artifactId>jaxen</artifactId>
@@ -516,10 +512,6 @@
<groupId>org.apache.pdfbox</groupId> <groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId> <artifactId>pdfbox</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.ibm.icu</groupId> <groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId> <artifactId>icu4j</artifactId>
@@ -657,11 +649,18 @@
<version>1.1.1</version> <version>1.1.1</version>
</dependency> </dependency>
<!-- guava is needed by OAuth, Guice, Mockserver, ORCID, s3mock, Solr, JClouds -->
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
</dependency> </dependency>
<!-- Gson is needed by JENA, borker-client, OAuth, Handle and JClouds -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
@@ -735,7 +734,7 @@
<dependency> <dependency>
<groupId>com.amazonaws</groupId> <groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId> <artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.781</version> <version>1.12.788</version>
</dependency> </dependency>
<!-- TODO: This may need to be replaced with the "orcid-model" artifact once this ticket is resolved: <!-- TODO: This may need to be replaced with the "orcid-model" artifact once this ticket is resolved:
@@ -776,7 +775,7 @@
<dependency> <dependency>
<groupId>com.opencsv</groupId> <groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId> <artifactId>opencsv</artifactId>
<version>5.10</version> <version>5.12.0</version>
</dependency> </dependency>
<!-- Email templating --> <!-- Email templating -->
@@ -789,7 +788,7 @@
<dependency> <dependency>
<groupId>org.xmlunit</groupId> <groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId> <artifactId>xmlunit-core</artifactId>
<version>2.10.0</version> <version>2.10.3</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
@@ -864,5 +863,86 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </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> </dependencies>
</project> </project>

View File

@@ -8,8 +8,10 @@
package org.dspace.access.status; package org.dspace.access.status;
import java.sql.SQLException; 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.content.Item;
import org.dspace.core.Context; import org.dspace.core.Context;
@@ -23,20 +25,35 @@ public interface AccessStatusHelper {
* @param context the DSpace context * @param context the DSpace context
* @param item the item * @param item the item
* @param threshold the embargo threshold date * @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. * @throws SQLException An exception that provides information on a database access error or other errors.
*/ */
public String getAccessStatusFromItem(Context context, Item item, Date threshold) public AccessStatus getAccessStatusFromItem(Context context,
throws SQLException; 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 context the DSpace context
* @param item the item to check for embargo information * @param item the item to check for embargo information
* @param threshold the embargo threshold date * @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. * @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.sql.SQLException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; 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.access.status.service.AccessStatusService;
import org.dspace.content.AccessStatus;
import org.dspace.content.Bitstream;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.core.service.PluginService; import org.dspace.core.service.PluginService;
@@ -23,10 +27,15 @@ import org.springframework.beans.factory.annotation.Autowired;
* Implementation for the access status calculation service. * Implementation for the access status calculation service.
*/ */
public class AccessStatusServiceImpl implements AccessStatusService { public class AccessStatusServiceImpl implements AccessStatusService {
private static final Logger log = LogManager.getLogger(AccessStatusServiceImpl.class);
// Plugin implementation, set from the DSpace configuration by init(). // Plugin implementation, set from the DSpace configuration by init().
protected AccessStatusHelper helper = null; 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) @Autowired(required = true)
protected ConfigurationService configurationService; protected ConfigurationService configurationService;
@@ -56,20 +65,39 @@ public class AccessStatusServiceImpl implements AccessStatusService {
int month = configurationService.getIntProperty("access.status.embargo.forever.month"); int month = configurationService.getIntProperty("access.status.embargo.forever.month");
int day = configurationService.getIntProperty("access.status.embargo.forever.day"); 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() .atStartOfDay()
.atZone(ZoneId.systemDefault()) .atZone(ZoneId.systemDefault())
.toInstant()); .toLocalDate();
itemCalculationType = getAccessStatusCalculationType("access.status.for-user.item");
bitstreamCalculationType = getAccessStatusCalculationType("access.status.for-user.bitstream");
} }
} }
@Override @Override
public String getAccessStatus(Context context, Item item) throws SQLException { public AccessStatus getAccessStatus(Context context, Item item) throws SQLException {
return helper.getAccessStatusFromItem(context, item, forever_date); return helper.getAccessStatusFromItem(context, item, forever_date, itemCalculationType);
} }
@Override @Override
public String getEmbargoFromItem(Context context, Item item) throws SQLException { public AccessStatus getAnonymousAccessStatus(Context context, Item item) throws SQLException {
return helper.getEmbargoFromItem(context, item, forever_date); 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; package org.dspace.access.status;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant; import java.time.LocalDate;
import java.util.Date; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory; import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.AuthorizeService;
import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.AccessStatus;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.Bundle; import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
@@ -26,21 +28,23 @@ import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group; 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. * 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 * The methods provides a simple logic to calculate the access status
* * retrieve embargo information of bitstreams from an item based on the policies of * of an item based on the policies of the primary or the first bitstream
* * the primary or the first bitstream in the original bundle. * in the original bundle. Users can override those methods for
* * Users can override this method for enhanced functionality. * enhanced functionality.
*/ */
public class DefaultAccessStatusHelper implements AccessStatusHelper { 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 EMBARGO = "embargo";
public static final String METADATA_ONLY = "metadata.only"; public static final String METADATA_ONLY = "metadata.only";
public static final String OPEN_ACCESS = "open.access"; public static final String OPEN_ACCESS = "open.access";
@@ -53,13 +57,15 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
AuthorizeServiceFactory.getInstance().getResourcePolicyService(); AuthorizeServiceFactory.getInstance().getResourcePolicyService();
protected AuthorizeService authorizeService = protected AuthorizeService authorizeService =
AuthorizeServiceFactory.getInstance().getAuthorizeService(); AuthorizeServiceFactory.getInstance().getAuthorizeService();
protected GroupService groupService =
EPersonServiceFactory.getInstance().getGroupService();
public DefaultAccessStatusHelper() { public DefaultAccessStatusHelper() {
super(); 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. * It is also considering a date threshold for embargoes and restrictions.
* *
* If the item is null, simply returns the "unknown" value. * 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 context the DSpace context
* @param item the item to check for embargoes * @param item the item to check for embargoes
* @param threshold the embargo threshold date * @param threshold the embargo threshold date
* @return an access status value * @param type the type of calculation
* @return the access status
*/ */
@Override @Override
public String getAccessStatusFromItem(Context context, Item item, Date threshold) public AccessStatus getAccessStatusFromItem(Context context, Item item, LocalDate threshold, String type)
throws SQLException { throws SQLException {
if (item == null) { 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. // Consider only the original bundles.
List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME); List<Bundle> bundles = item.getBundles(Constants.DEFAULT_BUNDLE_NAME);
// Check for primary bitstreams first. // Check for primary bitstreams first.
@@ -92,157 +154,159 @@ public class DefaultAccessStatusHelper implements AccessStatusHelper {
.findFirst() .findFirst()
.orElse(null); .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
*
* 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.
* *
* @param context the DSpace context * @param context the DSpace context
* @param dso the DSpace object * @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 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 availabilityDate the DSpace object availability date
* @param threshold the embargo threshold date * @param threshold the embargo threshold date
* @return an access status value * @return an access status value
*/ */
private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold) private String getAccessStatusFromAvailabilityDate(LocalDate availabilityDate, LocalDate threshold) {
throws SQLException { // If there is no availability date, it's an open access.
if (dso == null) { if (availabilityDate == null) {
return METADATA_ONLY; return OPEN_ACCESS;
} }
// 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 // If the policy start date have a value and if this value
// is equal or superior to the configured forever date, the // is equal or superior to the configured forever date, the
// access status is also restricted. // access status is also restricted.
restrictedCount++; if (!availabilityDate.isBefore(threshold)) {
} else {
// If the current date is not between the policy start date
// and end date, the access status is embargo.
embargoCount++;
}
}
}
}
if (openAccessCount > 0) {
return OPEN_ACCESS;
}
if (embargoCount > 0 && restrictedCount == 0) {
return EMBARGO;
}
if (unknownCount > 0) {
return UNKNOWN;
}
return RESTRICTED; return RESTRICTED;
} }
return EMBARGO;
/**
* 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;
} }
} }

View File

@@ -9,6 +9,8 @@ package org.dspace.access.status.service;
import java.sql.SQLException; import java.sql.SQLException;
import org.dspace.content.AccessStatus;
import org.dspace.content.Bitstream;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.core.Context; import org.dspace.core.Context;
@@ -40,18 +42,28 @@ public interface AccessStatusService {
* *
* @param context the DSpace context * @param context the DSpace context
* @param item the item * @param item the item
* @return an access status value * @return the access status
* @throws SQLException An exception that provides information on a database access error or other errors. * @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 context the DSpace context
* @param item the item to check for embargo information * @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. * @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.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.lang.time.DateUtils;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.ProcessStatus; import org.dspace.content.ProcessStatus;
import org.dspace.core.Context; 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 { private void performDeletion(Context context) throws SQLException, IOException, AuthorizeException {
List<ProcessStatus> statuses = getProcessToDeleteStatuses(); List<ProcessStatus> statuses = getProcessToDeleteStatuses();
Date creationDate = calculateCreationDate(); Instant creationDate = calculateCreationDate();
handler.logInfo("Searching for processes with status: " + statuses); handler.logInfo("Searching for processes with status: " + statuses);
List<Process> processes = processService.findByStatusAndCreationTimeOlderThan(context, statuses, creationDate); List<Process> processes = processService.findByStatusAndCreationTimeOlderThan(context, statuses, creationDate);
@@ -126,8 +126,8 @@ public class ProcessCleaner extends DSpaceRunnable<ProcessCleanerConfiguration<P
return statuses; return statuses;
} }
private Date calculateCreationDate() { private Instant calculateCreationDate() {
return DateUtils.addDays(new Date(), -days); return Instant.now().minus(days, ChronoUnit.DAYS);
} }
@Override @Override

View File

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

View File

@@ -13,7 +13,6 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
@@ -21,7 +20,15 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory; 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.apache.logging.log4j.Logger;
import org.dspace.app.util.XMLUtils;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.BitstreamFormat; import org.dspace.content.BitstreamFormat;
import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.factory.ContentServiceFactory;
@@ -41,7 +48,7 @@ import org.xml.sax.SAXException;
* <P> * <P>
* <code>RegistryLoader -bitstream bitstream-formats.xml</code> * <code>RegistryLoader -bitstream bitstream-formats.xml</code>
* <P> * <P>
* <code>RegistryLoader -dc dc-types.xml</code> * <code>RegistryLoader -metadata dc-types.xml</code>
* *
* @author Robert Tansley * @author Robert Tansley
* @version $Revision$ * @version $Revision$
@@ -50,7 +57,7 @@ public class RegistryLoader {
/** /**
* log4j category * 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() protected static BitstreamFormatService bitstreamFormatService = ContentServiceFactory.getInstance()
.getBitstreamFormatService(); .getBitstreamFormatService();
@@ -67,40 +74,55 @@ public class RegistryLoader {
* @throws Exception if error * @throws Exception if error
*/ */
public static void main(String[] argv) throws Exception { public static void main(String[] argv) throws Exception {
String usage = "Usage: " + RegistryLoader.class.getName() // Set up command-line options and parse arguments
+ " (-bitstream | -metadata) registry-file.xml"; CommandLineParser parser = new DefaultParser();
Options options = createCommandLineOptions();
Context context = null;
try { 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 // Can't update registries anonymously, so we need to turn off
// authorisation // authorisation
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
try {
// Work out what we're loading // Work out what we're loading
if (argv[0].equalsIgnoreCase("-bitstream")) { if (line.hasOption('b')) {
RegistryLoader.loadBitstreamFormats(context, argv[1]); String filename = line.getOptionValue('b');
} else if (argv[0].equalsIgnoreCase("-metadata")) { 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 // Call MetadataImporter, as it handles Metadata schema updates
MetadataImporter.loadRegistry(argv[1], true); MetadataImporter.loadRegistry(filename, true);
} else { } else {
System.err.println(usage); System.err.println("No registry type specified");
printHelp(options);
System.exit(1);
} }
// Commit changes and close Context // Commit changes and close Context
context.complete(); context.complete();
System.exit(0); System.exit(0);
} catch (ArrayIndexOutOfBoundsException ae) {
System.err.println(usage);
System.exit(1);
} catch (Exception e) { } catch (Exception e) {
log.fatal(LogHelper.getHeader(context, "error_loading_registries", log.fatal(LogHelper.getHeader(context, "error_loading_registries", ""), e);
""), e);
System.err.println("Error: \n - " + e.getMessage()); System.err.println("Error: \n - " + e.getMessage());
System.exit(1); System.exit(1);
} finally { } finally {
@@ -109,6 +131,40 @@ public class RegistryLoader {
context.abort(); context.abort();
} }
} }
} catch (ParseException e) {
System.err.println("Error parsing command-line arguments: " + e.getMessage());
printHelp(options);
System.exit(1);
}
}
/**
* 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);
} }
/** /**
@@ -210,8 +266,9 @@ public class RegistryLoader {
*/ */
private static Document loadXML(String filename) throws IOException, private static Document loadXML(String filename) throws IOException,
ParserConfigurationException, SAXException { ParserConfigurationException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance() // This XML builder will *not* disable external entities as XML
.newDocumentBuilder(); // registries are considered trusted content
DocumentBuilder builder = XMLUtils.getTrustedDocumentBuilder();
return builder.parse(new File(filename)); return builder.parse(new File(filename));
} }
@@ -221,7 +278,7 @@ public class RegistryLoader {
* contains: * contains:
* <P> * <P>
* <code> * <code>
* &lt;foo&gt;&lt;mimetype&gt;application/pdf&lt;/mimetype&gt;&lt;/foo&gt; * <foo><mimetype>application/pdf</mimetype></foo>
* </code> * </code>
* passing this the <code>foo</code> node and <code>mimetype</code> will * passing this the <code>foo</code> node and <code>mimetype</code> will
* return <code>application/pdf</code>. * return <code>application/pdf</code>.
@@ -262,10 +319,10 @@ public class RegistryLoader {
* document contains: * document contains:
* <P> * <P>
* <code> * <code>
* &lt;foo&gt; * <foo>
* &lt;bar&gt;val1&lt;/bar&gt; * <bar>val1</bar>
* &lt;bar&gt;val2&lt;/bar&gt; * <bar>val2</bar>
* &lt;/foo&gt; * </foo>
* </code> * </code>
* passing this the <code>foo</code> node and <code>bar</code> will * passing this the <code>foo</code> node and <code>bar</code> will
* return <code>val1</code> and <code>val2</code>. * 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.List;
import java.util.Map; import java.util.Map;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath; 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.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.dspace.app.util.XMLUtils;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection; import org.dspace.content.Collection;
import org.dspace.content.Community; import org.dspace.content.Community;
@@ -613,8 +613,8 @@ public class StructBuilder {
*/ */
private static org.w3c.dom.Document loadXML(InputStream input) private static org.w3c.dom.Document loadXML(InputStream input)
throws IOException, ParserConfigurationException, SAXException { throws IOException, ParserConfigurationException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance() // This builder factory does not disable external DTD, entities, etc.
.newDocumentBuilder(); DocumentBuilder builder = XMLUtils.getTrustedDocumentBuilder();
org.w3c.dom.Document document = builder.parse(input); org.w3c.dom.Document document = builder.parse(input);

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ package org.dspace.alerts.service;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Date; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import org.dspace.alerts.AllowSessionsEnum; import org.dspace.alerts.AllowSessionsEnum;
@@ -35,7 +35,7 @@ public interface SystemWideAlertService {
* @throws SQLException If something goes wrong * @throws SQLException If something goes wrong
*/ */
SystemWideAlert create(Context context, String message, AllowSessionsEnum allowSessionsType, SystemWideAlert create(Context context, String message, AllowSessionsEnum allowSessionsType,
Date countdownTo, boolean active ZonedDateTime countdownTo, boolean active
) throws SQLException, AuthorizeException; ) 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.sql.SQLException; import java.sql.SQLException;
import java.text.DateFormat; import java.time.LocalDate;
import java.text.SimpleDateFormat; import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -154,7 +154,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
} }
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(TimeZone.getTimeZone("UTC")); mapper.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
BulkAccessControlInput accessControl; BulkAccessControlInput accessControl;
context = new Context(Context.Mode.BATCH_EDIT); context = new Context(Context.Mode.BATCH_EDIT);
setEPerson(context); setEPerson(context);
@@ -312,7 +312,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
* check the validation of access condition, * check the validation of access condition,
* the access condition name must equal to one of configured access conditions, * the access condition name must equal to one of configured access conditions,
* then call {@link AccessConditionOption#validateResourcePolicy( * 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 * @param accessCondition the accessCondition
* @throws BulkAccessControlException if the accessCondition is invalid * @throws BulkAccessControlException if the accessCondition is invalid
@@ -416,7 +416,7 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
discoverQuery.setQuery(query); discoverQuery.setQuery(query);
discoverQuery.setStart(start); discoverQuery.setStart(start);
discoverQuery.setMaxResults(limit); discoverQuery.setMaxResults(limit);
discoverQuery.setSortField("search.resourceid", DiscoverQuery.SORT_ORDER.asc);
return discoverQuery; return discoverQuery;
} }
@@ -596,8 +596,8 @@ public class BulkAccessControl extends DSpaceRunnable<BulkAccessControlScriptCon
String name = accessCondition.getName(); String name = accessCondition.getName();
String description = accessCondition.getDescription(); String description = accessCondition.getDescription();
Date startDate = accessCondition.getStartDate(); LocalDate startDate = accessCondition.getStartDate();
Date endDate = accessCondition.getEndDate(); LocalDate endDate = accessCondition.getEndDate();
try { try {
accessConditionOption.createResourcePolicy(context, obj, name, description, startDate, endDate); 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) { private void AppendAccessConditionsInfo(StringBuilder message, List<AccessCondition> accessConditions) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); DateTimeFormatter dateFormat = DateTimeFormatter.ISO_LOCAL_DATE;
message.append("{"); message.append("{");
for (int i = 0; i < accessConditions.size(); i++) { for (int i = 0; i < accessConditions.size(); i++) {

View File

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

View File

@@ -19,6 +19,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -457,7 +458,7 @@ public class DSpaceCSV implements Serializable {
List<Collection> collections = i.getCollections(); List<Collection> collections = i.getCollections();
for (Collection c : collections) { for (Collection c : collections) {
// Only add if it is not the owning collection // 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()); 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); Iterator<Item> itemIterator = searchService.iteratorSearch(context, dso, discoverQuery);
handler.logDebug("creating dspacecsv"); 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.logDebug("writing to file " + getFileNameOrExportFile());
handler.writeFilestream(context, getFileNameOrExportFile(), dSpaceCSV.getInputStream(), EXPORT_CSV); handler.writeFilestream(context, getFileNameOrExportFile(), dSpaceCSV.getInputStream(), EXPORT_CSV);
context.restoreAuthSystemState(); context.restoreAuthSystemState();

View File

@@ -23,6 +23,7 @@ import java.util.UUID;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.util.RelationshipUtils; import org.dspace.app.util.RelationshipUtils;
@@ -89,7 +90,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
/** /**
* The authority controlled fields * The authority controlled fields
*/ */
protected static Set<String> authorityControlled; protected Set<String> authorityControlled;
/** /**
* The prefix of the authority controlled field * The prefix of the authority controlled field
@@ -742,10 +743,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
if (value == null || !value.contains(csv.getAuthoritySeparator())) { if (value == null || !value.contains(csv.getAuthoritySeparator())) {
simplyCopyValue(value, dcv); simplyCopyValue(value, dcv);
} else { } else {
String[] parts = value.split(csv.getAuthoritySeparator()); resolveValueAndAuthority(value, dcv);
dcv.setValue(parts[0]);
dcv.setAuthority(parts[1]);
dcv.setConfidence((parts.length > 2 ? Integer.valueOf(parts[2]) : Choices.CF_ACCEPTED));
} }
// fromAuthority==null: with the current implementation metadata values from external authority sources // 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 // 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; AuthorityValue authorityValue = null;
if (byValue.isEmpty()) { if (byValue.isEmpty()) {
String toGenerate = fromAuthority.generateString() + value; String toGenerate = fromAuthority.generateString() + value;
String field = schema + "_" + element + (StringUtils.isNotBlank(qualifier) ? "_" + qualifier : ""); String field = schema + "_" + element + (StringUtils.isNotBlank(qualifier) ? "_" + qualifier : "");
authorityValue = authorityValueService.generate(c, toGenerate, value, field); authorityValue = authorityValueService.generate(toGenerate, value, field);
dcv.setAuthority(toGenerate); dcv.setAuthority(toGenerate);
} else { } else {
authorityValue = byValue.get(0); authorityValue = byValue.get(0);
@@ -1162,10 +1160,7 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
} else if (value == null || !value.contains(csv.getAuthoritySeparator())) { } else if (value == null || !value.contains(csv.getAuthoritySeparator())) {
simplyCopyValue(value, dcv); simplyCopyValue(value, dcv);
} else { } else {
String[] parts = value.split(csv.getEscapedAuthoritySeparator()); resolveValueAndAuthority(value, dcv);
dcv.setValue(parts[0]);
dcv.setAuthority(parts[1]);
dcv.setConfidence((parts.length > 2 ? Integer.valueOf(parts[2]) : Choices.CF_ACCEPTED));
} }
return dcv; return dcv;
} }
@@ -1176,6 +1171,35 @@ public class MetadataImport extends DSpaceRunnable<MetadataImportScriptConfigura
dcv.setConfidence(Choices.CF_UNSET); 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 * 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 * 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; String mdf = md.contains(":") ? StringUtils.substringAfter(md, ":") : md;
mdf = StringUtils.substringBefore(mdf, "["); 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) { String targetType, String originType, String originTypeName) {
return RelationshipUtils.matchRelationshipType(relTypes, targetType, originType, 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.io.FileNotFoundException;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -149,7 +148,7 @@ public final class ChecksumChecker {
+ " old results from the database."); + " old results from the database.");
} }
Date processStart = Calendar.getInstance().getTime(); Instant processStart = Instant.now();
BitstreamDispatcher dispatcher = null; BitstreamDispatcher dispatcher = null;
@@ -180,10 +179,8 @@ public final class ChecksumChecker {
// run checker process for specified duration // run checker process for specified duration
try { try {
dispatcher = new LimitedDurationDispatcher( dispatcher = new LimitedDurationDispatcher(
new SimpleDispatcher(context, processStart, true), new Date( new SimpleDispatcher(context, processStart, true), Instant.ofEpochMilli(
System.currentTimeMillis() Instant.now().toEpochMilli() + Utils.parseDuration(line.getOptionValue('d'))));
+ Utils.parseDuration(line
.getOptionValue('d'))));
} catch (Exception e) { } catch (Exception e) {
LOG.fatal("Couldn't parse " + line.getOptionValue('d') LOG.fatal("Couldn't parse " + line.getOptionValue('d')
+ " as a duration: ", e); + " 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.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.sql.SQLException; 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.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@@ -678,7 +679,7 @@ public class ItemExportServiceImpl implements ItemExportService {
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
String fileName = assembleFileName("item", eperson, String fileName = assembleFileName("item", eperson,
new Date()); LocalDate.now());
String workParentDir = getExportWorkDirectory() String workParentDir = getExportWorkDirectory()
+ System.getProperty("file.separator") + System.getProperty("file.separator")
+ fileName; + fileName;
@@ -750,16 +751,16 @@ public class ItemExportServiceImpl implements ItemExportService {
@Override @Override
public String assembleFileName(String type, EPerson eperson, public String assembleFileName(String type, EPerson eperson,
Date date) throws Exception { LocalDate date) throws Exception {
// to format the date // to format the date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MMM_dd"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MMM_dd");
String downloadDir = getExportDownloadDirectory(eperson); String downloadDir = getExportDownloadDirectory(eperson);
// used to avoid name collision // used to avoid name collision
int count = 1; int count = 1;
boolean exists = true; boolean exists = true;
String fileName = null; String fileName = null;
while (exists) { while (exists) {
fileName = type + "_export_" + sdf.format(date) + "_" + count + "_" fileName = type + "_export_" + formatter.format(date) + "_" + count + "_"
+ eperson.getID(); + eperson.getID();
exists = new File(downloadDir exists = new File(downloadDir
+ System.getProperty("file.separator") + fileName + ".zip") + System.getProperty("file.separator") + fileName + ".zip")
@@ -915,14 +916,12 @@ public class ItemExportServiceImpl implements ItemExportService {
public void deleteOldExportArchives(EPerson eperson) throws Exception { public void deleteOldExportArchives(EPerson eperson) throws Exception {
int hours = configurationService int hours = configurationService
.getIntProperty("org.dspace.app.itemexport.life.span.hours"); .getIntProperty("org.dspace.app.itemexport.life.span.hours");
Calendar now = Calendar.getInstance(); Instant modifiedTime = Instant.now().minus(hours, ChronoUnit.HOURS);
now.setTime(new Date());
now.add(Calendar.HOUR, -hours);
File downloadDir = new File(getExportDownloadDirectory(eperson)); File downloadDir = new File(getExportDownloadDirectory(eperson));
if (downloadDir.exists()) { if (downloadDir.exists()) {
File[] files = downloadDir.listFiles(); File[] files = downloadDir.listFiles();
for (File file : files) { for (File file : files) {
if (file.lastModified() < now.getTimeInMillis()) { if (file.lastModified() < modifiedTime.toEpochMilli()) {
if (!file.delete()) { if (!file.delete()) {
logError("Unable to delete export file"); logError("Unable to delete export file");
} }
@@ -935,9 +934,7 @@ public class ItemExportServiceImpl implements ItemExportService {
@Override @Override
public void deleteOldExportArchives() throws Exception { public void deleteOldExportArchives() throws Exception {
int hours = configurationService.getIntProperty("org.dspace.app.itemexport.life.span.hours"); int hours = configurationService.getIntProperty("org.dspace.app.itemexport.life.span.hours");
Calendar now = Calendar.getInstance(); Instant modifiedTime = Instant.now().minus(hours, ChronoUnit.HOURS);
now.setTime(new Date());
now.add(Calendar.HOUR, -hours);
File downloadDir = new File(configurationService.getProperty("org.dspace.app.itemexport.download.dir")); File downloadDir = new File(configurationService.getProperty("org.dspace.app.itemexport.download.dir"));
if (downloadDir.exists()) { if (downloadDir.exists()) {
// Get a list of all the sub-directories, potentially one for each ePerson. // 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. // For each sub-directory delete any old files.
File[] files = dir.listFiles(); File[] files = dir.listFiles();
for (File file : files) { for (File file : files) {
if (file.lastModified() < now.getTimeInMillis()) { if (file.lastModified() < modifiedTime.toEpochMilli()) {
if (!file.delete()) { if (!file.delete()) {
logError("Unable to delete old files"); logError("Unable to delete old files");
} }

View File

@@ -8,7 +8,7 @@
package org.dspace.app.itemexport.service; package org.dspace.app.itemexport.service;
import java.io.InputStream; import java.io.InputStream;
import java.util.Date; import java.time.LocalDate;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -130,7 +130,7 @@ public interface ItemExportService {
* @throws Exception if error * @throws Exception if error
*/ */
public String assembleFileName(String type, EPerson eperson, 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.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.LineNumberReader; import java.io.LineNumberReader;
import java.text.SimpleDateFormat; import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
/** /**
@@ -23,7 +21,7 @@ import java.util.List;
*/ */
public class BatchUpload { public class BatchUpload {
private Date date; private Instant date;
private File dir; private File dir;
private boolean successful; private boolean successful;
private int itemsImported; private int itemsImported;
@@ -65,9 +63,7 @@ public class BatchUpload {
String dirName = dir.getName(); String dirName = dir.getName();
long timeMillis = Long.parseLong(dirName); long timeMillis = Long.parseLong(dirName);
Calendar calendar = new GregorianCalendar(); this.date = Instant.ofEpochMilli(timeMillis);
calendar.setTimeInMillis(timeMillis);
this.date = calendar.getTime();
try { try {
this.itemsImported = countLines(dir + File.separator + "mapfile"); this.itemsImported = countLines(dir + File.separator + "mapfile");
@@ -149,7 +145,7 @@ public class BatchUpload {
* *
* @return Date * @return Date
*/ */
public Date getDate() { public Instant getDate() {
return date; 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 * @return date as string
*/ */
public String getDateFormatted() { public String getDateFormatted() {
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy - HH:mm"); return DateTimeFormatter.ISO_INSTANT.format(date);
return df.format(date);
} }
/** /**

View File

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

View File

@@ -29,13 +29,15 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.URL; import java.net.URL;
import java.nio.file.Path;
import java.sql.SQLException; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -47,7 +49,6 @@ import java.util.UUID;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerException;
import javax.xml.xpath.XPath; 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.itemimport.service.ItemImportService;
import org.dspace.app.util.LocalSchemaFilenameFilter; import org.dspace.app.util.LocalSchemaFilenameFilter;
import org.dspace.app.util.RelationshipUtils; import org.dspace.app.util.RelationshipUtils;
import org.dspace.app.util.XMLUtils;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.AuthorizeService;
@@ -179,6 +181,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
@Autowired(required = true) @Autowired(required = true)
protected MetadataValueService metadataValueService; protected MetadataValueService metadataValueService;
protected DocumentBuilder builder;
protected String tempWorkDir; protected String tempWorkDir;
protected boolean isTest = false; protected boolean isTest = false;
@@ -742,15 +746,22 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
myitem = wi.getItem(); 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 // now fill out dublin core for item
loadMetadata(c, myitem, path + File.separatorChar + itemname loadMetadata(c, myitem, itemPathDir);
+ File.separatorChar);
// and the bitstreams from the contents file // and the bitstreams from the contents file
// process contents file, add bistreams and bundles, return any // process contents file, add bistreams and bundles, return any
// non-standard permissions // non-standard permissions
List<String> options = processContentsFile(c, myitem, path List<String> options = processContentsFile(c, myitem, itemPathDir, "contents");
+ File.separatorChar + itemname, "contents");
if (useWorkflow) { if (useWorkflow) {
// don't process handle file // don't process handle file
@@ -768,8 +779,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
} }
} else { } else {
// only process handle file if not using workflow system // only process handle file if not using workflow system
String myhandle = processHandleFile(c, myitem, path String myhandle = processHandleFile(c, myitem, itemPathDir, "handle");
+ File.separatorChar + itemname, "handle");
// put item in system // put item in system
if (!isTest) { 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 * Read the collections file inside the item directory. If there
* is one and it is not empty return a list of collections in * 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:", ""); sDescription = sDescription.replaceFirst("description:", "");
} }
validateFilePath(path, sFilePath);
registerBitstream(c, i, iAssetstore, sFilePath, sBundle, sDescription); registerBitstream(c, i, iAssetstore, sFilePath, sBundle, sDescription);
logInfo("\tRegistering Bitstream: " + sFilePath logInfo("\tRegistering Bitstream: " + sFilePath
+ "\tAssetstore: " + iAssetstore + "\tAssetstore: " + iAssetstore
@@ -1414,6 +1453,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
return; return;
} }
validateFilePath(path, fileName);
String fullpath = path + File.separatorChar + fileName; String fullpath = path + File.separatorChar + fileName;
// get an input stream // get an input stream
@@ -1425,11 +1465,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
if (bundleName == null) { if (bundleName == null) {
// is it license.txt? // is it license.txt?
if ("license.txt".equals(fileName)) { if (Constants.LICENSE_BITSTREAM_NAME.equals(fileName)) {
newBundleName = "LICENSE"; newBundleName = Constants.LICENSE_BUNDLE_NAME;
} else { } else {
// call it ORIGINAL // call it ORIGINAL
newBundleName = "ORIGINAL"; newBundleName = Constants.CONTENT_BUNDLE_NAME;
} }
} }
@@ -1493,11 +1533,11 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
if (StringUtils.isBlank(bundleName)) { if (StringUtils.isBlank(bundleName)) {
// is it license.txt? // is it license.txt?
if (bitstreamPath.endsWith("license.txt")) { if (bitstreamPath.endsWith(Constants.LICENSE_BITSTREAM_NAME)) {
newBundleName = "LICENSE"; newBundleName = Constants.LICENSE_BUNDLE_NAME;
} else { } else {
// call it ORIGINAL // 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, protected Document loadXML(String filename) throws IOException,
ParserConfigurationException, SAXException { ParserConfigurationException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance() DocumentBuilder builder = XMLUtils.getDocumentBuilder();
.newDocumentBuilder();
return builder.parse(new File(filename)); return builder.parse(new File(filename));
} }
@@ -2039,8 +2077,8 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
*/ */
protected String generateRandomFilename(boolean hidden) { protected String generateRandomFilename(boolean hidden) {
String filename = String.format("%s", RandomStringUtils.randomAlphanumeric(8)); String filename = String.format("%s", RandomStringUtils.randomAlphanumeric(8));
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmm"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm");
String datePart = sdf.format(new Date()); String datePart = formatter.format(LocalDateTime.now(ZoneOffset.UTC));
filename = datePart + "_" + filename; filename = datePart + "_" + filename;
return filename; return filename;
@@ -2102,8 +2140,7 @@ public class ItemImportServiceImpl implements ItemImportService, InitializingBea
"org.dspace.app.batchitemimport.work.dir") + File.separator + "batchuploads" + File.separator "org.dspace.app.batchitemimport.work.dir") + File.separator + "batchuploads" + File.separator
+ context + context
.getCurrentUser() .getCurrentUser()
.getID() + File.separator + (isResume ? theResumeDir : (new GregorianCalendar()) .getID() + File.separator + (isResume ? theResumeDir : Instant.now().toEpochMilli());
.getTimeInMillis());
File importDirFile = new File(importDir); File importDirFile = new File(importDir);
if (!importDirFile.exists()) { if (!importDirFile.exists()) {
boolean success = importDirFile.mkdirs(); 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.factory.ContentServiceFactory;
import org.dspace.content.service.BitstreamFormatService; import org.dspace.content.service.BitstreamFormatService;
import org.dspace.content.service.InstallItemService; import org.dspace.content.service.InstallItemService;
import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.eperson.Group; import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.factory.EPersonServiceFactory;
@@ -138,10 +139,10 @@ public class AddBitstreamsAction extends UpdateBitstreamsAction {
String newBundleName = ce.bundlename; String newBundleName = ce.bundlename;
if (ce.bundlename == null) { // should be required but default convention established if (ce.bundlename == null) { // should be required but default convention established
if (ce.filename.equals("license.txt")) { if (ce.filename.equals(Constants.LICENSE_BITSTREAM_NAME)) {
newBundleName = "LICENSE"; newBundleName = Constants.LICENSE_BUNDLE_NAME;
} else { } else {
newBundleName = "ORIGINAL"; newBundleName = Constants.CONTENT_BUNDLE_NAME;
} }
} }
ItemUpdate.pr(" Bitstream " + ce.filename + " to be added to bundle: " + newBundleName); 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.Iterator;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer; import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerConfigurationException;
@@ -33,6 +31,7 @@ import javax.xml.transform.TransformerFactory;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.util.LocalSchemaFilenameFilter; import org.dspace.app.util.LocalSchemaFilenameFilter;
import org.dspace.app.util.XMLUtils;
import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeException;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
import org.dspace.content.Item; import org.dspace.content.Item;
@@ -52,7 +51,6 @@ public class ItemArchive {
public static final String DUBLIN_CORE_XML = "dublin_core.xml"; public static final String DUBLIN_CORE_XML = "dublin_core.xml";
protected static DocumentBuilder builder = null;
protected Transformer transformer = null; protected Transformer transformer = null;
protected List<DtoMetadata> dtomList = null; protected List<DtoMetadata> dtomList = null;
@@ -95,14 +93,14 @@ public class ItemArchive {
InputStream is = null; InputStream is = null;
try { try {
is = new FileInputStream(new File(dir, DUBLIN_CORE_XML)); 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 //The code to search for local schema files was copied from org.dspace.app.itemimport
// .ItemImportServiceImpl.java // .ItemImportServiceImpl.java
File file[] = dir.listFiles(new LocalSchemaFilenameFilter()); File file[] = dir.listFiles(new LocalSchemaFilenameFilter());
for (int i = 0; i < file.length; i++) { for (int i = 0; i < file.length; i++) {
is = new FileInputStream(file[i]); is = new FileInputStream(file[i]);
itarch.dtomList.addAll(MetadataUtilities.loadDublinCore(getDocumentBuilder(), is)); itarch.dtomList.addAll(MetadataUtilities.loadDublinCore(XMLUtils.getDocumentBuilder(), is));
} }
} finally { } finally {
if (is != null) { if (is != null) {
@@ -126,14 +124,6 @@ public class ItemArchive {
return itarch; return itarch;
} }
protected static DocumentBuilder getDocumentBuilder()
throws ParserConfigurationException {
if (builder == null) {
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
return builder;
}
/** /**
* Getter for Transformer * Getter for Transformer
* *
@@ -318,7 +308,7 @@ public class ItemArchive {
try { try {
out = new FileOutputStream(new File(dir, "dublin_core.xml")); 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); MetadataUtilities.writeDocument(doc, getTransformer(), out);
// if undo has delete bitstream // if undo has delete bitstream

View File

@@ -14,9 +14,9 @@ import java.io.FileWriter;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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); context = new Context(Context.Mode.BATCH_EDIT);
iu.setEPerson(context, iu.eperson); iu.setEPerson(context, iu.eperson);

View File

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

View File

@@ -11,9 +11,9 @@ import static java.lang.String.format;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@@ -21,7 +21,6 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -48,6 +47,10 @@ import org.dspace.event.Event;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.utils.DSpace; 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; import org.dspace.web.ContextUtil;
/** /**
@@ -63,6 +66,9 @@ public class LDNMessageConsumer implements Consumer {
private ConfigurationService configurationService; private ConfigurationService configurationService;
private ItemService itemService; private ItemService itemService;
private BitstreamService bitstreamService; private BitstreamService bitstreamService;
private final String RESUBMISSION_SUFFIX = "-resubmission";
private final String ENDORSEMENT_PATTERN = "request-endorsement";
private final String REVIEW_PATTERN = "request-review";
@Override @Override
public void initialize() throws Exception { public void initialize() throws Exception {
@@ -83,6 +89,9 @@ public class LDNMessageConsumer implements Consumer {
} }
Item item = (Item) event.getSubject(context); Item item = (Item) event.getSubject(context);
if (item == null) {
return;
}
createManualLDNMessages(context, item); createManualLDNMessages(context, item);
createAutomaticLDNMessages(context, item); createAutomaticLDNMessages(context, item);
} }
@@ -90,10 +99,24 @@ public class LDNMessageConsumer implements Consumer {
private void createManualLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException { private void createManualLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException {
List<NotifyPatternToTrigger> patternsToTrigger = List<NotifyPatternToTrigger> patternsToTrigger =
notifyPatternToTriggerService.findByItem(context, item); 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) { 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(), 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) { for (NotifyServiceInboundPattern inboundPattern : inboundPatterns) {
if (StringUtils.isEmpty(inboundPattern.getConstraint()) || if (StringUtils.isEmpty(inboundPattern.getConstraint()) ||
evaluateFilter(context, item, 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) { private boolean evaluateFilter(Context context, Item item, String constraint) {
LogicalStatement filter = LogicalStatement filter =
new DSpace().getServiceManager().getServiceByName(constraint, LogicalStatement.class); new DSpace().getServiceManager().getServiceByName(constraint, LogicalStatement.class);
@@ -116,19 +161,37 @@ public class LDNMessageConsumer implements Consumer {
return filter != null && filter.getResult(context, item); return filter != null && filter.getResult(context, item);
} }
private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern) private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern,
throws SQLException, JsonMappingException, JsonProcessingException { String resubmissionID)
throws SQLException, JsonProcessingException {
LDN ldn = getLDNMessage(pattern); // 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 = LDNMessageEntity ldnMessage =
ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID())); ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID()));
ldnMessage.setObject(item); ldnMessage.setObject(item);
ldnMessage.setTarget(service); ldnMessage.setTarget(service);
ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED); 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(); ObjectMapper mapper = new ObjectMapper();
Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class); Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class);
@@ -139,6 +202,10 @@ public class LDNMessageConsumer implements Consumer {
Collections.sort(notificationTypeArrayList); Collections.sort(notificationTypeArrayList);
ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0)); ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1)); ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1));
// If a resubmission, set inReplyTo
if (resubmissionID != null) {
ldnMessage.setInReplyTo(ldnMessageService.find(context, resubmissionID));
}
ldnMessageService.update(context, ldnMessage); 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(); Item item = (Item) ldnMessage.getObject();
if (actorID != null) {
ldn.addArgument("mailto:" + actorID);
} else {
ldn.addArgument(getUiUrl()); ldn.addArgument(getUiUrl());
}
ldn.addArgument(configurationService.getProperty("ldn.notify.inbox")); 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().getUrl(), ""));
ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getLdnUrl(), "")); ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getLdnUrl(), ""));
ldn.addArgument(getUiUrl() + "/handle/" + ldnMessage.getObject().getHandle()); ldn.addArgument(getUiUrl() + "/handle/" + ldnMessage.getObject().getHandle());
@@ -166,6 +238,17 @@ public class LDNMessageConsumer implements Consumer {
ldn.addArgument(getRelationUri(item)); ldn.addArgument(getRelationUri(item));
ldn.addArgument("http://purl.org/vocab/frbr/core#supplement"); ldn.addArgument("http://purl.org/vocab/frbr/core#supplement");
ldn.addArgument(format("urn:uuid:%s", UUID.randomUUID())); 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()); ldnMessage.setMessage(ldn.generateLDNMessage());
} }

View File

@@ -8,7 +8,7 @@
package org.dspace.app.ldn; package org.dspace.app.ldn;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Date; import java.time.Instant;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
@@ -16,10 +16,10 @@ import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
import org.dspace.core.ReloadableEntity; 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, * 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") @Column(name = "queue_attempts")
private Integer queueAttempts = 0; private Integer queueAttempts = 0;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "queue_last_start_time") @Column(name = "queue_last_start_time")
private Date queueLastStartTime = null; private Instant queueLastStartTime = null;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "queue_timeout") @Column(name = "queue_timeout")
private Date queueTimeout = null; private Instant queueTimeout = null;
@ManyToOne @ManyToOne
@JoinColumn(name = "origin", referencedColumnName = "id") @JoinColumn(name = "origin", referencedColumnName = "id")
@@ -259,19 +257,19 @@ public class LDNMessageEntity implements ReloadableEntity<String> {
this.queueAttempts = queueAttempts; this.queueAttempts = queueAttempts;
} }
public Date getQueueLastStartTime() { public Instant getQueueLastStartTime() {
return queueLastStartTime; return queueLastStartTime;
} }
public void setQueueLastStartTime(Date queueLastStartTime) { public void setQueueLastStartTime(Instant queueLastStartTime) {
this.queueLastStartTime = queueLastStartTime; this.queueLastStartTime = queueLastStartTime;
} }
public Date getQueueTimeout() { public Instant getQueueTimeout() {
return queueTimeout; return queueTimeout;
} }
public void setQueueTimeout(Date queueTimeout) { public void setQueueTimeout(Instant queueTimeout) {
this.queueTimeout = queueTimeout; this.queueTimeout = queueTimeout;
} }
@@ -289,7 +287,11 @@ public class LDNMessageEntity implements ReloadableEntity<String> {
} }
public static String getNotificationType(LDNMessageEntity ldnMessage) { 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_INCOMING;
} }
return TYPE_OUTGOING; return TYPE_OUTGOING;

View File

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

View File

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

View File

@@ -9,9 +9,8 @@ package org.dspace.app.ldn.action;
import static java.lang.String.format; import static java.lang.String.format;
import java.text.SimpleDateFormat; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -34,8 +33,6 @@ public class LDNEmailAction implements LDNAction {
private static final Logger log = LogManager.getLogger(LDNEmailAction.class); private static final Logger log = LogManager.getLogger(LDNEmailAction.class);
private final static String DATE_PATTERN = "dd-MM-yyyy HH:mm:ss";
@Autowired @Autowired
private ConfigurationService configurationService; private ConfigurationService configurationService;
@@ -80,7 +77,7 @@ public class LDNEmailAction implements LDNAction {
email.addRecipient(recipient); 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(notification.getActor().getName());
email.addArgument(item.getName()); email.addArgument(item.getName());
@@ -139,7 +136,13 @@ public class LDNEmailAction implements LDNAction {
List<String> recipients = new LinkedList<String>(); List<String> recipients = new LinkedList<String>();
if (actionSendFilter.startsWith("SUBMITTER")) { if (actionSendFilter.startsWith("SUBMITTER")) {
if (item.getSubmitter() != null) {
recipients.add(item.getSubmitter().getEmail()); recipients.add(item.getSubmitter().getEmail());
} else {
// Fallback configured option
recipients.add(configurationService.getProperty("ldn.notification.email.submitter.fallback"));
}
} else if (actionSendFilter.startsWith("GROUP:")) { } else if (actionSendFilter.startsWith("GROUP:")) {
String groupName = actionSendFilter.replace("GROUP:", ""); String groupName = actionSendFilter.replace("GROUP:", "");
String property = format("email.%s.list", groupName); 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.math.BigDecimal;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Date; import java.time.Instant;
import java.util.Set; import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@@ -75,8 +75,7 @@ public class LDNRelationCorrectionAction implements LDNAction {
qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE, qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE,
handleService.findHandle(context, item), item.getID().toString(), itemName, handleService.findHandle(context, item), item.getID().toString(), itemName,
this.getQaEventTopic(), doubleScoreValue, this.getQaEventTopic(), doubleScoreValue,
mapper.writeValueAsString(message), mapper.writeValueAsString(message), Instant.now());
new Date());
qaEventService.store(context, qaEvent); qaEventService.store(context, qaEvent);
result = LDNActionStatus.CONTINUE; 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.client.methods.HttpPost;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient; 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.client.DSpaceHttpClientFactory;
import org.dspace.app.ldn.model.Notification; import org.dspace.app.ldn.model.Notification;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.core.Context; import org.dspace.core.Context;
@@ -34,22 +34,14 @@ public class SendLDNMessageAction implements LDNAction {
private static final Logger log = LogManager.getLogger(SendLDNMessageAction.class); private static final Logger log = LogManager.getLogger(SendLDNMessageAction.class);
private CloseableHttpClient client = null; private CloseableHttpClient client;
public SendLDNMessageAction() { public SendLDNMessageAction() {
HttpClientBuilder builder = HttpClientBuilder.create();
client = builder
.disableAutomaticRetries()
.setMaxConnTotal(5)
.build();
} }
public SendLDNMessageAction(CloseableHttpClient client) { public SendLDNMessageAction(CloseableHttpClient client) {
this();
if (client != null) {
this.client = client; this.client = client;
} }
}
@Override @Override
public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception { public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
@@ -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" // 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. // 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 // See the frontend configuration at [dspace.ui.url]/admin/ldn/services
try ( if (client == null) {
CloseableHttpResponse response = client.execute(httpPost); client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5);
) { }
try (CloseableHttpResponse response = client.execute(httpPost)) {
if (isSuccessful(response.getStatusLine().getStatusCode())) { if (isSuccessful(response.getStatusLine().getStatusCode())) {
result = LDNActionStatus.CONTINUE; result = LDNActionStatus.CONTINUE;
} else if (isRedirect(response.getStatusLine().getStatusCode())) { } else if (isRedirect(response.getStatusLine().getStatusCode())) {
@@ -77,6 +70,7 @@ public class SendLDNMessageAction implements LDNAction {
} catch (Exception e) { } catch (Exception e) {
log.error(e); log.error(e);
} }
client.close();
return result; return result;
} }
@@ -91,9 +85,9 @@ public class SendLDNMessageAction implements LDNAction {
statusCode == HttpStatus.SC_TEMPORARY_REDIRECT; statusCode == HttpStatus.SC_TEMPORARY_REDIRECT;
} }
private LDNActionStatus handleRedirect(CloseableHttpResponse oldresponse, private LDNActionStatus handleRedirect(CloseableHttpResponse oldResponse,
HttpPost request) throws HttpException { 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; String url = urls.length > 0 && urls[0] != null ? urls[0].getValue() : null;
if (url == null) { if (url == null) {
throw new HttpException("Error following redirect, unable to reach" throw new HttpException("Error following redirect, unable to reach"
@@ -102,17 +96,14 @@ public class SendLDNMessageAction implements LDNAction {
LDNActionStatus result = LDNActionStatus.ABORT; LDNActionStatus result = LDNActionStatus.ABORT;
try { try {
request.setURI(new URI(url)); request.setURI(new URI(url));
try ( try (CloseableHttpResponse response = client.execute(request)) {
CloseableHttpResponse response = client.execute(request);
) {
if (isSuccessful(response.getStatusLine().getStatusCode())) { if (isSuccessful(response.getStatusLine().getStatusCode())) {
return LDNActionStatus.CONTINUE; result = LDNActionStatus.CONTINUE;
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Error following redirect:", e); log.error("Error following redirect:", e);
} }
return result;
return LDNActionStatus.ABORT;
} }
} }

View File

@@ -8,8 +8,8 @@
package org.dspace.app.ldn.dao.impl; package org.dspace.app.ldn.dao.impl;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -47,7 +47,7 @@ public class LDNMessageDaoImpl extends AbstractHibernateDAO<LDNMessageEntity> im
andPredicates andPredicates
.add(criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_QUEUED)); .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_.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[] {}))); criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
List<Order> orderList = new LinkedList<>(); List<Order> orderList = new LinkedList<>();
orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts))); orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
@@ -94,7 +94,7 @@ public class LDNMessageDaoImpl extends AbstractHibernateDAO<LDNMessageEntity> im
andPredicates.add( andPredicates.add(
criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSING)); criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSING));
andPredicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(LDNMessageEntity_.queueAttempts), max_attempts)); 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[] {}))); criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
List<Order> orderList = new LinkedList<>(); List<Order> orderList = new LinkedList<>();
orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts))); orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
@@ -149,8 +149,11 @@ public class LDNMessageDaoImpl extends AbstractHibernateDAO<LDNMessageEntity> im
Predicate activityPredicate = null; Predicate activityPredicate = null;
andPredicates.add( andPredicates.add(
criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED)); 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( 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) { if (activities != null && activities.length > 0) {
activityPredicate = root.get(LDNMessageEntity_.activityStreamType).in(activities); activityPredicate = root.get(LDNMessageEntity_.activityStreamType).in(activities);
andPredicates.add(activityPredicate); andPredicates.add(activityPredicate);

View File

@@ -8,11 +8,12 @@
package org.dspace.app.ldn.model; package org.dspace.app.ldn.model;
/** /**
* REQUESTED means acknowledgements not received yet * REQUESTED means acknowledgements not received yet
* ACCEPTED means acknowledgements of "Accept" type received * ACCEPTED means acknowledgements of "Accept" or "TentativeAccept" type received
* REJECTED means ack of "TentativeReject" 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) * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
*/ */
public enum NotifyRequestStatusEnum { 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.http.client.HttpResponseException;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 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.LDNAction;
import org.dspace.app.ldn.action.LDNActionStatus; import org.dspace.app.ldn.action.LDNActionStatus;
import org.dspace.app.ldn.model.Notification; import org.dspace.app.ldn.model.Notification;
@@ -59,6 +60,8 @@ public class LDNMetadataProcessor implements LDNProcessor {
"Announce", "Announce",
"TentativeReject", "TentativeReject",
"Accept", "Accept",
"TentativeAccept",
"Reject",
"coar-notify:ReviewAction", "coar-notify:ReviewAction",
"coar-notify:IngestAction", "coar-notify:IngestAction",
"coar-notify:EndorsementAction"); "coar-notify:EndorsementAction");
@@ -168,7 +171,22 @@ public class LDNMetadataProcessor implements LDNProcessor {
String url = null; String url = null;
if (CONTEXT_ID_ITEM_TYPES.containsAll(notification.getType())) { if (CONTEXT_ID_ITEM_TYPES.containsAll(notification.getType())) {
if (notification.getContext() != null) {
url = notification.getContext().getId(); 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())) { } else if (OBJECT_ID_ITEM_TYPES.containsAll(notification.getType())) {
url = notification.getObject().getId(); url = notification.getObject().getId();
} else if (OBJECT_SUBJECT_ITEM_TYPES.containsAll(notification.getType())) { } 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; 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 * 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.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -22,7 +23,6 @@ import java.util.UUID;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.ldn.LDNMessageEntity; import org.dspace.app.ldn.LDNMessageEntity;
@@ -145,6 +145,12 @@ public class LDNMessageServiceImpl implements LDNMessageService {
ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0)); ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
if (notificationTypeArrayList.size() > 1) { if (notificationTypeArrayList.size() > 1) {
ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(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.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
ldnMessage.setSourceIp(sourceIp); ldnMessage.setSourceIp(sourceIp);
@@ -157,7 +163,7 @@ public class LDNMessageServiceImpl implements LDNMessageService {
ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED_IP); ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED_IP);
} }
} }
ldnMessage.setQueueTimeout(new Date()); ldnMessage.setQueueTimeout(Instant.now());
update(context, ldnMessage); update(context, ldnMessage);
return ldnMessage; return ldnMessage;
@@ -282,9 +288,9 @@ public class LDNMessageServiceImpl implements LDNMessageService {
msg.setQueueAttempts(msg.getQueueAttempts() + 1); msg.setQueueAttempts(msg.getQueueAttempts() + 1);
update(context, msg); update(context, msg);
} else { } else {
msg.setQueueLastStartTime(new Date()); msg.setQueueLastStartTime(Instant.now());
msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSING); msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSING);
msg.setQueueTimeout(DateUtils.addMinutes(new Date(), timeoutInMinutes)); msg.setQueueTimeout(Instant.now().plus(timeoutInMinutes, ChronoUnit.MINUTES));
update(context, msg); update(context, msg);
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
Notification notification = mapper.readValue(msg.getMessage(), Notification.class); Notification notification = mapper.readValue(msg.getMessage(), Notification.class);
@@ -368,18 +374,23 @@ public class LDNMessageServiceImpl implements LDNMessageService {
offer.setServiceUrl(nse == null ? "" : nse.getUrl()); offer.setServiceUrl(nse == null ? "" : nse.getUrl());
offer.setOfferType(LDNUtils.getNotifyType(msg.getCoarNotifyType())); offer.setOfferType(LDNUtils.getNotifyType(msg.getCoarNotifyType()));
List<LDNMessageEntity> acks = ldnMessageDao.findAllRelatedMessagesByItem( 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()) { if (acks == null || acks.isEmpty()) {
offer.setStatus(NotifyRequestStatusEnum.REQUESTED); 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() } else if (acks.stream()
.filter(c -> (c.getActivityStreamType().equalsIgnoreCase("TentativeAccept") || .filter(c -> (c.getActivityStreamType().equalsIgnoreCase("TentativeAccept") ||
c.getActivityStreamType().equalsIgnoreCase("Accept"))) c.getActivityStreamType().equalsIgnoreCase("Accept")))
.findAny().isPresent()) { .findAny().isPresent()) {
offer.setStatus(NotifyRequestStatusEnum.ACCEPTED); offer.setStatus(NotifyRequestStatusEnum.ACCEPTED);
} else if (acks.stream()
.filter(c -> c.getActivityStreamType().equalsIgnoreCase("TentativeReject"))
.findAny().isPresent()) {
offer.setStatus(NotifyRequestStatusEnum.REJECTED);
} }
if (acks.stream().filter( if (acks.stream().filter(
c -> c.getActivityStreamType().equalsIgnoreCase("Announce")) c -> c.getActivityStreamType().equalsIgnoreCase("Announce"))
@@ -391,6 +402,32 @@ public class LDNMessageServiceImpl implements LDNMessageService {
return result; 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 { public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException {
ldnMessageDao.delete(context, ldnMessage); ldnMessageDao.delete(context, ldnMessage);
} }

View File

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

View File

@@ -38,6 +38,8 @@ import org.xml.sax.SAXException;
public class TikaTextExtractionFilter public class TikaTextExtractionFilter
extends MediaFilter { extends MediaFilter {
private final static Logger log = LogManager.getLogger(); 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 @Override
public String getFilteredName(String oldFilename) { 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. // 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; 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 { try {
// Use Tika to extract text from input. Tika will automatically detect the file type. // Use Tika to extract text from input. Tika will automatically detect the file type.
Tika tika = new Tika(); Tika tika = new Tika();
tika.setMaxStringLength(maxChars); // Tell Tika the maximum number of characters to extract 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); extractedText = tika.parseToString(source);
} catch (IOException e) { } catch (IOException e) {
System.err.format("Unable to extract text from bitstream in Item %s%n", currentItem.getID().toString()); System.err.format("Unable to extract text from bitstream in Item %s%n", currentItem.getID().toString());
@@ -141,7 +144,7 @@ public class TikaTextExtractionFilter
@Override @Override
public void characters(char[] ch, int start, int length) throws SAXException { public void characters(char[] ch, int start, int length) throws SAXException {
try { try {
writer.append(new String(ch), start, length); writer.append(new String(ch, start, length));
} catch (IOException e) { } catch (IOException e) {
String errorMsg = String.format("Could not append to temporary file at %s " + String errorMsg = String.format("Could not append to temporary file at %s " +
"when performing text extraction", "when performing text extraction",
@@ -159,7 +162,7 @@ public class TikaTextExtractionFilter
@Override @Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
try { try {
writer.append(new String(ch), start, length); writer.append(new String(ch, start, length));
} catch (IOException e) { } catch (IOException e) {
String errorMsg = String.format("Could not append to temporary file at %s " + String errorMsg = String.format("Could not append to temporary file at %s " +
"when performing text extraction", "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(); AutoDetectParser parser = new AutoDetectParser();
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
// parse our source InputStream using the above custom handler // parse our source InputStream using the above custom handler

View File

@@ -7,7 +7,7 @@
*/ */
package org.dspace.app.requestitem; package org.dspace.app.requestitem;
import java.util.Date; import java.time.Instant;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
@@ -19,8 +19,6 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator; import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.core.Context; import org.dspace.core.Context;
@@ -63,20 +61,23 @@ public class RequestItem implements ReloadableEntity<Integer> {
private boolean allfiles; private boolean allfiles;
@Column(name = "decision_date") @Column(name = "decision_date")
@Temporal(TemporalType.TIMESTAMP) private Instant decision_date = null;
private Date decision_date = null;
@Column(name = "expires") @Column(name = "expires")
@Temporal(TemporalType.TIMESTAMP) private Instant expires = null;
private Date expires = null;
@Column(name = "request_date") @Column(name = "request_date")
@Temporal(TemporalType.TIMESTAMP) private Instant request_date = null;
private Date request_date = null;
@Column(name = "accept_request") @Column(name = "accept_request")
private boolean 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: * Protected constructor, create object using:
* {@link org.dspace.app.requestitem.service.RequestItemService#createRequest( * {@link org.dspace.app.requestitem.service.RequestItemService#createRequest(
@@ -90,7 +91,7 @@ public class RequestItem implements ReloadableEntity<Integer> {
return requestitem_id; return requestitem_id;
} }
void setAllfiles(boolean allfiles) { public void setAllfiles(boolean allfiles) {
this.allfiles = 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() { public String getToken() {
return token; return token;
@@ -161,11 +163,11 @@ public class RequestItem implements ReloadableEntity<Integer> {
return bitstream; return bitstream;
} }
public Date getDecision_date() { public Instant getDecision_date() {
return decision_date; return decision_date;
} }
public void setDecision_date(Date decision_date) { public void setDecision_date(Instant decision_date) {
this.decision_date = decision_date; this.decision_date = decision_date;
} }
@@ -177,19 +179,53 @@ public class RequestItem implements ReloadableEntity<Integer> {
this.accept_request = accept_request; this.accept_request = accept_request;
} }
public Date getExpires() { public Instant getExpires() {
return expires; return expires;
} }
void setExpires(Date expires) { void setExpires(Instant expires) {
this.expires = expires; this.expires = expires;
} }
public Date getRequest_date() { public Instant getRequest_date() {
return request_date; return request_date;
} }
void setRequest_date(Date request_date) { void setRequest_date(Instant request_date) {
this.request_date = 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.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import jakarta.annotation.ManagedBean; import jakarta.annotation.ManagedBean;
@@ -28,6 +30,7 @@ import org.dspace.core.Context;
import org.dspace.core.Email; import org.dspace.core.Email;
import org.dspace.core.I18nUtil; import org.dspace.core.I18nUtil;
import org.dspace.core.LogHelper; import org.dspace.core.LogHelper;
import org.dspace.core.Utils;
import org.dspace.eperson.EPerson; import org.dspace.eperson.EPerson;
import org.dspace.handle.service.HandleService; import org.dspace.handle.service.HandleService;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
@@ -174,9 +177,23 @@ public class RequestItemEmailNotifier {
grantorAddress = grantor.getEmail(); grantorAddress = grantor.getEmail();
} }
// Build an email back to the requester. // Set date format for access expiry date
Email email = Email.getEmail(I18nUtil.getEmailFilename(context.getCurrentLocale(), 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")); ri.isAccept_request() ? "request_item.granted" : "request_item.rejected"));
}
// Build an email back to the requester.
email.addArgument(ri.getReqName()); // {0} requestor's name email.addArgument(ri.getReqName()); // {0} requestor's name
email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); // {1} URL of the requested Item email.addArgument(handleService.getCanonicalForm(ri.getItem().getHandle())); // {1} URL of the requested Item
email.addArgument(ri.getItem().getName()); // {2} title of the requested Item email.addArgument(ri.getItem().getName()); // {2} title of the requested Item
@@ -188,6 +205,18 @@ public class RequestItemEmailNotifier {
// Attach bitstreams. // Attach bitstreams.
try { try {
if (ri.isAccept_request()) { if (ri.isAccept_request()) {
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 {
if (ri.isAllfiles()) { if (ri.isAllfiles()) {
Item item = ri.getItem(); Item item = ri.getItem();
List<Bundle> bundles = item.getBundles("ORIGINAL"); List<Bundle> bundles = item.getBundles("ORIGINAL");
@@ -210,13 +239,14 @@ public class RequestItemEmailNotifier {
} }
} else { } else {
Bitstream bitstream = ri.getBitstream(); Bitstream bitstream = ri.getBitstream();
// #8636 Anyone receiving the email can respond to the request without authenticating into DSpace //#8636 Anyone receiving the email can respond to the request without authenticating into DSpace
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
email.addAttachment(bitstreamService.retrieve(context, bitstream), email.addAttachment(bitstreamService.retrieve(context, bitstream),
bitstream.getName(), bitstream.getName(),
bitstream.getFormat(context).getMIMEType()); bitstream.getFormat(context).getMIMEType());
context.restoreAuthSystemState(); context.restoreAuthSystemState();
} }
}
email.send(); email.send();
} else { } else {
boolean sendRejectEmail = configurationService boolean sendRejectEmail = configurationService

View File

@@ -7,25 +7,40 @@
*/ */
package org.dspace.app.requestitem; package org.dspace.app.requestitem;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException; 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.Iterator;
import java.util.List; 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.requestitem.dao.RequestItemDAO; import org.dspace.app.requestitem.dao.RequestItemDAO;
import org.dspace.app.requestitem.service.RequestItemService; import org.dspace.app.requestitem.service.RequestItemService;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy; import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.AuthorizeService;
import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.Bundle;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.core.LogHelper; import org.dspace.core.LogHelper;
import org.dspace.core.Utils; 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; 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. * This class should never be accessed directly.
* *
* @author kevinvandevelde at atmire.com * @author kevinvandevelde at atmire.com
* @author Kim Shepherd
*/ */
public class RequestItemServiceImpl implements RequestItemService { public class RequestItemServiceImpl implements RequestItemService {
@@ -49,16 +65,43 @@ public class RequestItemServiceImpl implements RequestItemService {
@Autowired(required = true) @Autowired(required = true)
protected ResourcePolicyService resourcePolicyService; 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() { 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 @Override
public String createRequest(Context context, Bitstream bitstream, Item item, public String createRequest(Context context, Bitstream bitstream, Item item,
boolean allFiles, String reqEmail, String reqName, String reqMessage) boolean allFiles, String reqEmail, String reqName, String reqMessage)
throws SQLException { throws SQLException {
// Create an empty request item
RequestItem requestItem = requestItemDAO.create(context, new RequestItem()); RequestItem requestItem = requestItemDAO.create(context, new RequestItem());
// Set values of the request item based on supplied parameters
requestItem.setToken(Utils.generateHexKey()); requestItem.setToken(Utils.generateHexKey());
requestItem.setBitstream(bitstream); requestItem.setBitstream(bitstream);
requestItem.setItem(item); requestItem.setItem(item);
@@ -66,12 +109,58 @@ public class RequestItemServiceImpl implements RequestItemService {
requestItem.setReqEmail(reqEmail); requestItem.setReqEmail(reqEmail);
requestItem.setReqName(reqName); requestItem.setReqName(reqName);
requestItem.setReqMessage(reqMessage); 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); requestItemDAO.save(context, requestItem);
log.debug("Created RequestItem with ID {} and token {}", log.debug("Created RequestItem with ID {}, approval token {}, access token {}, access expiry {}",
requestItem::getID, requestItem::getToken); requestItem::getID, requestItem::getToken, requestItem::getAccess_token, requestItem::getAccess_expiry);
// Return the approver token
return requestItem.getToken(); return requestItem.getToken();
} }
@@ -128,4 +217,186 @@ public class RequestItemServiceImpl implements RequestItemService {
} }
return true; 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> { 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 context the current DSpace context.
* @param token uniquely identifies the request. * @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; 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; 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)); criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.token), token));
return uniqueResult(context, criteriaQuery, false, RequestItem.class); 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 @Override
public Iterator<RequestItem> findByItem(Context context, Item item) throws SQLException { public Iterator<RequestItem> findByItem(Context context, Item item) throws SQLException {
CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);

View File

@@ -8,13 +8,19 @@
/** /**
* Feature for conveying a request that materials forbidden to the requester * Feature for conveying a request that materials forbidden to the requester
* by resource policy be made available by other means. The request will be * by resource policy be made available by other means.
* e-mailed to a responsible party for consideration and action. Find details *
* in the user documentation under the rubric "Request a Copy". * 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 * <p>Mailing is handled by {@link RequestItemEmailNotifier}. Responsible
* parties are represented by {@link RequestItemAuthor} * 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 * <p>This package includes several "strategy" classes which discover
* responsible parties in various ways. See * responsible parties in various ways. See
* {@link RequestItemSubmitterStrategy} and the classes which extend it, and * {@link RequestItemSubmitterStrategy} and the classes which extend it, and

View File

@@ -7,11 +7,15 @@
*/ */
package org.dspace.app.requestitem.service; package org.dspace.app.requestitem.service;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.dspace.app.requestitem.RequestItem; import org.dspace.app.requestitem.RequestItem;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
import org.dspace.content.Item; import org.dspace.content.Item;
@@ -23,6 +27,7 @@ import org.dspace.core.Context;
* for the RequestItem object and is autowired by Spring. * for the RequestItem object and is autowired by Spring.
* *
* @author kevinvandevelde at atmire.com * @author kevinvandevelde at atmire.com
* @author Kim Shepherd
*/ */
public interface RequestItemService { public interface RequestItemService {
@@ -40,7 +45,7 @@ public interface RequestItemService {
* @return the token of the request item * @return the token of the request item
* @throws SQLException if database error * @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) boolean allFiles, String reqEmail, String reqName, String reqMessage)
throws SQLException; throws SQLException;
@@ -49,35 +54,46 @@ public interface RequestItemService {
* *
* @param context current DSpace session. * @param context current DSpace session.
* @return all item requests. * @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; throws SQLException;
/** /**
* Retrieve a request by its token. * Retrieve a request by its approver token.
* *
* @param context current DSpace session. * @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. * @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. * Retrieve a request based on the item.
* @param context current DSpace session. * @param context current DSpace session.
* @param item the item to find requests for. * @param item the item to find requests for.
* @return the matching requests, or null if not found. * @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 context The relevant DSpace Context.
* @param requestItem requested item * @param requestItem requested item
*/ */
public void update(Context context, RequestItem requestItem); void update(Context context, RequestItem requestItem);
/** /**
* Remove the record from the database. * Remove the record from the database.
@@ -85,7 +101,7 @@ public interface RequestItemService {
* @param context current DSpace context. * @param context current DSpace context.
* @param request record to be removed. * @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? * 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. * @return true if a READ policy applies.
* @throws SQLException passed through. * @throws SQLException passed through.
*/ */
public boolean isRestricted(Context context, DSpaceObject o) boolean isRestricted(Context context, DSpaceObject o)
throws SQLException; 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.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.dspace.app.sfx.service.SFXFileReaderService; import org.dspace.app.sfx.service.SFXFileReaderService;
import org.dspace.app.util.XMLUtils;
import org.dspace.content.DCPersonName; import org.dspace.content.DCPersonName;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.MetadataValue; import org.dspace.content.MetadataValue;
@@ -79,9 +80,9 @@ public class SFXFileReaderServiceImpl implements SFXFileReaderService {
log.info("Parsing XML file... " + fileName); log.info("Parsing XML file... " + fileName);
DocumentBuilder docBuilder; DocumentBuilder docBuilder;
Document doc = null; Document doc = null;
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setIgnoringElementContentWhitespace(true);
try { try {
DocumentBuilderFactory docBuilderFactory = XMLUtils.getDocumentBuilderFactory();
docBuilderFactory.setIgnoringElementContentWhitespace(true);
docBuilder = docBuilderFactory.newDocumentBuilder(); docBuilder = docBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) { } catch (ParserConfigurationException e) {
log.error("Wrong parser configuration: " + e.getMessage()); 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.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig; 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.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder; import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient; 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.LogManager;
import org.apache.logging.log4j.Logger; 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.SHERPAPublisherResponse;
import org.dspace.app.sherpa.v2.SHERPAResponse; import org.dspace.app.sherpa.v2.SHERPAResponse;
import org.dspace.app.sherpa.v2.SHERPAUtils; import org.dspace.app.sherpa.v2.SHERPAUtils;
@@ -45,8 +45,6 @@ import org.springframework.cache.annotation.Cacheable;
*/ */
public class SHERPAService { public class SHERPAService {
private CloseableHttpClient client = null;
private int maxNumberOfTries; private int maxNumberOfTries;
private long sleepBetweenTimeouts; private long sleepBetweenTimeouts;
private int timeout = 5000; private int timeout = 5000;
@@ -59,19 +57,6 @@ public class SHERPAService {
@Autowired @Autowired
ConfigurationService configurationService; 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. * Complete initialization of the Bean.
*/ */
@@ -132,14 +117,14 @@ public class SHERPAService {
timeout, timeout,
sleepBetweenTimeouts)); sleepBetweenTimeouts));
try { try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5)) {
Thread.sleep(sleepBetweenTimeouts); Thread.sleep(sleepBetweenTimeouts);
// Construct a default HTTP method (first result) // Construct a default HTTP method (first result)
method = constructHttpGet(type, field, predicate, value, start, limit); method = constructHttpGet(type, field, predicate, value, start, limit);
// Execute the method // Execute the method
HttpResponse response = client.execute(method); try (CloseableHttpResponse response = client.execute(method)) {
int statusCode = response.getStatusLine().getStatusCode(); int statusCode = response.getStatusLine().getStatusCode();
log.debug(response.getStatusLine().getStatusCode() + ": " log.debug(response.getStatusLine().getStatusCode() + ": "
@@ -173,6 +158,7 @@ public class SHERPAService {
log.debug("Empty SHERPA response body for query on " + value); log.debug("Empty SHERPA response body for query on " + value);
sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO returned no response"); sherpaResponse = new SHERPAPublisherResponse("SHERPA/RoMEO returned no response");
} }
}
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
String errorMessage = "Error building SHERPA v2 API URI: " + e.getMessage(); String errorMessage = "Error building SHERPA v2 API URI: " + e.getMessage();
log.error(errorMessage, e); log.error(errorMessage, e);
@@ -235,14 +221,14 @@ public class SHERPAService {
timeout, timeout,
sleepBetweenTimeouts)); sleepBetweenTimeouts));
try { try (CloseableHttpClient client = DSpaceHttpClientFactory.getInstance().buildWithoutAutomaticRetries(5)) {
Thread.sleep(sleepBetweenTimeouts); Thread.sleep(sleepBetweenTimeouts);
// Construct a default HTTP method (first result) // Construct a default HTTP method (first result)
method = constructHttpGet(type, field, predicate, value, start, limit); method = constructHttpGet(type, field, predicate, value, start, limit);
// Execute the method // Execute the method
HttpResponse response = client.execute(method); try (CloseableHttpResponse response = client.execute(method)) {
int statusCode = response.getStatusLine().getStatusCode(); int statusCode = response.getStatusLine().getStatusCode();
log.debug(response.getStatusLine().getStatusCode() + ": " log.debug(response.getStatusLine().getStatusCode() + ": "
@@ -275,6 +261,7 @@ public class SHERPAService {
log.debug("Empty SHERPA response body for query on " + value); log.debug("Empty SHERPA response body for query on " + value);
sherpaResponse = new SHERPAResponse("SHERPA/RoMEO returned no response"); sherpaResponse = new SHERPAResponse("SHERPA/RoMEO returned no response");
} }
}
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
String errorMessage = "Error building SHERPA v2 API URI: " + e.getMessage(); String errorMessage = "Error building SHERPA v2 API URI: " + e.getMessage();
log.error(errorMessage, e); log.error(errorMessage, e);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
*/ */
package org.dspace.app.statistics; 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 * 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 * the date of the log file line
*/ */
private Date date = null; private LocalDate date = null;
/** /**
* the level of the log line type * the level of the log line type
@@ -47,7 +47,7 @@ public class LogLine {
/** /**
* constructor to create new statistic * 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.date = date;
this.level = level; this.level = level;
this.user = user; this.user = user;
@@ -60,8 +60,8 @@ public class LogLine {
* *
* @return the date of this log line * @return the date of this log line
*/ */
public Date getDate() { public LocalDate getDate() {
return this.date == null ? null : new Date(this.date.getTime()); return this.date;
} }
@@ -108,12 +108,12 @@ public class LogLine {
/** /**
* find out if this log file line is before the given date * 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 * @return true if the line is before the given date, false if not
*/ */
public boolean beforeDate(Date date) { public boolean beforeDate(LocalDate compareDate) {
if (date != null) { if (compareDate != null) {
return (date.compareTo(this.date) >= 0); return this.date.isBefore(compareDate);
} }
return false; return false;
} }
@@ -122,12 +122,12 @@ public class LogLine {
/** /**
* find out if this log file line is after the given date * 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 * @return true if the line is after the given date, false if not
*/ */
public boolean afterDate(Date date) { public boolean afterDate(LocalDate compareDate) {
if (date != null) { if (compareDate != null) {
return (date.compareTo(this.date) <= 0); return this.date.isAfter(compareDate);
} }
return false; return false;
} }

View File

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

View File

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

View File

@@ -13,6 +13,7 @@ import java.util.UUID;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.SolrServerException;
import org.dspace.content.Item;
import org.dspace.content.service.ItemService; import org.dspace.content.service.ItemService;
import org.dspace.core.Context; import org.dspace.core.Context;
import org.dspace.external.model.ExternalDataObject; import org.dspace.external.model.ExternalDataObject;
@@ -137,4 +138,16 @@ public abstract class SolrSuggestionProvider implements SuggestionProvider {
*/ */
protected abstract boolean isExternalDataObjectPotentiallySuggested(Context context, protected abstract boolean isExternalDataObjectPotentiallySuggested(Context context,
ExternalDataObject externalDataObject); 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; 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 * an evidence / fact that has been used to evaluate the precision of a suggestion increasing or decreasing the score
* of the suggestion. * of the suggestion.
* *

View File

@@ -5,7 +5,7 @@
* *
* http://www.dspace.org/license/ * 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.getAllEntriesByMetadatum;
import static org.dspace.app.suggestion.SuggestionUtils.getFirstEntryByMetadatum; 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.SolrSuggestionProvider;
import org.dspace.app.suggestion.Suggestion; import org.dspace.app.suggestion.Suggestion;
import org.dspace.app.suggestion.SuggestionEvidence; 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.Item;
import org.dspace.content.dto.MetadataValueDTO; import org.dspace.content.dto.MetadataValueDTO;
import org.dspace.core.Context; import org.dspace.core.Context;
@@ -28,23 +30,23 @@ import org.dspace.services.ConfigurationService;
import org.springframework.beans.factory.annotation.Autowired; 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) * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
* *
*/ */
public class PublicationLoader extends SolrSuggestionProvider { 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 @Autowired
private ConfigurationService configurationService; protected ConfigurationService configurationService;
private List<EvidenceScorer> pipeline; protected List<EvidenceScorer> pipeline;
public void setPrimaryProvider(ExternalDataProvider primaryProvider) { public void setPrimaryProvider(ExternalDataProvider primaryProvider) {
this.primaryProvider = primaryProvider; this.primaryProvider = primaryProvider;
@@ -66,7 +68,7 @@ public class PublicationLoader extends SolrSuggestionProvider {
* This method filter a list of ImportRecords using a pipeline of AuthorNamesApprover * This method filter a list of ImportRecords using a pipeline of AuthorNamesApprover
* and return a filtered list of ImportRecords. * and return a filtered list of ImportRecords.
* *
* @see org.dspace.app.suggestion.openaire.AuthorNamesScorer * @see AuthorNamesScorer
* @param researcher the researcher Item * @param researcher the researcher Item
* @param importRecords List of import record * @param importRecords List of import record
* @return a list of filtered import records * @return a list of filtered import records
@@ -103,8 +105,9 @@ public class PublicationLoader extends SolrSuggestionProvider {
* @throws SolrServerException * @throws SolrServerException
* @throws IOException * @throws IOException
*/ */
public void importAuthorRecords(Context context, Item researcher) @Override
throws SolrServerException, IOException { public void importRecords(Context context, Item researcher)
throws Exception {
int offset = 0; int offset = 0;
int limit = 10; int limit = 10;
int loaded = limit; int loaded = limit;
@@ -132,18 +135,20 @@ public class PublicationLoader extends SolrSuggestionProvider {
* @return Suggestion * @return Suggestion
*/ */
private Suggestion translateImportRecordToSuggestion(Item item, ExternalDataObject record) { private Suggestion translateImportRecordToSuggestion(Item item, ExternalDataObject record) {
String openAireId = record.getId(); String recordId = record.getId();
Suggestion suggestion = new Suggestion(getSourceName(), item, openAireId); Suggestion suggestion = new Suggestion(getSourceName(), item, recordId);
suggestion.setDisplay(getFirstEntryByMetadatum(record, "dc", "title", null)); suggestion.setDisplay(getFirstEntryByMetadatum(record, "dc", "title", null));
suggestion.getMetadata().add( 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, 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, 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") suggestion.setExternalSourceUri(configurationService.getProperty("dspace.server.url")
+ "/api/integration/externalsources/" + primaryProvider.getSourceIdentifier() + "/entryValues/" + "/api/integration/externalsources/" +
+ openAireId); primaryProvider.getSourceIdentifier() + "/entryValues/"
+ recordId);
for (String o : getAllEntriesByMetadatum(record, "dc", "source", null)) { for (String o : getAllEntriesByMetadatum(record, "dc", "source", null)) {
suggestion.getMetadata().add(new MetadataValueDTO("dc", "source", null, null, o)); 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 * 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 OpenAIRE. * get from metadata key defined in class level variable names as author to query external source.
* *
* @see org.dspace.importer.external.openaire.service.OpenAireImportMetadataSourceServiceImpl * @see org.dspace.importer.external.service.AbstractImportMetadataSourceService
* @param searchValues query * @param searchValues query
* @param researcher item to extract metadata from * @param researcher item to extract metadata from
* @param limit for pagination purpose * @param limit for pagination purpose
@@ -230,7 +235,7 @@ public class PublicationLoader extends SolrSuggestionProvider {
* @param researcher DSpace item * @param researcher DSpace item
* @return list of metadata values * @return list of metadata values
*/ */
private List<String> searchMetadataValues(Item researcher) { public List<String> searchMetadataValues(Item researcher) {
List<String> authors = new ArrayList<String>(); List<String> authors = new ArrayList<String>();
for (String name : names) { for (String name : names) {
String value = itemService.getMetadata(researcher, name); String value = itemService.getMetadata(researcher, name);
@@ -247,7 +252,8 @@ public class PublicationLoader extends SolrSuggestionProvider {
return true; return true;
} else if (otherProviders != null) { } else if (otherProviders != null) {
return otherProviders.stream() return otherProviders.stream()
.anyMatch(x -> StringUtils.equals(externalDataObject.getSource(), x.getSourceIdentifier())); .anyMatch(
x -> StringUtils.equals(externalDataObject.getSource(), x.getSourceIdentifier()));
} else { } else {
return false; 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/ * 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; import static org.dspace.app.suggestion.SuggestionUtils.getAllEntriesByMetadatum;
@@ -137,6 +137,8 @@ public class AuthorNamesScorer implements EvidenceScorer {
* */ * */
private String normalize(String value) { private String normalize(String value) {
String norm = Normalizer.normalize(value, Normalizer.NFD); String norm = Normalizer.normalize(value, Normalizer.NFD);
// Removes diacritical marks
norm = norm.replaceAll("\\p{M}", "");
CharsetDetector cd = new CharsetDetector(); CharsetDetector cd = new CharsetDetector();
cd.setText(value.getBytes()); cd.setText(value.getBytes());
CharsetMatch detect = cd.detect(); CharsetMatch detect = cd.detect();

View File

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

View File

@@ -5,7 +5,7 @@
* *
* http://www.dspace.org/license/ * 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.app.suggestion.SuggestionEvidence;
import org.dspace.content.Item; import org.dspace.content.Item;
@@ -32,6 +32,6 @@ public interface EvidenceScorer {
* @return the generated suggestion evidence or null if the record should be * @return the generated suggestion evidence or null if the record should be
* discarded * 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/ * 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.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) * @author Alessandro Martelli (alessandro.martelli at 4science.it)
*/ */
public class PublicationLoaderCliScriptConfiguration<T extends PublicationLoaderRunnable> public class PublicationLoaderCliScriptConfiguration<T extends PublicationLoaderRunnable>
extends PublicationLoaderScriptConfiguration<T> { 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 @Override
public Options getOptions() { public Options getOptions() {
Options options = super.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; package org.dspace.app.util;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.time.Instant;
import java.util.Date;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -35,7 +34,7 @@ abstract public class AbstractDSpaceWebapp
protected String kind; protected String kind;
protected Date started; protected Instant started;
protected String url; protected String url;
@@ -55,7 +54,7 @@ abstract public class AbstractDSpaceWebapp
public AbstractDSpaceWebapp(String kind) { public AbstractDSpaceWebapp(String kind) {
this.kind = kind; this.kind = kind;
started = new Date(); started = Instant.now();
ConfigurationService configurationService ConfigurationService configurationService
= DSpaceServicesFactory.getInstance().getConfigurationService(); = DSpaceServicesFactory.getInstance().getConfigurationService();
@@ -70,10 +69,9 @@ abstract public class AbstractDSpaceWebapp
*/ */
public void register() { public void register() {
// Create the database entry // Create the database entry
Timestamp now = new Timestamp(started.getTime());
try { try {
Context context = new Context(); 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(); context.complete();
} catch (SQLException e) { } catch (SQLException e) {
log.error("Failed to record startup in Webapp table.", 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.List;
import java.util.Map; import java.util.Map;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.FactoryConfigurationError;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -118,15 +117,17 @@ public class DCInputsReader {
formDefns = new HashMap<String, List<List<Map<String, String>>>>(); formDefns = new HashMap<String, List<List<Map<String, String>>>>();
valuePairs = new HashMap<String, List<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 { try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // This document builder will *not* disable external
factory.setValidating(false); // entities as they can be useful in managing large forms, but
factory.setIgnoringComments(true); // it will restrict them to be within the directory that the
factory.setIgnoringElementContentWhitespace(true); // current input form XML file exists (or a sub-directory)
DocumentBuilder db = XMLUtils.getTrustedDocumentBuilder(inputFileDir);
DocumentBuilder db = factory.newDocumentBuilder();
Document doc = db.parse(uri); Document doc = db.parse(uri);
doNodes(doc); doNodes(doc);
checkValues(); checkValues();
@@ -379,7 +380,7 @@ public class DCInputsReader {
} }
// sanity check number of fields // sanity check number of fields
if (fields.size() < 1) { 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.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
@@ -64,20 +63,36 @@ public class InitializeEntities {
*/ */
public static void main(String[] argv) throws SQLException, AuthorizeException, ParseException { public static void main(String[] argv) throws SQLException, AuthorizeException, ParseException {
InitializeEntities initializeEntities = new InitializeEntities(); InitializeEntities initializeEntities = new InitializeEntities();
// Set up command-line options and parse arguments
CommandLineParser parser = new DefaultParser(); CommandLineParser parser = new DefaultParser();
Options options = createCommandLineOptions(); Options options = createCommandLineOptions();
CommandLine line = parser.parse(options,argv); 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); checkHelpEntered(options, line);
// Get the file location from the command line
String fileLocation = getFileLocationFromCommandLine(line);
// Run the script
initializeEntities.run(fileLocation); 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) { private static void checkHelpEntered(Options options, CommandLine line) {
if (line.hasOption("h")) { if (line.hasOption("h") || !line.hasOption("f")) {
HelpFormatter formatter = new HelpFormatter(); HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("Initialize Entities", options); formatter.printHelp("Initialize Entities", options);
System.exit(0); 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) { private static String getFileLocationFromCommandLine(CommandLine line) {
String query = line.getOptionValue("f"); String query = line.getOptionValue("f");
if (StringUtils.isEmpty(query)) { if (StringUtils.isEmpty(query)) {
@@ -88,13 +103,25 @@ public class InitializeEntities {
return query; return query;
} }
/**
* Create the command-line options
* @return the command-line options
*/
protected static Options createCommandLineOptions() { protected static Options createCommandLineOptions() {
Options options = new Options(); 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; 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 { private void run(String fileLocation) throws SQLException, AuthorizeException {
Context context = new Context(); Context context = new Context();
context.turnOffAuthorisationSystem(); context.turnOffAuthorisationSystem();
@@ -102,11 +129,18 @@ public class InitializeEntities {
context.complete(); 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 { private void parseXMLToRelations(Context context, String fileLocation) throws AuthorizeException {
try { try {
File fXmlFile = new File(fileLocation); File fXmlFile = new File(fileLocation);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); // This XML builder will allow external entities, so the relationship types XML should
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); // be considered trusted by administrators
DocumentBuilder dBuilder = XMLUtils.getTrustedDocumentBuilder();
Document doc = dBuilder.parse(fXmlFile); Document doc = dBuilder.parse(fXmlFile);
doc.getDocumentElement().normalize(); doc.getDocumentElement().normalize();
@@ -158,15 +192,15 @@ public class InitializeEntities {
for (int j = 0; j < leftCardinalityList.getLength(); j++) { for (int j = 0; j < leftCardinalityList.getLength(); j++) {
Node node = leftCardinalityList.item(j); Node node = leftCardinalityList.item(j);
leftCardinalityMin = getString(leftCardinalityMin,(Element) node, "min"); leftCardinalityMin = getCardinalityMinString(leftCardinalityMin,(Element) node, "min");
leftCardinalityMax = getString(leftCardinalityMax,(Element) node, "max"); leftCardinalityMax = getCardinalityMinString(leftCardinalityMax,(Element) node, "max");
} }
for (int j = 0; j < rightCardinalityList.getLength(); j++) { for (int j = 0; j < rightCardinalityList.getLength(); j++) {
Node node = rightCardinalityList.item(j); Node node = rightCardinalityList.item(j);
rightCardinalityMin = getString(rightCardinalityMin,(Element) node, "min"); rightCardinalityMin = getCardinalityMinString(rightCardinalityMin,(Element) node, "min");
rightCardinalityMax = getString(rightCardinalityMax,(Element) node, "max"); rightCardinalityMax = getCardinalityMinString(rightCardinalityMax,(Element) node, "max");
} }
populateRelationshipType(context, leftType, rightType, leftwardType, rightwardType, 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) { if (node.getElementsByTagName(minOrMax).getLength() > 0) {
leftCardinalityMin = node.getElementsByTagName(minOrMax).item(0).getTextContent(); leftCardinalityMin = node.getElementsByTagName(minOrMax).item(0).getTextContent();
} }
return leftCardinalityMin; 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, private void populateRelationshipType(Context context, String leftType, String rightType, String leftwardType,
String rightwardType, String leftCardinalityMin, String leftCardinalityMax, String rightwardType, String leftCardinalityMin, String leftCardinalityMax,
String rightCardinalityMin, String rightCardinalityMax, String rightCardinalityMin, String rightCardinalityMax,

View File

@@ -100,6 +100,14 @@ public class OpenSearchServiceImpl implements OpenSearchService {
configurationService.getProperty("websvc.opensearch.uicontext"); 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 @Override
public String getContentType(String format) { public String getContentType(String format) {
return "html".equals(format) ? "text/html" : return "html".equals(format) ? "text/html" :

View File

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

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