diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 9893d233e16f..8f061cab8866 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -7,16 +7,16 @@ assignees: ''
---
-**Describe the bug**
+## Describe the bug
A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem. Link to examples if they are public.
-**To Reproduce**
+## To Reproduce
Steps to reproduce the behavior:
1. Do this
2. Then this...
-**Expected behavior**
+## Expected behavior
A clear and concise description of what you expected to happen.
-**Related work**
+## Related work
Link to any related tickets or PRs here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 34cc2c9e4f38..9eaa4d9f3f8f 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -7,14 +7,14 @@ assignees: ''
---
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+## Is your feature request related to a problem? Please describe.
+A clear and concise description of what the problem or use case is. For example, I'm always frustrated when [...]
-**Describe the solution you'd like**
+## Describe the solution you'd like
A clear and concise description of what you want to happen.
-**Describe alternatives or workarounds you've considered**
+## Describe alternatives or workarounds you've considered
A clear and concise description of any alternative solutions or features you've considered.
-**Additional context**
-Add any other context or screenshots about the feature request here.
+## Additional information
+Add any other information, related tickets or screenshots about the feature request here.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000000..b6412b25b660
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,118 @@
+#-------------------
+# DSpace's dependabot rules. Enables maven updates for all dependencies on a weekly basis
+# for main and any maintenance branches. Security updates only apply to main.
+#-------------------
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ # 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.plugins:*"
+ exclude-patterns:
+ # Exclude anything from Spring, as that is in a separate group
+ - "org.springframework.*:*"
+ update-types:
+ - "minor"
+ - "patch"
+ test-tools:
+ applies-to: version-updates
+ patterns:
+ - "junit:*"
+ - "com.github.stefanbirker:system-rules"
+ - "com.h2database:*"
+ - "io.findify:s3mock*"
+ - "io.netty:*"
+ - "org.hamcrest:*"
+ - "org.mock-server:*"
+ - "org.mockito:*"
+ 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:
+ - "minor"
+ - "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 Google deps in a single PR
+ google-apis:
+ applies-to: version-updates
+ patterns:
+ - "com.google.apis:*"
+ - "com.google.api-client:*"
+ - "com.google.http-client:*"
+ - "com.google.oauth-client:*"
+ 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"
+ ignore:
+ # Don't try to auto-update any DSpace dependencies
+ - dependency-name: "org.dspace:*"
+ - dependency-name: "org.dspace.*:*"
+ # Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
+ - dependency-name: "*"
+ update-types: ["version-update:semver-major"]
diff --git a/.github/disabled-workflows/pull_request_opened.yml b/.github/disabled-workflows/pull_request_opened.yml
deleted file mode 100644
index 0dc718c0b9a3..000000000000
--- a/.github/disabled-workflows/pull_request_opened.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-# This workflow runs whenever a new pull request is created
-# TEMPORARILY DISABLED. Unfortunately this doesn't work for PRs created from forked repositories (which is how we tend to create PRs).
-# There is no known workaround yet. See https://github.community/t/how-to-use-github-token-for-prs-from-forks/16818
-name: Pull Request opened
-
-# Only run for newly opened PRs against the "main" branch
-on:
- pull_request:
- types: [opened]
- branches:
- - main
-
-jobs:
- automation:
- runs-on: ubuntu-latest
- steps:
- # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
- # See https://github.com/marketplace/actions/pull-request-assigner
- - name: Assign PR to creator
- uses: thomaseizinger/assign-pr-creator-action@v1.0.0
- # Note, this authentication token is created automatically
- # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- # Ignore errors. It is possible the PR was created by someone who cannot be assigned
- continue-on-error: true
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 5b3f4336e6a2..4da35876d2ec 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,7 +1,7 @@
## References
_Add references/links to any related issues or PRs. These may include:_
-* Fixes #`issue-number` (if this fixes an issue ticket)
-* Related to DSpace/RestContract#`pr-number` (if a corresponding REST Contract PR exists)
+* Fixes #issue-number (if this fixes an issue ticket)
+* Related to DSpace/RestContract#pr-number (if a corresponding REST Contract PR exists)
## Description
Short summary of changes (1-2 sentences).
@@ -16,12 +16,15 @@ List of changes in this PR:
**Include guidance for how to test or review your PR.** This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes.
## Checklist
-_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_
+_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome).
+However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_
-- [ ] My PR is small in size (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon.
-- [ ] My PR passes Checkstyle validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide).
-- [ ] My PR includes Javadoc for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods.
-- [ ] My PR passes all tests and includes new/updated Unit or Integration Tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
+- [ ] My PR is **created against the `main` branch** of code (unless it is a backport or is fixing an issue specific to an older branch).
+- [ ] My PR is **small in size** (e.g. less than 1,000 lines of code, not including comments & integration tests). Exceptions may be made if previously agreed upon.
+- [ ] My PR **passes Checkstyle** validation based on the [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide).
+- [ ] My PR **includes Javadoc** for _all new (or modified) public methods and classes_. It also includes Javadoc for large or complex private methods.
+- [ ] My PR **passes all tests and includes new/updated Unit or Integration Tests** based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
+- [ ] My PR **includes details on how to test it**. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
- [ ] If my PR includes new libraries/dependencies (in any `pom.xml`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
- [ ] If my PR modifies REST API endpoints, I've opened a separate [REST Contract](https://github.com/DSpace/RestContract/blob/main/README.md) PR related to this change.
- [ ] If my PR includes new configurations, I've provided basic technical documentation in the PR itself.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 99c9efe0190f..39a6f41429fd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,11 +21,11 @@ jobs:
# Also specify version of Java to use (this can allow us to optionally run tests on multiple JDKs in future)
matrix:
include:
- # NOTE: Unit Tests include deprecated REST API v6 (as it has unit tests)
+ # NOTE: Unit Tests include a retry for occasionally failing tests
# - surefire.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries
- type: "Unit Tests"
- java: 11
- mvnflags: "-DskipUnitTests=false -Pdspace-rest -Dsurefire.rerunFailingTestsCount=2"
+ java: 17
+ mvnflags: "-DskipUnitTests=false -Dsurefire.rerunFailingTestsCount=2"
resultsdir: "**/target/surefire-reports/**"
# NOTE: ITs skip all code validation checks, as they are already done by Unit Test job.
# - enforcer.skip => Skip maven-enforcer-plugin rules
@@ -34,7 +34,7 @@ jobs:
# - xml.skip => Skip all XML/XSLT validation by xml-maven-plugin
# - failsafe.rerunFailingTestsCount => try again for flakey tests, and keep track of/report on number of retries
- type: "Integration Tests"
- java: 11
+ java: 17
mvnflags: "-DskipIntegrationTests=false -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true -Dfailsafe.rerunFailingTestsCount=2"
resultsdir: "**/target/failsafe-reports/**"
# Do NOT exit immediately if one matrix job fails
@@ -45,24 +45,15 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
# https://github.com/actions/setup-java
- name: Install JDK ${{ matrix.java }}
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'
-
- # https://github.com/actions/cache
- - name: Cache Maven dependencies
- uses: actions/cache@v3
- with:
- # Cache entire ~/.m2/repository
- path: ~/.m2/repository
- # Cache key is hash of all pom.xml files. Therefore any changes to POMs will invalidate cache
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: ${{ runner.os }}-maven-
+ cache: 'maven'
# Run parallel Maven builds based on the above 'strategy.matrix'
- name: Run Maven ${{ matrix.type }}
@@ -74,14 +65,14 @@ jobs:
# (This artifact is downloadable at the bottom of any job's summary page)
- name: Upload Results of ${{ matrix.type }} to Artifact
if: ${{ failure() }}
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: ${{ matrix.type }} results
path: ${{ matrix.resultsdir }}
# Upload code coverage report to artifact, so that it can be shared with the 'codecov' job (see below)
- name: Upload code coverage report to Artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: ${{ matrix.type }} coverage report
path: 'dspace/target/site/jacoco-aggregate/jacoco.xml'
@@ -96,11 +87,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
# Download artifacts from previous 'tests' job
- name: Download coverage artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
# Now attempt upload to Codecov using its action.
# NOTE: We use a retry action to retry the Codecov upload if it fails the first time.
@@ -108,10 +99,14 @@ jobs:
# Retry action: https://github.com/marketplace/actions/retry-action
# Codecov action: https://github.com/codecov/codecov-action
- name: Upload coverage to Codecov.io
- uses: Wandalen/wretry.action@v1.0.36
+ uses: Wandalen/wretry.action@v1.3.0
with:
- action: codecov/codecov-action@v3
- # Try upload 5 times max
+ action: codecov/codecov-action@v4
+ # Ensure codecov-action throws an error when it fails to upload
+ with: |
+ fail_ci_if_error: true
+ token: ${{ secrets.CODECOV_TOKEN }}
+ # Try re-running action 5 times max
attempt_limit: 5
# Run again in 30 seconds
attempt_delay: 30000
diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml
index 7580b4ba3dc3..3a563c6fa39c 100644
--- a/.github/workflows/codescan.yml
+++ b/.github/workflows/codescan.yml
@@ -5,12 +5,16 @@
# because CodeQL requires a fresh build with all tests *disabled*.
name: "Code Scanning"
-# Run this code scan for all pushes / PRs to main branch. Also run once a week.
+# Run this code scan for all pushes / PRs to main or maintenance branches. Also run once a week.
on:
push:
- branches: [ main ]
+ branches:
+ - main
+ - 'dspace-**'
pull_request:
- branches: [ main ]
+ branches:
+ - main
+ - 'dspace-**'
# Don't run if PR is only updating static documentation
paths-ignore:
- '**/*.md'
@@ -31,13 +35,13 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
# https://github.com/actions/setup-java
- name: Install JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: 'temurin'
# Initializes the CodeQL tools for scanning.
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 971954a5e1ee..9d32cb119d41 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -3,6 +3,7 @@ name: Docker images
# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
+# NOTE: uses "reusable-docker-build.yml" to actually build each of the Docker images.
on:
push:
branches:
@@ -14,242 +15,235 @@ on:
permissions:
contents: read # to fetch code (actions/checkout)
+ packages: write # to write images to GitHub Container Registry (GHCR)
jobs:
- docker:
+ ####################################################
+ # Build/Push the 'dspace/dspace-dependencies' image.
+ # This image is used by all other DSpace build jobs.
+ ####################################################
+ dspace-dependencies:
+ # 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-dependencies
+ image_name: dspace/dspace-dependencies
+ dockerfile_path: ./Dockerfile.dependencies
+ secrets:
+ DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+ DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
+
+ #######################################
+ # Build/Push the 'dspace/dspace' image
+ #######################################
+ dspace:
+ # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
+ if: github.repository == 'dspace/dspace'
+ # Must run after 'dspace-dependencies' job above
+ needs: dspace-dependencies
+ uses: ./.github/workflows/reusable-docker-build.yml
+ with:
+ build_id: dspace-prod
+ image_name: dspace/dspace
+ dockerfile_path: ./Dockerfile
+ secrets:
+ DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+ DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
+ # Enable redeploy of sandbox & demo if the branch for this image matches the deployment branch of
+ # these sites as specified in reusable-docker-build.xml
+ REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
+ REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
+
+ #############################################################
+ # Build/Push the 'dspace/dspace' image ('-test' tag)
+ #############################################################
+ dspace-test:
+ # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
+ if: github.repository == 'dspace/dspace'
+ # Must run after 'dspace-dependencies' job above
+ needs: dspace-dependencies
+ uses: ./.github/workflows/reusable-docker-build.yml
+ with:
+ build_id: dspace-test
+ image_name: dspace/dspace
+ dockerfile_path: ./Dockerfile.test
+ # As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same
+ # tagging logic as the primary 'dspace/dspace' image above.
+ tags_flavor: suffix=-test
+ secrets:
+ DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+ DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
+
+ ###########################################
+ # Build/Push the 'dspace/dspace-cli' image
+ ###########################################
+ dspace-cli:
+ # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
+ if: github.repository == 'dspace/dspace'
+ # Must run after 'dspace-dependencies' job above
+ needs: dspace-dependencies
+ uses: ./.github/workflows/reusable-docker-build.yml
+ with:
+ build_id: dspace-cli
+ image_name: dspace/dspace-cli
+ dockerfile_path: ./Dockerfile.cli
+ secrets:
+ DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+ DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
+
+ ###########################################
+ # Build/Push the 'dspace/dspace-solr' image
+ ###########################################
+ dspace-solr:
+ # 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-solr
+ image_name: dspace/dspace-solr
+ dockerfile_path: ./dspace/src/main/docker/dspace-solr/Dockerfile
+ # Must pass solrconfigs to the Dockerfile so that it can find the required Solr config files
+ dockerfile_additional_contexts: 'solrconfigs=./dspace/solr/'
+ secrets:
+ DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+ DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
+ # Enable redeploy of sandbox & demo SOLR instance whenever dspace-solr image changes for deployed branch.
+ # These URLs MUST use different secrets than 'dspace/dspace' image build above as they are deployed separately.
+ REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_SOLR_URL }}
+ REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_SOLR_URL }}
+
+ ###########################################################
+ # Build/Push the 'dspace/dspace-postgres-pgcrypto' image
+ ###########################################################
+ dspace-postgres-pgcrypto:
+ # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
+ if: github.repository == 'dspace/dspace'
+ uses: ./.github/workflows/reusable-docker-build.yml
+ with:
+ build_id: dspace-postgres-pgcrypto-prod
+ image_name: dspace/dspace-postgres-pgcrypto
+ # Must build out of subdirectory to have access to install script for pgcrypto.
+ # NOTE: this context will build the image based on the Dockerfile in the specified directory
+ dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
+ secrets:
+ DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+ DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
+
+ ########################################################################
+ # Build/Push the 'dspace/dspace-postgres-pgcrypto' image (-loadsql tag)
+ ########################################################################
+ dspace-postgres-pgcrypto-loadsql:
+ # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
+ if: github.repository == 'dspace/dspace'
+ uses: ./.github/workflows/reusable-docker-build.yml
+ with:
+ build_id: dspace-postgres-pgcrypto-loadsql
+ image_name: dspace/dspace-postgres-pgcrypto
+ # Must build out of subdirectory to have access to install script for pgcrypto.
+ # NOTE: this context will build the image based on the Dockerfile in the specified directory
+ dockerfile_context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/
+ # Suffix all tags with "-loadsql". Otherwise, it uses the same
+ # tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above.
+ tags_flavor: suffix=-loadsql
+ secrets:
+ DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
+ DOCKER_ACCESS_TOKEN: ${{ secrets.DOCKER_ACCESS_TOKEN }}
+
+ #################################################################################
+ # Test Deployment via Docker to ensure newly built images are working properly
+ #################################################################################
+ docker-deploy:
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace'
if: github.repository == 'dspace/dspace'
runs-on: ubuntu-latest
+ # Must run after all major images are built
+ needs: [dspace, dspace-test, dspace-cli, dspace-postgres-pgcrypto, dspace-solr]
env:
- # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
- # For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image.
- # For a new commit on other branches, use the branch name as the tag for Docker image.
- # For a new tag, copy that tag name as the tag for Docker image.
- IMAGE_TAGS: |
- type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
- type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
- type=ref,event=tag
- # Define default tag "flavor" for docker/metadata-action per
- # https://github.com/docker/metadata-action#flavor-input
- # We turn off 'latest' tag by default.
- TAGS_FLAVOR: |
- latest=false
- # Architectures / Platforms for which we will build Docker images
- # 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 NOT a PR (e.g. a tag or merge commit), also build for ARM64. NOTE: The ARM64 build takes MUCH
- # longer (around 45mins or so) which is why we only run it when pushing a new Docker image.
- PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }}
-
+ # 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
+ # Enable all optional modules / controllers for this test deployment.
+ # This helps check for errors in deploying these modules via Spring Boot
+ iiif__P__enabled: true
+ ldn__P__enabled: true
+ oai__P__enabled: true
+ rdf__P__enabled: true
+ signposting__P__enabled: true
+ sword__D__server__P__enabled: true
+ swordv2__D__server__P__enabled: true
+ # If this is a PR against main (default branch), use "latest".
+ # Else if this is a PR against a different branch, used the base branch name.
+ # Else if this is a commit on main (default branch), use the "latest" tag.
+ # Else, just use the branch name.
+ # NOTE: DSPACE_VER is used because our docker compose scripts default to using the "-test" image.
+ DSPACE_VER: ${{ (github.event_name == 'pull_request' && github.event.pull_request.base.ref == github.event.repository.default_branch && 'latest') || (github.event_name == 'pull_request' && github.event.pull_request.base.ref) || (github.ref_name == github.event.repository.default_branch && 'latest') || github.ref_name }}
+ # Docker Registry to use for Docker compose scripts below.
+ # We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub.
+ DOCKER_REGISTRY: ghcr.io
steps:
- # https://github.com/actions/checkout
+ # Checkout our codebase (to get access to Docker Compose scripts)
- name: Checkout codebase
- uses: actions/checkout@v3
-
- # https://github.com/docker/setup-buildx-action
- - name: Setup Docker Buildx
- uses: docker/setup-buildx-action@v2
-
- # https://github.com/docker/setup-qemu-action
- - name: Set up QEMU emulation to build for multiple architectures
- uses: docker/setup-qemu-action@v2
-
- # https://github.com/docker/login-action
- - name: Login to DockerHub
- # Only login if not a PR, as PRs only trigger a Docker build and not a push
- if: github.event_name != 'pull_request'
- uses: docker/login-action@v2
- with:
- username: ${{ secrets.DOCKER_USERNAME }}
- password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
-
- ####################################################
- # Build/Push the 'dspace/dspace-dependencies' image
- ####################################################
- # https://github.com/docker/metadata-action
- # Get Metadata for docker_build_deps step below
- - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-dependencies' image
- id: meta_build_deps
- uses: docker/metadata-action@v4
- with:
- images: dspace/dspace-dependencies
- tags: ${{ env.IMAGE_TAGS }}
- flavor: ${{ env.TAGS_FLAVOR }}
-
- # https://github.com/docker/build-push-action
- - name: Build and push 'dspace-dependencies' image
- id: docker_build_deps
- uses: docker/build-push-action@v3
- with:
- context: .
- file: ./Dockerfile.dependencies
- platforms: ${{ env.PLATFORMS }}
- # For pull requests, we run the Docker build (to ensure no PR changes break the build),
- # but we ONLY do an image push to DockerHub if it's NOT a PR
- push: ${{ github.event_name != 'pull_request' }}
- # Use tags / labels provided by 'docker/metadata-action' above
- tags: ${{ steps.meta_build_deps.outputs.tags }}
- labels: ${{ steps.meta_build_deps.outputs.labels }}
-
- #######################################
- # Build/Push the 'dspace/dspace' image
- #######################################
- # Get Metadata for docker_build step below
- - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace' image
- id: meta_build
- uses: docker/metadata-action@v4
- with:
- images: dspace/dspace
- tags: ${{ env.IMAGE_TAGS }}
- flavor: ${{ env.TAGS_FLAVOR }}
-
- - name: Build and push 'dspace' image
- id: docker_build
- uses: docker/build-push-action@v3
- with:
- context: .
- file: ./Dockerfile
- platforms: ${{ env.PLATFORMS }}
- # For pull requests, we run the Docker build (to ensure no PR changes break the build),
- # but we ONLY do an image push to DockerHub if it's NOT a PR
- push: ${{ github.event_name != 'pull_request' }}
- # Use tags / labels provided by 'docker/metadata-action' above
- tags: ${{ steps.meta_build.outputs.tags }}
- labels: ${{ steps.meta_build.outputs.labels }}
-
- #####################################################
- # Build/Push the 'dspace/dspace' image ('-test' tag)
- #####################################################
- # Get Metadata for docker_build_test step below
- - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-test' image
- id: meta_build_test
- uses: docker/metadata-action@v4
- with:
- images: dspace/dspace
- tags: ${{ env.IMAGE_TAGS }}
- # As this is a test/development image, its tags are all suffixed with "-test". Otherwise, it uses the same
- # tagging logic as the primary 'dspace/dspace' image above.
- flavor: ${{ env.TAGS_FLAVOR }}
- suffix=-test
-
- - name: Build and push 'dspace-test' image
- id: docker_build_test
- uses: docker/build-push-action@v3
- with:
- context: .
- file: ./Dockerfile.test
- platforms: ${{ env.PLATFORMS }}
- # For pull requests, we run the Docker build (to ensure no PR changes break the build),
- # but we ONLY do an image push to DockerHub if it's NOT a PR
- push: ${{ github.event_name != 'pull_request' }}
- # Use tags / labels provided by 'docker/metadata-action' above
- tags: ${{ steps.meta_build_test.outputs.tags }}
- labels: ${{ steps.meta_build_test.outputs.labels }}
-
- ###########################################
- # Build/Push the 'dspace/dspace-cli' image
- ###########################################
- # Get Metadata for docker_build_test step below
- - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-cli' image
- id: meta_build_cli
- uses: docker/metadata-action@v4
- with:
- images: dspace/dspace-cli
- tags: ${{ env.IMAGE_TAGS }}
- flavor: ${{ env.TAGS_FLAVOR }}
-
- - name: Build and push 'dspace-cli' image
- id: docker_build_cli
- uses: docker/build-push-action@v3
- with:
- context: .
- file: ./Dockerfile.cli
- platforms: ${{ env.PLATFORMS }}
- # For pull requests, we run the Docker build (to ensure no PR changes break the build),
- # but we ONLY do an image push to DockerHub if it's NOT a PR
- push: ${{ github.event_name != 'pull_request' }}
- # Use tags / labels provided by 'docker/metadata-action' above
- tags: ${{ steps.meta_build_cli.outputs.tags }}
- labels: ${{ steps.meta_build_cli.outputs.labels }}
-
- ###########################################
- # Build/Push the 'dspace/dspace-solr' image
- ###########################################
- # Get Metadata for docker_build_solr step below
- - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-solr' image
- id: meta_build_solr
- uses: docker/metadata-action@v4
- with:
- images: dspace/dspace-solr
- tags: ${{ env.IMAGE_TAGS }}
- flavor: ${{ env.TAGS_FLAVOR }}
-
- - name: Build and push 'dspace-solr' image
- id: docker_build_solr
- uses: docker/build-push-action@v3
- with:
- context: .
- file: ./dspace/src/main/docker/dspace-solr/Dockerfile
- platforms: ${{ env.PLATFORMS }}
- # For pull requests, we run the Docker build (to ensure no PR changes break the build),
- # but we ONLY do an image push to DockerHub if it's NOT a PR
- push: ${{ github.event_name != 'pull_request' }}
- # Use tags / labels provided by 'docker/metadata-action' above
- tags: ${{ steps.meta_build_solr.outputs.tags }}
- labels: ${{ steps.meta_build_solr.outputs.labels }}
-
- ###########################################################
- # Build/Push the 'dspace/dspace-postgres-pgcrypto' image
- ###########################################################
- # Get Metadata for docker_build_postgres step below
- - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto' image
- id: meta_build_postgres
- uses: docker/metadata-action@v4
- with:
- images: dspace/dspace-postgres-pgcrypto
- tags: ${{ env.IMAGE_TAGS }}
- flavor: ${{ env.TAGS_FLAVOR }}
-
- - name: Build and push 'dspace-postgres-pgcrypto' image
- id: docker_build_postgres
- uses: docker/build-push-action@v3
- with:
- # Must build out of subdirectory to have access to install script for pgcrypto
- context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
- dockerfile: Dockerfile
- platforms: ${{ env.PLATFORMS }}
- # For pull requests, we run the Docker build (to ensure no PR changes break the build),
- # but we ONLY do an image push to DockerHub if it's NOT a PR
- push: ${{ github.event_name != 'pull_request' }}
- # Use tags / labels provided by 'docker/metadata-action' above
- tags: ${{ steps.meta_build_postgres.outputs.tags }}
- labels: ${{ steps.meta_build_postgres.outputs.labels }}
-
- ###########################################################
- # Build/Push the 'dspace/dspace-postgres-pgcrypto' image ('-loadsql' tag)
- ###########################################################
- # Get Metadata for docker_build_postgres_loadsql step below
- - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-postgres-pgcrypto-loadsql' image
- id: meta_build_postgres_loadsql
- uses: docker/metadata-action@v4
- with:
- images: dspace/dspace-postgres-pgcrypto
- tags: ${{ env.IMAGE_TAGS }}
- # Suffix all tags with "-loadsql". Otherwise, it uses the same
- # tagging logic as the primary 'dspace/dspace-postgres-pgcrypto' image above.
- flavor: ${{ env.TAGS_FLAVOR }}
- suffix=-loadsql
-
- - name: Build and push 'dspace-postgres-pgcrypto-loadsql' image
- id: docker_build_postgres_loadsql
- uses: docker/build-push-action@v3
+ uses: actions/checkout@v4
+ # Download Docker image artifacts (which were just built by reusable-docker-build.yml)
+ - name: Download Docker image artifacts
+ uses: actions/download-artifact@v4
with:
- # Must build out of subdirectory to have access to install script for pgcrypto
- context: ./dspace/src/main/docker/dspace-postgres-pgcrypto-curl/
- dockerfile: Dockerfile
- platforms: ${{ env.PLATFORMS }}
- # For pull requests, we run the Docker build (to ensure no PR changes break the build),
- # but we ONLY do an image push to DockerHub if it's NOT a PR
- push: ${{ github.event_name != 'pull_request' }}
- # Use tags / labels provided by 'docker/metadata-action' above
- tags: ${{ steps.meta_build_postgres_loadsql.outputs.tags }}
- labels: ${{ steps.meta_build_postgres_loadsql.outputs.labels }}
\ No newline at end of file
+ # Download all amd64 Docker images (TAR files) into the /tmp/docker directory
+ pattern: docker-image-*-linux-amd64
+ path: /tmp/docker
+ merge-multiple: true
+ # Load each of the images into Docker by calling "docker image load" for each.
+ # This ensures we are using the images just built & not any prior versions on DockerHub
+ - name: Load all downloaded Docker images
+ run: |
+ find /tmp/docker -type f -name "*.tar" -exec docker image load --input "{}" \;
+ docker image ls -a
+ # Start backend using our compose script in the codebase.
+ - name: Start backend in Docker
+ run: |
+ docker compose -f docker-compose.yml up -d
+ sleep 10
+ docker container ls
+ # Create a test admin account. Load test data from a simple set of AIPs as defined in cli.ingest.yml
+ - name: Load test data into Backend
+ run: |
+ docker compose -f docker-compose-cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en
+ docker compose -f docker-compose-cli.yml -f dspace/src/main/docker-compose/cli.ingest.yml run --rm dspace-cli
+ # Verify backend started successfully.
+ # 1. Make sure root endpoint is responding (check for dspace.name defined in docker-compose.yml)
+ # 2. Also check /collections endpoint to ensure the test data loaded properly (check for a collection name in AIPs)
+ - name: Verify backend is responding properly
+ run: |
+ result=$(wget -O- -q http://127.0.0.1:8080/server/api)
+ echo "$result"
+ echo "$result" | grep -oE "\"DSpace Started with Docker Compose\","
+ result=$(wget -O- -q http://127.0.0.1:8080/server/api/core/collections)
+ echo "$result"
+ echo "$result" | grep -oE "\"Dog in Yard\","
+ # Verify Handle Server can be stared and is working properly
+ # 1. First generate the "[dspace]/handle-server" folder with the sitebndl.zip
+ # 2. Start the Handle Server (and wait 20 seconds to let it start up)
+ # 3. Verify logs do NOT include "Exception" in the text (as that means an error occurred)
+ # 4. Check that Handle Proxy HTML page is responding on default port (8000)
+ - name: Verify Handle Server is working properly
+ run: |
+ docker exec -i dspace /dspace/bin/make-handle-config
+ echo "Starting Handle Server..."
+ docker exec -i dspace /dspace/bin/start-handle-server
+ sleep 20
+ echo "Checking for errors in error.log"
+ result=$(docker exec -i dspace sh -c "cat /dspace/handle-server/logs/error.log* || echo ''")
+ echo "$result"
+ echo "$result" | grep -vqz "Exception"
+ echo "Checking for errors in handle-server.log..."
+ result=$(docker exec -i dspace cat /dspace/log/handle-server.log)
+ echo "$result"
+ echo "$result" | grep -vqz "Exception"
+ echo "Checking to see if Handle Proxy webpage is available..."
+ result=$(wget -O- -q http://127.0.0.1:8000/)
+ echo "$result"
+ echo "$result" | grep -oE "Handle Proxy"
+ # Shutdown our containers
+ - name: Shutdown Docker containers
+ run: |
+ docker compose -f docker-compose.yml down
diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml
index b4436dca3aad..0a35a6a95044 100644
--- a/.github/workflows/issue_opened.yml
+++ b/.github/workflows/issue_opened.yml
@@ -16,7 +16,7 @@ jobs:
# Only add to project board if issue is flagged as "needs triage" or has no labels
# NOTE: By default we flag new issues as "needs triage" in our issue template
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
- uses: actions/add-to-project@v0.5.0
+ uses: actions/add-to-project@v1.0.0
# Note, the authentication token below is an ORG level Secret.
# It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml
index cc0c7099f40e..a023f4eef246 100644
--- a/.github/workflows/label_merge_conflicts.yml
+++ b/.github/workflows/label_merge_conflicts.yml
@@ -1,11 +1,12 @@
# This workflow checks open PRs for merge conflicts and labels them when conflicts are found
name: Check for merge conflicts
-# Run whenever the "main" branch is updated
-# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
+# Run this for all pushes (i.e. merges) to 'main' or maintenance branches
on:
push:
- branches: [ main ]
+ branches:
+ - main
+ - 'dspace-**'
# So that the `conflict_label_name` is removed if conflicts are resolved,
# we allow this to run for `pull_request_target` so that github secrets are available.
pull_request_target:
@@ -24,6 +25,8 @@ jobs:
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
- name: Auto-label PRs with merge conflicts
uses: prince-chrismc/label-merge-conflicts-action@v3
+ # Ignore any failures -- may occur (randomly?) for older, outdated PRs.
+ continue-on-error: true
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
# Note, the authentication token is created automatically
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml
new file mode 100644
index 000000000000..857f22755e49
--- /dev/null
+++ b/.github/workflows/port_merged_pull_request.yml
@@ -0,0 +1,46 @@
+# This workflow will attempt to port a merged pull request to
+# the branch specified in a "port to" label (if exists)
+name: Port merged Pull Request
+
+# Only run for merged PRs against the "main" or maintenance branches
+# We allow this to run for `pull_request_target` so that github secrets are available
+# (This is required when the PR comes from a forked repo)
+on:
+ pull_request_target:
+ types: [ closed ]
+ branches:
+ - main
+ - 'dspace-**'
+
+permissions:
+ contents: write # so action can add comments
+ pull-requests: write # so action can create pull requests
+
+jobs:
+ port_pr:
+ runs-on: ubuntu-latest
+ # Don't run on closed *unmerged* pull requests
+ if: github.event.pull_request.merged
+ steps:
+ # Checkout code
+ - uses: actions/checkout@v4
+ # Port PR to other branch (ONLY if labeled with "port to")
+ # See https://github.com/korthout/backport-action
+ - name: Create backport pull requests
+ uses: korthout/backport-action@v2
+ with:
+ # Trigger based on a "port to [branch]" label on PR
+ # (This label must specify the branch name to port to)
+ label_pattern: '^port to ([^ ]+)$'
+ # Title to add to the (newly created) port PR
+ pull_title: '[Port ${target_branch}] ${pull_title}'
+ # Description to add to the (newly created) port PR
+ pull_description: 'Port of #${pull_number} by @${pull_author} to `${target_branch}`.'
+ # Copy all labels from original PR to (newly created) port PR
+ # NOTE: The labels matching 'label_pattern' are automatically excluded
+ copy_labels_pattern: '.*'
+ # Skip any merge commits in the ported PR. This means only non-merge commits are cherry-picked to the new PR
+ merge_commits: 'skip'
+ # Use a personal access token (PAT) to create PR as 'dspace-bot' user.
+ # A PAT is required in order for the new PR to trigger its own actions (for CI checks)
+ github_token: ${{ secrets.PR_PORT_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml
new file mode 100644
index 000000000000..bbac52af2438
--- /dev/null
+++ b/.github/workflows/pull_request_opened.yml
@@ -0,0 +1,24 @@
+# This workflow runs whenever a new pull request is created
+name: Pull Request opened
+
+# Only run for newly opened PRs against the "main" or maintenance branches
+# We allow this to run for `pull_request_target` so that github secrets are available
+# (This is required to assign a PR back to the creator when the PR comes from a forked repo)
+on:
+ pull_request_target:
+ types: [ opened ]
+ branches:
+ - main
+ - 'dspace-**'
+
+permissions:
+ pull-requests: write
+
+jobs:
+ automation:
+ runs-on: ubuntu-latest
+ steps:
+ # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards
+ # See https://github.com/toshimaru/auto-author-assign
+ - name: Assign PR to creator
+ uses: toshimaru/auto-author-assign@v2.1.0
diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml
new file mode 100644
index 000000000000..7a8abda3e106
--- /dev/null
+++ b/.github/workflows/reusable-docker-build.yml
@@ -0,0 +1,358 @@
+#
+# DSpace's reusable Docker build/push workflow.
+#
+# This is used by docker.yml for all Docker image builds
+name: Reusable DSpace Docker Build
+
+on:
+ workflow_call:
+ # Possible Inputs to this reusable job
+ inputs:
+ # Build name/id for this Docker build. Used for digest storage to avoid digest overlap between builds.
+ build_id:
+ required: true
+ type: string
+ # Requires the image name to build (e.g dspace/dspace-test)
+ image_name:
+ required: true
+ type: string
+ # Optionally the path to the Dockerfile to use for the build. (Default is [dockerfile_context]/Dockerfile)
+ dockerfile_path:
+ required: false
+ type: string
+ # Optionally the context directory to build the Dockerfile within. Defaults to "." (current directory)
+ dockerfile_context:
+ required: false
+ type: string
+ default: '.'
+ # Optionally a list of "additional_contexts" to pass to Dockerfile. Defaults to empty
+ dockerfile_additional_contexts:
+ required: false
+ type: string
+ default: ''
+ # If Docker image should have additional tag flavor details (e.g. a suffix), it may be passed in.
+ tags_flavor:
+ required: false
+ type: string
+ secrets:
+ # Requires that Docker login info be passed in as secrets.
+ DOCKER_USERNAME:
+ required: true
+ DOCKER_ACCESS_TOKEN:
+ required: true
+ # These URL secrets are optional. When specified & branch checks match, the redeployment code below will trigger.
+ # Therefore builds which need to trigger redeployment MUST specify these URLs. All others should leave them empty.
+ REDEPLOY_SANDBOX_URL:
+ required: false
+ REDEPLOY_DEMO_URL:
+ required: false
+
+# Define shared default settings as environment variables
+env:
+ IMAGE_NAME: ${{ inputs.image_name }}
+ # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
+ # For a new commit on default branch (main), use the literal tag 'latest' on Docker image.
+ # For a new commit on other branches, use the branch name as the tag for Docker image.
+ # For a new tag, copy that tag name as the tag for Docker image.
+ # For a pull request, use the name of the base branch that the PR was created against or "latest" (for main).
+ # e.g. PR against 'main' will use "latest". a PR against 'dspace-7_x' will use 'dspace-7_x'.
+ IMAGE_TAGS: |
+ type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch }}
+ type=ref,event=branch,enable=${{ github.ref_name != github.event.repository.default_branch }}
+ type=ref,event=tag
+ type=raw,value=${{ (github.event.pull_request.base.ref == github.event.repository.default_branch && 'latest') || github.event.pull_request.base.ref }},enable=${{ github.event_name == 'pull_request' }}
+ # Define default tag "flavor" for docker/metadata-action per
+ # https://github.com/docker/metadata-action#flavor-input
+ # We manage the 'latest' tag ourselves to the 'main' branch (see settings above)
+ TAGS_FLAVOR: |
+ latest=false
+ ${{ inputs.tags_flavor }}
+ # When these URL variables are specified & required branch matches, then the sandbox or demo site will be redeployed.
+ # See "Redeploy" steps below for more details.
+ REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
+ REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
+ # Current DSpace branches (and architecture) which are deployed to demo.dspace.org & sandbox.dspace.org respectively
+ DEPLOY_DEMO_BRANCH: 'dspace-8_x'
+ DEPLOY_SANDBOX_BRANCH: 'main'
+ DEPLOY_ARCH: 'linux/amd64'
+ # Registry used during building of Docker images. (All images are later copied to docker.io registry)
+ # We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub.
+ DOCKER_BUILD_REGISTRY: ghcr.io
+
+jobs:
+ docker-build:
+
+ strategy:
+ matrix:
+ # Architectures / Platforms for which we will build Docker images
+ arch: [ 'linux/amd64', 'linux/arm64' ]
+ os: [ ubuntu-latest ]
+ isPr:
+ - ${{ github.event_name == 'pull_request' }}
+ # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work.
+ # The below exclude therefore ensures we do NOT build ARM64 for PRs.
+ exclude:
+ - isPr: true
+ os: ubuntu-latest
+ arch: linux/arm64
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ # This step converts the slashes in the "arch" matrix values above into dashes & saves to env.ARCH_NAME
+ # E.g. "linux/amd64" becomes "linux-amd64"
+ # This is necessary because all upload artifacts CANNOT have special chars (like slashes)
+ # NOTE: The regex-like syntax below is Bash Parameter Substitution
+ - name: Prepare
+ run: |
+ platform=${{ matrix.arch }}
+ echo "ARCH_NAME=${platform//\//-}" >> $GITHUB_ENV
+
+ # https://github.com/actions/checkout
+ - name: Checkout codebase
+ uses: actions/checkout@v4
+
+ # https://github.com/docker/login-action
+ # NOTE: This login occurs for BOTH non-PRs or PRs. PRs *must* also login to access private images from GHCR
+ # during the build process
+ - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_BUILD_REGISTRY }}
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ # https://github.com/docker/setup-qemu-action
+ - name: Set up QEMU emulation to build for multiple architectures
+ uses: docker/setup-qemu-action@v3
+
+ # https://github.com/docker/setup-buildx-action
+ - name: Setup Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ # https://github.com/docker/metadata-action
+ # Extract metadata used for Docker images in all build steps below
+ - name: Extract metadata (tags, labels) from GitHub for Docker image
+ id: meta_build
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: ${{ env.IMAGE_TAGS }}
+ flavor: ${{ env.TAGS_FLAVOR }}
+
+ #--------------------------------------------------------------------
+ # First, for all branch commits (non-PRs) we build the image & upload
+ # to GitHub Container Registry (GHCR). After uploading the image
+ # to GHCR, we store the image digest in an artifact, so we can
+ # create a merged manifest later (see 'docker-build_manifest' job).
+ #
+ # NOTE: We use GHCR in order to avoid aggressive rate limits at DockerHub.
+ #--------------------------------------------------------------------
+ # https://github.com/docker/build-push-action
+ - name: Build and push image to ${{ env.DOCKER_BUILD_REGISTRY }}
+ if: ${{ ! matrix.isPr }}
+ id: docker_build
+ uses: docker/build-push-action@v5
+ with:
+ build-contexts: |
+ ${{ inputs.dockerfile_additional_contexts }}
+ context: ${{ inputs.dockerfile_context }}
+ file: ${{ inputs.dockerfile_path }}
+ # Tell DSpace's Docker files to use the build registry instead of DockerHub
+ build-args:
+ DOCKER_REGISTRY=${{ env.DOCKER_BUILD_REGISTRY }}
+ platforms: ${{ matrix.arch }}
+ push: true
+ # Use tags / labels provided by 'docker/metadata-action' above
+ tags: ${{ steps.meta_build.outputs.tags }}
+ labels: ${{ steps.meta_build.outputs.labels }}
+ # Use GitHub cache to load cached Docker images and cache the results of this build
+ # This decreases the number of images we need to fetch from DockerHub
+ cache-from: type=gha,scope=${{ inputs.build_id }}
+ cache-to: type=gha,scope=${{ inputs.build_id }},mode=max
+
+ # Export the digest of Docker build locally
+ - name: Export Docker build digest
+ if: ${{ ! matrix.isPr }}
+ run: |
+ mkdir -p /tmp/digests
+ digest="${{ steps.docker_build.outputs.digest }}"
+ touch "/tmp/digests/${digest#sha256:}"
+
+ # Upload digest to an artifact, so that it can be used in combined manifest below
+ # (The purpose of the combined manifest is to list both amd64 and arm64 builds under same tag)
+ - name: Upload Docker build digest to artifact
+ if: ${{ ! matrix.isPr }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: digests-${{ inputs.build_id }}-${{ env.ARCH_NAME }}
+ path: /tmp/digests/*
+ if-no-files-found: error
+ retention-days: 1
+
+ #------------------------------------------------------------------------------
+ # Second, we build the image again in order to store it in a local TAR file.
+ # This TAR of the image is cached/saved as an artifact, so that it can be used
+ # by later jobs to install the brand-new images for automated testing.
+ # This TAR build is performed BOTH for PRs and for branch commits (non-PRs).
+ #
+ # (This approach has the advantage of avoiding having to download the newly built
+ # image from DockerHub or GHCR during automated testing.)
+ #
+ # See the 'docker-deploy' job in docker.yml as an example of where this TAR is used.
+ #-------------------------------------------------------------------------------
+ # Build local image (again) and store in a TAR file in /tmp directory
+ # This step is only done for AMD64, as that's the only image we use in our automated testing (at this time).
+ # NOTE: This step cannot be combined with the build above as it's a different type of output.
+ - name: Build and push image to local TAR file
+ if: ${{ matrix.arch == 'linux/amd64'}}
+ uses: docker/build-push-action@v5
+ with:
+ build-contexts: |
+ ${{ inputs.dockerfile_additional_contexts }}
+ context: ${{ inputs.dockerfile_context }}
+ file: ${{ inputs.dockerfile_path }}
+ # Tell DSpace's Docker files to use the build registry instead of DockerHub
+ build-args:
+ DOCKER_REGISTRY=${{ env.DOCKER_BUILD_REGISTRY }}
+ platforms: ${{ matrix.arch }}
+ tags: ${{ steps.meta_build.outputs.tags }}
+ labels: ${{ steps.meta_build.outputs.labels }}
+ # Use GitHub cache to load cached Docker images and cache the results of this build
+ # This decreases the number of images we need to fetch from DockerHub
+ cache-from: type=gha,scope=${{ inputs.build_id }}
+ cache-to: type=gha,scope=${{ inputs.build_id }},mode=max
+ # Export image to a local TAR file
+ outputs: type=docker,dest=/tmp/${{ inputs.build_id }}.tar
+
+ # Upload the local docker image (in TAR file) to a build Artifact
+ # This step is only done for AMD64, as that's the only image we use in our automated testing (at this time).
+ - name: Upload local image TAR to artifact
+ if: ${{ matrix.arch == 'linux/amd64'}}
+ uses: actions/upload-artifact@v4
+ with:
+ name: docker-image-${{ inputs.build_id }}-${{ env.ARCH_NAME }}
+ path: /tmp/${{ inputs.build_id }}.tar
+ if-no-files-found: error
+ retention-days: 1
+
+ ##########################################################################################
+ # Merge Docker digests (from various architectures) into a single manifest.
+ # This runs after all Docker builds complete above. The purpose is to include all builds
+ # under a single manifest for this tag.
+ # (e.g. both linux/amd64 and linux/arm64 should be listed under the same tagged Docker image)
+ ##########################################################################################
+ docker-build_manifest:
+ # Only run if this is NOT a PR
+ if: ${{ github.event_name != 'pull_request' }}
+ runs-on: ubuntu-latest
+ needs:
+ - docker-build
+ steps:
+ - name: Download Docker build digests
+ uses: actions/download-artifact@v4
+ with:
+ path: /tmp/digests
+ # Download digests for both AMD64 and ARM64 into same directory
+ pattern: digests-${{ inputs.build_id }}-*
+ merge-multiple: true
+
+ - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_BUILD_REGISTRY }}
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Add Docker metadata for image
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: ${{ env.IMAGE_TAGS }}
+ flavor: ${{ env.TAGS_FLAVOR }}
+
+ - name: Create manifest list from digests and push to ${{ env.DOCKER_BUILD_REGISTRY }}
+ working-directory: /tmp/digests
+ run: |
+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+ $(printf '${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
+
+ - name: Inspect manifest in ${{ env.DOCKER_BUILD_REGISTRY }}
+ run: |
+ docker buildx imagetools inspect ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
+
+ ##########################################################################################
+ # Copy images / manifest to DockerHub.
+ # This MUST run after *both* images (AMD64 and ARM64) are built and uploaded to GitHub
+ # Container Registry (GHCR). Attempting to run this in parallel to GHCR builds can result
+ # in a race condition...i.e. the copy to DockerHub may fail if GHCR image has been updated
+ # at the moment when the copy occurs.
+ ##########################################################################################
+ docker-copy_to_dockerhub:
+ # Only run if this is NOT a PR
+ if: ${{ github.event_name != 'pull_request' }}
+ runs-on: ubuntu-latest
+ needs:
+ - docker-build_manifest
+
+ steps:
+ # 'regctl' is used to more easily copy the image to DockerHub and obtain the digest from DockerHub
+ # See https://github.com/regclient/regclient/blob/main/docs/regctl.md
+ - name: Install regctl for Docker registry tools
+ uses: regclient/actions/regctl-installer@main
+ with:
+ release: 'v0.8.0'
+
+ # This recreates Docker tags for DockerHub
+ - name: Add Docker metadata for image
+ id: meta_dockerhub
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.IMAGE_NAME }}
+ tags: ${{ env.IMAGE_TAGS }}
+ flavor: ${{ env.TAGS_FLAVOR }}
+
+ # Login to source registry first, as this is where we are copying *from*
+ - name: Login to ${{ env.DOCKER_BUILD_REGISTRY }}
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.DOCKER_BUILD_REGISTRY }}
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ # Login to DockerHub, since this is where we are copying *to*
+ - name: Login to DockerHub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
+
+ # Copy the image from source to DockerHub
+ - name: Copy image from ${{ env.DOCKER_BUILD_REGISTRY }} to docker.io
+ run: |
+ regctl image copy ${{ env.DOCKER_BUILD_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta_dockerhub.outputs.version }} docker.io/${{ env.IMAGE_NAME }}:${{ steps.meta_dockerhub.outputs.version }}
+
+ #--------------------------------------------------------------------
+ # Finally, check whether demo.dspace.org or sandbox.dspace.org need
+ # to be redeployed based on these new DockerHub images.
+ #--------------------------------------------------------------------
+ # If this build is for the branch that Sandbox uses and passed in a REDEPLOY_SANDBOX_URL secret,
+ # Then redeploy https://sandbox.dspace.org
+ - name: Redeploy sandbox.dspace.org (based on main branch)
+ if: |
+ env.REDEPLOY_SANDBOX_URL != '' &&
+ github.ref_name == env.DEPLOY_SANDBOX_BRANCH
+ run: |
+ curl -X POST $REDEPLOY_SANDBOX_URL
+ # If this build is for the branch that Demo uses and passed in a REDEPLOY_DEMO_URL secret,
+ # Then redeploy https://demo.dspace.org
+ - name: Redeploy demo.dspace.org (based on maintenance branch)
+ if: |
+ env.REDEPLOY_DEMO_URL != '' &&
+ github.ref_name == env.DEPLOY_DEMO_BRANCH
+ run: |
+ curl -X POST $REDEPLOY_DEMO_URL
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 2fcb46b9932c..529351edc5c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ tags
.project
.classpath
.checkstyle
+.factorypath
## Ignore project files created by IntelliJ IDEA
*.iml
diff --git a/.lgtm.yml b/.lgtm.yml
deleted file mode 100644
index 132de8a6de5a..000000000000
--- a/.lgtm.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-# LGTM Settings (https://lgtm.com/)
-# For reference, see https://lgtm.com/help/lgtm/lgtm.yml-configuration-file
-# or template at https://lgtm.com/static/downloads/lgtm.template.yml
-
-extraction:
- java:
- index:
- # Specify the Java version required to build the project
- java_version: 11
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 45a6af9ce5a3..657e11eee771 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,13 +10,14 @@ DSpace is a community built and supported project. We do not have a centralized
## Contribute new code via a Pull Request
We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone.
-Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC7x/Release+Notes).
+Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC8x/Release+Notes).
Code Contribution Checklist
- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests)
- [ ] PRs **must** pass Checkstyle validation based on our [Code Style Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Style+Guide).
- [ ] PRs **must** include Javadoc for _all new/modified public methods and classes_. Larger private methods should also have Javadoc
- [ ] PRs **must** pass all automated tests and include new/updated Unit or Integration tests based on our [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
+- [ ] Details on how to test the PR **must** be provided. Reviewers must be aware of any steps they need to take to successfully test your fix or feature.
- [ ] If a PR includes new libraries/dependencies (in any `pom.xml`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
- [ ] Basic technical documentation _should_ be provided for any new features or changes to the REST API. REST API changes should be documented in our [Rest Contract](https://github.com/DSpace/RestContract).
- [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
@@ -25,7 +26,7 @@ Additional details on the code contribution process can be found in our [Code Co
## Contribute documentation
-DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x
+DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC
If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org.
Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation.
@@ -33,7 +34,7 @@ Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyra
## Help others on mailing lists or Slack
DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered.
-Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via LYRASIS).
+Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via Lyrasis).
## Join a working or interest group
@@ -41,5 +42,5 @@ Most of the work in building/improving DSpace comes via [Working Groups](https:/
All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include:
-* [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) - This is the main (mostly volunteer) development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs.
-* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers.
\ No newline at end of file
+* [DSpace Developer Team](https://wiki.lyrasis.org/display/DSPACE/Developer+Meetings) - This is the primary, volunteer development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs. This is also were discussions of the next release or major issues occur. Anyone is welcome to attend.
+* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers. Anyone is welcome to attend.
diff --git a/Dockerfile b/Dockerfile
index f1ff6adf5ac5..5aece8b7d37e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,19 @@
# This image will be published as dspace/dspace
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
-# - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x
+# - note: default tag for branch: dspace/dspace: dspace/dspace:latest
-# This Dockerfile uses JDK11 by default, but has also been tested with JDK17.
-# To build with JDK17, use "--build-arg JDK_VERSION=17"
-ARG JDK_VERSION=11
+# This Dockerfile uses JDK17 by default.
+# To build with other versions, use "--build-arg JDK_VERSION=[value]"
+ARG JDK_VERSION=17
+# The Docker version tag to build from
+ARG DSPACE_VERSION=latest
+# The Docker registry to use for DSpace images. Defaults to "docker.io"
+# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument
+ARG DOCKER_REGISTRY=docker.io
# Step 1 - Run Maven Build
-FROM dspace/dspace-dependencies:dspace-7_x as build
+FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build
ARG TARGET_DIR=dspace-installer
WORKDIR /app
# The dspace-installer directory will be written to /install
@@ -18,50 +23,51 @@ RUN mkdir /install \
USER dspace
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
ADD --chown=dspace . /app/
-# Build DSpace (note: this build doesn't include the optional, deprecated "dspace-rest" webapp)
+# Build DSpace
# Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small
-RUN mvn --no-transfer-progress package && \
+# Maven flags here ensure that we skip building test environment and skip all code verification checks.
+# These flags speed up this compilation as much as reasonably possible.
+ENV MAVEN_FLAGS="-P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxml.skip=true"
+RUN mvn --no-transfer-progress package ${MAVEN_FLAGS} && \
mv /app/dspace/target/${TARGET_DIR}/* /install && \
mvn clean
+# Remove the server webapp to keep image small.
+RUN rm -rf /install/webapps/server/
# Step 2 - Run Ant Deploy
-FROM openjdk:${JDK_VERSION}-slim as ant_build
+FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build
ARG TARGET_DIR=dspace-installer
# COPY the /install directory from 'build' container to /dspace-src in this container
COPY --from=build /install /dspace-src
WORKDIR /dspace-src
# Create the initial install deployment using ANT
-ENV ANT_VERSION 1.10.13
-ENV ANT_HOME /tmp/ant-$ANT_VERSION
-ENV PATH $ANT_HOME/bin:$PATH
-# Need wget to install ant
-RUN apt-get update \
- && apt-get install -y --no-install-recommends wget \
- && apt-get purge -y --auto-remove \
- && rm -rf /var/lib/apt/lists/*
+ENV ANT_VERSION=1.10.13
+ENV ANT_HOME=/tmp/ant-$ANT_VERSION
+ENV PATH=$ANT_HOME/bin:$PATH
# Download and install 'ant'
RUN mkdir $ANT_HOME && \
- wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME
+ curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
+ https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
+ tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
+ rm /tmp/apache-ant.tar.gz
# Run necessary 'ant' deploy scripts
RUN ant init_installation update_configs update_code update_webapps
-# Step 3 - Run tomcat
-# Create a new tomcat image that does not retain the the build directory contents
-FROM tomcat:9-jdk${JDK_VERSION}
+# Step 3 - Start up DSpace via Runnable JAR
+FROM docker.io/eclipse-temurin:${JDK_VERSION}
# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
ENV DSPACE_INSTALL=/dspace
-# Copy the /dspace directory from 'ant_build' containger to /dspace in this container
+# Copy the /dspace directory from 'ant_build' container to /dspace in this container
COPY --from=ant_build /dspace $DSPACE_INSTALL
-# Expose Tomcat port and AJP port
-EXPOSE 8080 8009
+WORKDIR $DSPACE_INSTALL
+# Need host command for "[dspace]/bin/make-handle-config"
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends host \
+ && apt-get purge -y --auto-remove \
+ && rm -rf /var/lib/apt/lists/*
+# Expose Tomcat port (8080) & Handle Server HTTP port (8000)
+EXPOSE 8080 8000
# Give java extra memory (2GB)
ENV JAVA_OPTS=-Xmx2000m
-
-# Link the DSpace 'server' webapp into Tomcat's webapps directory.
-# This ensures that when we start Tomcat, it runs from /server path (e.g. http://localhost:8080/server/)
-RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server
-# If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN.
-# You also MUST update the 'dspace.server.url' configuration to match.
-# Please note that server webapp should only run on one path at a time.
-#RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \
-# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT
+# On startup, run DSpace Runnable JAR
+ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar", "--dspace.dir=$DSPACE_INSTALL"]
diff --git a/Dockerfile.cli b/Dockerfile.cli
index 62e83b79ef02..e43c8eb95dd6 100644
--- a/Dockerfile.cli
+++ b/Dockerfile.cli
@@ -1,14 +1,19 @@
# This image will be published as dspace/dspace-cli
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
-# - note: default tag for branch: dspace/dspace-cli: dspace/dspace-cli:dspace-7_x
+# - note: default tag for branch: dspace/dspace-cli: dspace/dspace-cli:latest
-# This Dockerfile uses JDK11 by default, but has also been tested with JDK17.
-# To build with JDK17, use "--build-arg JDK_VERSION=17"
-ARG JDK_VERSION=11
+# This Dockerfile uses JDK17 by default.
+# To build with other versions, use "--build-arg JDK_VERSION=[value]"
+ARG JDK_VERSION=17
+# The Docker version tag to build from
+ARG DSPACE_VERSION=latest
+# The Docker registry to use for DSpace images. Defaults to "docker.io"
+# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument
+ARG DOCKER_REGISTRY=docker.io
# Step 1 - Run Maven Build
-FROM dspace/dspace-dependencies:dspace-7_x as build
+FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build
ARG TARGET_DIR=dspace-installer
WORKDIR /app
# The dspace-installer directory will be written to /install
@@ -24,31 +29,34 @@ RUN mvn --no-transfer-progress package && \
mvn clean
# Step 2 - Run Ant Deploy
-FROM openjdk:${JDK_VERSION}-slim as ant_build
+FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build
ARG TARGET_DIR=dspace-installer
# COPY the /install directory from 'build' container to /dspace-src in this container
COPY --from=build /install /dspace-src
WORKDIR /dspace-src
# Create the initial install deployment using ANT
-ENV ANT_VERSION 1.10.13
-ENV ANT_HOME /tmp/ant-$ANT_VERSION
-ENV PATH $ANT_HOME/bin:$PATH
-# Need wget to install ant, and unzip for managing AIPs
-RUN apt-get update \
- && apt-get install -y --no-install-recommends wget unzip \
- && apt-get purge -y --auto-remove \
- && rm -rf /var/lib/apt/lists/*
+ENV ANT_VERSION=1.10.13
+ENV ANT_HOME=/tmp/ant-$ANT_VERSION
+ENV PATH=$ANT_HOME/bin:$PATH
# Download and install 'ant'
RUN mkdir $ANT_HOME && \
- wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME
+ curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
+ https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
+ tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
+ rm /tmp/apache-ant.tar.gz
# Run necessary 'ant' deploy scripts
RUN ant init_installation update_configs update_code
# Step 3 - Run jdk
-FROM openjdk:${JDK_VERSION}
+FROM docker.io/eclipse-temurin:${JDK_VERSION}
# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
ENV DSPACE_INSTALL=/dspace
# Copy the /dspace directory from 'ant_build' container to /dspace in this container
COPY --from=ant_build /dspace $DSPACE_INSTALL
# Give java extra memory (1GB)
ENV JAVA_OPTS=-Xmx1000m
+# Install unzip for AIPs
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends unzip \
+ && apt-get purge -y --auto-remove \
+ && rm -rf /var/lib/apt/lists/*
diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies
index a55b323339dc..04233cd415fa 100644
--- a/Dockerfile.dependencies
+++ b/Dockerfile.dependencies
@@ -2,12 +2,12 @@
# The purpose of this image is to make the build for dspace/dspace run faster
#
-# This Dockerfile uses JDK11 by default, but has also been tested with JDK17.
-# To build with JDK17, use "--build-arg JDK_VERSION=17"
-ARG JDK_VERSION=11
+# This Dockerfile uses JDK17 by default.
+# To build with other versions, use "--build-arg JDK_VERSION=[value]"
+ARG JDK_VERSION=17
-# Step 1 - Run Maven Build
-FROM maven:3-openjdk-${JDK_VERSION}-slim as build
+# Step 1 - Download all Dependencies
+FROM docker.io/maven:3-eclipse-temurin-${JDK_VERSION} AS build
ARG TARGET_DIR=dspace-installer
WORKDIR /app
# Create the 'dspace' user account & home directory
@@ -15,22 +15,64 @@ RUN useradd dspace \
&& mkdir -p /home/dspace \
&& chown -Rv dspace: /home/dspace
RUN chown -Rv dspace: /app
-# Need git to support buildnumber-maven-plugin, which lets us know what version of DSpace is being run.
-RUN apt-get update \
- && apt-get install -y --no-install-recommends git \
- && apt-get purge -y --auto-remove \
- && rm -rf /var/lib/apt/lists/*
# Switch to dspace user & run below commands as that user
USER dspace
-# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
-ADD --chown=dspace . /app/
+# This next part may look odd, but it speeds up the build of this image *significantly*.
+# Copy ONLY the POMs to this image (from local machine). This will allow us to download all dependencies *without*
+# performing any code compilation steps.
+
+# Parent POM
+ADD --chown=dspace pom.xml /app/
+RUN mkdir -p /app/dspace
+
+# 'dspace' module POM. Includes 'additions' ONLY, as it's the only submodule that is required to exist.
+ADD --chown=dspace dspace/pom.xml /app/dspace/
+RUN mkdir -p /app/dspace/modules/
+ADD --chown=dspace dspace/modules/pom.xml /app/dspace/modules/
+RUN mkdir -p /app/dspace/modules/additions
+ADD --chown=dspace dspace/modules/additions/pom.xml /app/dspace/modules/additions/
+
+# 'dspace-api' module POM
+RUN mkdir -p /app/dspace-api
+ADD --chown=dspace dspace-api/pom.xml /app/dspace-api/
+
+# 'dspace-iiif' module POM
+RUN mkdir -p /app/dspace-iiif
+ADD --chown=dspace dspace-iiif/pom.xml /app/dspace-iiif/
+
+# 'dspace-oai' module POM
+RUN mkdir -p /app/dspace-oai
+ADD --chown=dspace dspace-oai/pom.xml /app/dspace-oai/
+
+# 'dspace-rdf' module POM
+RUN mkdir -p /app/dspace-rdf
+ADD --chown=dspace dspace-rdf/pom.xml /app/dspace-rdf/
+
+# 'dspace-server-webapp' module POM
+RUN mkdir -p /app/dspace-server-webapp
+ADD --chown=dspace dspace-server-webapp/pom.xml /app/dspace-server-webapp/
+
+# 'dspace-services' module POM
+RUN mkdir -p /app/dspace-services
+ADD --chown=dspace dspace-services/pom.xml /app/dspace-services/
+
+# 'dspace-sword' module POM
+RUN mkdir -p /app/dspace-sword
+ADD --chown=dspace dspace-sword/pom.xml /app/dspace-sword/
+
+# 'dspace-swordv2' module POM
+RUN mkdir -p /app/dspace-swordv2
+ADD --chown=dspace dspace-swordv2/pom.xml /app/dspace-swordv2/
# Trigger the installation of all maven dependencies (hide download progress messages)
-RUN mvn --no-transfer-progress package
+# Maven flags here ensure that we skip final assembly, skip building test environment and skip all code verification checks.
+# These flags speed up this installation and skip tasks we cannot perform as we don't have the full source code.
+ENV MAVEN_FLAGS="-P-assembly -P-test-environment -Denforcer.skip=true -Dcheckstyle.skip=true -Dlicense.skip=true -Dxjc.skip=true -Dxml.skip=true"
+RUN mvn --no-transfer-progress verify ${MAVEN_FLAGS}
-# Clear the contents of the /app directory (including all maven builds), so no artifacts remain.
+# Clear the contents of the /app directory (including all maven target folders), so no artifacts remain.
# This ensures when dspace:dspace is built, it will use the Maven local cache (~/.m2) for dependencies
USER root
RUN rm -rf /app/*
diff --git a/Dockerfile.test b/Dockerfile.test
index 4e9b2b5b4343..90266101dbf0 100644
--- a/Dockerfile.test
+++ b/Dockerfile.test
@@ -1,16 +1,21 @@
# This image will be published as dspace/dspace
# See https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker for usage details
#
-# - note: default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-test
+# - note: default tag for branch: dspace/dspace: dspace/dspace:latest-test
#
# This image is meant for TESTING/DEVELOPMENT ONLY as it deploys the old v6 REST API under HTTP (not HTTPS)
-# This Dockerfile uses JDK11 by default, but has also been tested with JDK17.
-# To build with JDK17, use "--build-arg JDK_VERSION=17"
-ARG JDK_VERSION=11
+# This Dockerfile uses JDK17 by default.
+# To build with other versions, use "--build-arg JDK_VERSION=[value]"
+ARG JDK_VERSION=17
+# The Docker version tag to build from
+ARG DSPACE_VERSION=latest
+# The Docker registry to use for DSpace images. Defaults to "docker.io"
+# NOTE: non-DSpace images are hardcoded to use "docker.io" and are not impacted by this build argument
+ARG DOCKER_REGISTRY=docker.io
# Step 1 - Run Maven Build
-FROM dspace/dspace-dependencies:dspace-7_x as build
+FROM ${DOCKER_REGISTRY}/dspace/dspace-dependencies:${DSPACE_VERSION} AS build
ARG TARGET_DIR=dspace-installer
WORKDIR /app
# The dspace-installer directory will be written to /install
@@ -20,63 +25,50 @@ RUN mkdir /install \
USER dspace
# Copy the DSpace source code (from local machine) into the workdir (excluding .dockerignore contents)
ADD --chown=dspace . /app/
-# Build DSpace (INCLUDING the optional, deprecated "dspace-rest" webapp)
+# Build DSpace
# Copy the dspace-installer directory to /install. Clean up the build to keep the docker image small
-RUN mvn --no-transfer-progress package -Pdspace-rest && \
+RUN mvn --no-transfer-progress package && \
mv /app/dspace/target/${TARGET_DIR}/* /install && \
mvn clean
+# Remove the server webapp to keep image small. Rename runnable JAR to server-boot.jar.
+RUN rm -rf /install/webapps/server/
# Step 2 - Run Ant Deploy
-FROM openjdk:${JDK_VERSION}-slim as ant_build
+FROM docker.io/eclipse-temurin:${JDK_VERSION} AS ant_build
ARG TARGET_DIR=dspace-installer
# COPY the /install directory from 'build' container to /dspace-src in this container
COPY --from=build /install /dspace-src
WORKDIR /dspace-src
# Create the initial install deployment using ANT
-ENV ANT_VERSION 1.10.12
-ENV ANT_HOME /tmp/ant-$ANT_VERSION
-ENV PATH $ANT_HOME/bin:$PATH
-# Need wget to install ant
-RUN apt-get update \
- && apt-get install -y --no-install-recommends wget \
- && apt-get purge -y --auto-remove \
- && rm -rf /var/lib/apt/lists/*
+ENV ANT_VERSION=1.10.12
+ENV ANT_HOME=/tmp/ant-$ANT_VERSION
+ENV PATH=$ANT_HOME/bin:$PATH
# Download and install 'ant'
RUN mkdir $ANT_HOME && \
- wget -qO- "https://archive.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME
+ curl --silent --show-error --location --fail --retry 5 --output /tmp/apache-ant.tar.gz \
+ https://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && \
+ tar -zx --strip-components=1 -f /tmp/apache-ant.tar.gz -C $ANT_HOME && \
+ rm /tmp/apache-ant.tar.gz
# Run necessary 'ant' deploy scripts
RUN ant init_installation update_configs update_code update_webapps
-# Step 3 - Run tomcat
-# Create a new tomcat image that does not retain the the build directory contents
-FROM tomcat:9-jdk${JDK_VERSION}
+# Step 3 - Start up DSpace via Runnable JAR
+FROM docker.io/eclipse-temurin:${JDK_VERSION}
+# NOTE: DSPACE_INSTALL must align with the "dspace.dir" default configuration.
ENV DSPACE_INSTALL=/dspace
-ENV TOMCAT_INSTALL=/usr/local/tomcat
-# Copy the /dspace directory from 'ant_build' containger to /dspace in this container
+# Copy the /dspace directory from 'ant_build' container to /dspace in this container
COPY --from=ant_build /dspace $DSPACE_INSTALL
-# Enable the AJP connector in Tomcat's server.xml
-# NOTE: secretRequired="false" should only be used when AJP is NOT accessible from an external network. But, secretRequired="true" isn't supported by mod_proxy_ajp until Apache 2.5
-RUN sed -i '/Service name="Catalina".*/a \\n ' $TOMCAT_INSTALL/conf/server.xml
-# Expose Tomcat port and AJP port
-EXPOSE 8080 8009 8000
+WORKDIR $DSPACE_INSTALL
+# Need host command for "[dspace]/bin/make-handle-config"
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends host \
+ && apt-get purge -y --auto-remove \
+ && rm -rf /var/lib/apt/lists/*
+# Expose Tomcat port and debugging port
+EXPOSE 8080 8000
# Give java extra memory (2GB)
ENV JAVA_OPTS=-Xmx2000m
# Set up debugging
ENV CATALINA_OPTS=-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:8000
-
-# Link the DSpace 'server' webapp into Tomcat's webapps directory.
-# This ensures that when we start Tomcat, it runs from /server path (e.g. http://localhost:8080/server/)
-# Also link the v6.x (deprecated) REST API off the "/rest" path
-RUN ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/server && \
- ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest
-# If you wish to run "server" webapp off the ROOT path, then comment out the above RUN, and uncomment the below RUN.
-# You also MUST update the 'dspace.server.url' configuration to match.
-# Please note that server webapp should only run on one path at a time.
-#RUN mv /usr/local/tomcat/webapps/ROOT /usr/local/tomcat/webapps/ROOT.bk && \
-# ln -s $DSPACE_INSTALL/webapps/server /usr/local/tomcat/webapps/ROOT && \
-# ln -s $DSPACE_INSTALL/webapps/rest /usr/local/tomcat/webapps/rest
-
-# Overwrite the v6.x (deprecated) REST API's web.xml, so that we can run it on HTTP (defaults to requiring HTTPS)
-# WARNING: THIS IS OBVIOUSLY INSECURE. NEVER DO THIS IN PRODUCTION.
-COPY dspace/src/main/docker/test/rest_web.xml $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml
-RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml
+# On startup, run DSpace Runnable JAR
+ENTRYPOINT ["java", "-jar", "webapps/server-boot.jar", "--dspace.dir=$DSPACE_INSTALL"]
diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY
index e494c80c5d6e..d7e928147c89 100644
--- a/LICENSES_THIRD_PARTY
+++ b/LICENSES_THIRD_PARTY
@@ -25,25 +25,29 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* AWS Java SDK for AWS KMS (com.amazonaws:aws-java-sdk-kms:1.12.261 - https://aws.amazon.com/sdkforjava)
* AWS Java SDK for Amazon S3 (com.amazonaws:aws-java-sdk-s3:1.12.261 - https://aws.amazon.com/sdkforjava)
* JMES Path Query library (com.amazonaws:jmespath-java:1.12.261 - https://aws.amazon.com/sdkforjava)
+ * Titanium JSON-LD 1.1 (JRE11) (com.apicatalog:titanium-json-ld:1.3.2 - https://github.com/filip26/titanium-json-ld)
* HPPC Collections (com.carrotsearch:hppc:0.8.1 - http://labs.carrotsearch.com/hppc.html/hppc)
- * com.drewnoakes:metadata-extractor (com.drewnoakes:metadata-extractor:2.18.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)
- * Esri Geometry API for Java (com.esri.geometry:esri-geometry-api:2.2.0 - https://github.com/Esri/geometry-api-java)
- * ClassMate (com.fasterxml:classmate:1.3.0 - http://github.com/cowtowncoder/java-classmate)
- * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.13.4 - http://github.com/FasterXML/jackson)
- * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.13.4 - https://github.com/FasterXML/jackson-core)
- * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.13.4.2 - http://github.com/FasterXML/jackson)
+ * ClassMate (com.fasterxml:classmate:1.6.0 - https://github.com/FasterXML/java-classmate)
+ * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.16.0 - https://github.com/FasterXML/jackson)
+ * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.16.0 - https://github.com/FasterXML/jackson-core)
+ * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.16.0 - https://github.com/FasterXML/jackson)
* Jackson dataformat: CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 - http://github.com/FasterXML/jackson-dataformats-binary)
- * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.13.3 - http://github.com/FasterXML/jackson-dataformats-binary)
- * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.1 - https://github.com/FasterXML/jackson-dataformats-text)
- * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8)
- * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.11.1 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
- * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
- * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names)
+ * Jackson dataformat: Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.15.2 - https://github.com/FasterXML/jackson-dataformats-binary)
+ * Jackson-dataformat-TOML (com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.15.2 - https://github.com/FasterXML/jackson-dataformats-text)
+ * Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.2 - https://github.com/FasterXML/jackson-dataformats-text)
+ * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.4 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8)
+ * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
+ * Jackson Jakarta-RS: base (com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-base:2.16.2 - https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-base)
+ * Jackson Jakarta-RS: JSON (com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider:2.16.2 - https://github.com/FasterXML/jackson-jakarta-rs-providers/jackson-jakarta-rs-json-provider)
+ * Jackson module: Jakarta XML Bind Annotations (jakarta.xml.bind) (com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:2.16.2 - https://github.com/FasterXML/jackson-modules-base)
+ * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.15.4 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names)
* Java UUID Generator (com.fasterxml.uuid:java-uuid-generator:4.0.1 - https://github.com/cowtowncoder/java-uuid-generator)
- * Woodstox (com.fasterxml.woodstox:woodstox-core:6.2.4 - https://github.com/FasterXML/woodstox)
- * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.6 - https://github.com/flipkart-incubator/zjsonpatch/)
- * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.2 - https://github.com/ben-manes/caffeine)
+ * Woodstox (com.fasterxml.woodstox:woodstox-core:6.5.1 - https://github.com/FasterXML/woodstox)
+ * zjsonpatch (com.flipkart.zjsonpatch:zjsonpatch:0.4.16 - https://github.com/flipkart-incubator/zjsonpatch/)
+ * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine)
+ * Caffeine cache (com.github.ben-manes.caffeine:caffeine:3.1.6 - https://github.com/ben-manes/caffeine)
* btf (com.github.java-json-tools:btf:1.3 - https://github.com/java-json-tools/btf)
* jackson-coreutils (com.github.java-json-tools:jackson-coreutils:2.0 - https://github.com/java-json-tools/jackson-coreutils)
* jackson-coreutils-equivalence (com.github.java-json-tools:jackson-coreutils-equivalence:1.0 - https://github.com/java-json-tools/jackson-coreutils)
@@ -54,9 +58,9 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* 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.1 - http://findbugs.sourceforge.net/)
- * Gson (com.google.code.gson:gson:2.9.0 - https://github.com/google/gson/gson)
- * error-prone annotations (com.google.errorprone:error_prone_annotations:2.18.0 - https://errorprone.info/error_prone_annotations)
+ * FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
+ * Gson (com.google.code.gson:gson:2.10.1 - https://github.com/google/gson/gson)
+ * error-prone annotations (com.google.errorprone:error_prone_annotations:2.10.0 - https://errorprone.info/error_prone_annotations)
* Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess)
* Guava: Google Core Libraries for Java (com.google.guava:guava:32.0.0-jre - https://github.com/google/guava)
* Guava: Google Core Libraries for Java (JDK5 Backport) (com.google.guava:guava-jdk5:17.0 - http://code.google.com/p/guava-libraries/guava-jdk5)
@@ -64,25 +68,24 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.23.0 - https://github.com/google/google-http-java-client/google-http-client)
* GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.41.7 - https://github.com/googleapis/google-http-java-client/google-http-client-gson)
* Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.23.0 - https://github.com/google/google-http-java-client/google-http-client-jackson2)
+ * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/)
* J2ObjC Annotations (com.google.j2objc:j2objc-annotations:2.8 - https://github.com/google/j2objc/)
* Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.33.3 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client)
* ConcurrentLinkedHashMap (com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - http://code.google.com/p/concurrentlinkedhashmap)
* libphonenumber (com.googlecode.libphonenumber:libphonenumber:8.11.1 - https://github.com/google/libphonenumber/)
- * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.2 - https://jackcess.sourceforge.io)
- * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.1 - http://jackcessencrypt.sf.net)
- * project ':json-path' (com.jayway.jsonpath:json-path:2.6.0 - https://github.com/jayway/JsonPath)
- * project ':json-path-assert' (com.jayway.jsonpath:json-path-assert:2.6.0 - https://github.com/jayway/JsonPath)
+ * Jackcess (com.healthmarketscience.jackcess:jackcess:4.0.5 - https://jackcess.sourceforge.io)
+ * Jackcess Encrypt (com.healthmarketscience.jackcess:jackcess-encrypt:4.0.2 - http://jackcessencrypt.sf.net)
+ * json-path (com.jayway.jsonpath:json-path:2.9.0 - https://github.com/jayway/JsonPath)
+ * json-path-assert (com.jayway.jsonpath:json-path-assert:2.9.0 - https://github.com/jayway/JsonPath)
* Disruptor Framework (com.lmax:disruptor:3.4.2 - http://lmax-exchange.github.com/disruptor)
- * builder-commons (com.lyncode:builder-commons:1.0.2 - http://nexus.sonatype.org/oss-repository-hosting.html/builder-commons)
- * MaxMind DB Reader (com.maxmind.db:maxmind-db:1.2.2 - http://dev.maxmind.com/)
- * MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.11.0 - http://dev.maxmind.com/geoip/geoip2/web-services)
- * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:7.9 - https://bitbucket.org/connect2id/nimbus-jose-jwt)
- * opencsv (com.opencsv:opencsv:5.6 - http://opencsv.sf.net)
+ * MaxMind DB Reader (com.maxmind.db:maxmind-db:2.1.0 - http://dev.maxmind.com/)
+ * MaxMind GeoIP2 API (com.maxmind.geoip2:geoip2:2.17.0 - https://dev.maxmind.com/geoip?lang=en)
+ * Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.37.3 - https://bitbucket.org/connect2id/nimbus-jose-jwt)
+ * opencsv (com.opencsv:opencsv:5.9 - http://opencsv.sf.net)
* java-libpst (com.pff:java-libpst:0.9.3 - https://github.com/rjohnsondev/java-libpst)
* rome (com.rometools:rome:1.19.0 - http://rometools.com/rome)
* rome-modules (com.rometools:rome-modules:1.19.0 - http://rometools.com/rome-modules)
* rome-utils (com.rometools:rome-utils:1.19.0 - http://rometools.com/rome-utils)
- * fastinfoset (com.sun.xml.fastinfoset:FastInfoset:1.2.15 - http://fi.java.net)
* T-Digest (com.tdunning:t-digest:3.1 - https://github.com/tdunning/t-digest)
* config (com.typesafe:config:1.3.3 - https://github.com/lightbend/config)
* ssl-config-core (com.typesafe:ssl-config-core_2.13:0.3.8 - https://github.com/lightbend/ssl-config)
@@ -94,240 +97,282 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* akka-stream (com.typesafe.akka:akka-stream_2.13:2.5.31 - https://akka.io/)
* scala-logging (com.typesafe.scala-logging:scala-logging_2.13:3.9.2 - https://github.com/lightbend/scala-logging)
* JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk)
- * SparseBitSet (com.zaxxer:SparseBitSet:1.2 - 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 CLI (commons-cli:commons-cli:1.4 - http://commons.apache.org/proper/commons-cli/)
- * Apache Commons Codec (commons-codec:commons-codec:1.10 - http://commons.apache.org/proper/commons-codec/)
+ * Apache Commons CLI (commons-cli:commons-cli:1.6.0 - https://commons.apache.org/proper/commons-cli/)
+ * Apache Commons Codec (commons-codec:commons-codec:1.16.0 - https://commons.apache.org/proper/commons-codec/)
* Apache Commons Collections (commons-collections:commons-collections:3.2.2 - http://commons.apache.org/collections/)
- * Commons Digester (commons-digester:commons-digester:1.8.1 - http://commons.apache.org/digester/)
- * Apache Commons FileUpload (commons-fileupload:commons-fileupload:1.5 - https://commons.apache.org/proper/commons-fileupload/)
- * Apache Commons IO (commons-io:commons-io:2.7 - https://commons.apache.org/proper/commons-io/)
+ * 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/)
* Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/)
- * Apache Commons Logging (commons-logging:commons-logging:1.2 - http://commons.apache.org/proper/commons-logging/)
- * Apache Commons Validator (commons-validator:commons-validator:1.5.0 - http://commons.apache.org/proper/commons-validator/)
+ * Apache Commons Logging (commons-logging:commons-logging:1.3.0 - https://commons.apache.org/proper/commons-logging/)
+ * Apache Commons Validator (commons-validator:commons-validator:1.7 - http://commons.apache.org/proper/commons-validator/)
* GeoJson POJOs for Jackson (de.grundid.opendatalab:geojson-jackson:1.14 - https://github.com/opendatalab-de/geojson-jackson)
+ * broker-client (eu.openaire:broker-client:1.1.2 - http://api.openaire.eu/broker/broker-client)
* OpenAIRE Funders Model (eu.openaire:funders-model:2.0.0 - https://api.openaire.eu)
* Metrics Core (io.dropwizard.metrics:metrics-core:4.1.5 - https://metrics.dropwizard.io/metrics-core)
* Graphite Integration for Metrics (io.dropwizard.metrics:metrics-graphite:4.1.5 - https://metrics.dropwizard.io/metrics-graphite)
* Metrics Integration for Jetty 9.3 and higher (io.dropwizard.metrics:metrics-jetty9:4.1.5 - https://metrics.dropwizard.io/metrics-jetty9)
* Metrics Integration with JMX (io.dropwizard.metrics:metrics-jmx:4.1.5 - https://metrics.dropwizard.io/metrics-jmx)
* JVM Integration for Metrics (io.dropwizard.metrics:metrics-jvm:4.1.5 - https://metrics.dropwizard.io/metrics-jvm)
- * micrometer-core (io.micrometer:micrometer-core:1.9.11 - https://github.com/micrometer-metrics/micrometer)
- * Netty/Buffer (io.netty:netty-buffer:4.1.68.Final - https://netty.io/netty-buffer/)
- * Netty/Codec (io.netty:netty-codec:4.1.68.Final - https://netty.io/netty-codec/)
+ * SWORD v2 Common Server Library (forked) (io.gdcc:sword2-server:2.0.0 - https://github.com/gdcc/sword2-server)
+ * micrometer-commons (io.micrometer:micrometer-commons:1.12.6 - https://github.com/micrometer-metrics/micrometer)
+ * micrometer-core (io.micrometer:micrometer-core:1.12.6 - https://github.com/micrometer-metrics/micrometer)
+ * micrometer-jakarta9 (io.micrometer:micrometer-jakarta9:1.12.6 - https://github.com/micrometer-metrics/micrometer)
+ * micrometer-observation (io.micrometer:micrometer-observation:1.12.6 - https://github.com/micrometer-metrics/micrometer)
+ * Netty/Buffer (io.netty:netty-buffer:4.1.106.Final - https://netty.io/netty-buffer/)
+ * Netty/Buffer (io.netty:netty-buffer:4.1.99.Final - https://netty.io/netty-buffer/)
+ * Netty/Codec (io.netty:netty-codec:4.1.106.Final - https://netty.io/netty-codec/)
+ * Netty/Codec (io.netty:netty-codec:4.1.99.Final - https://netty.io/netty-codec/)
* Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.53.Final - https://netty.io/netty-codec-http/)
* Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.53.Final - https://netty.io/netty-codec-socks/)
- * Netty/Common (io.netty:netty-common:4.1.68.Final - https://netty.io/netty-common/)
- * Netty/Handler (io.netty:netty-handler:4.1.68.Final - https://netty.io/netty-handler/)
+ * Netty/Common (io.netty:netty-common:4.1.106.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/Proxy (io.netty:netty-handler-proxy:4.1.53.Final - https://netty.io/netty-handler-proxy/)
- * Netty/Resolver (io.netty:netty-resolver:4.1.68.Final - https://netty.io/netty-resolver/)
- * Netty/Transport (io.netty:netty-transport:4.1.68.Final - https://netty.io/netty-transport/)
- * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.68.Final - https://netty.io/netty-transport-native-epoll/)
- * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.68.Final - https://netty.io/netty-transport-native-unix-common/)
+ * 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/Transport (io.netty:netty-transport:4.1.99.Final - https://netty.io/netty-transport/)
+ * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.99.Final - https://netty.io/netty-transport-native-epoll/)
+ * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.106.Final - https://netty.io/netty-transport-native-unix-common/)
+ * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.99.Final - https://netty.io/netty-transport-native-unix-common/)
* OpenTracing API (io.opentracing:opentracing-api:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-api)
* OpenTracing-noop (io.opentracing:opentracing-noop:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-noop)
* OpenTracing-util (io.opentracing:opentracing-util:0.33.0 - https://github.com/opentracing/opentracing-java/opentracing-util)
* Google S2 geometry library (io.sgr:s2-geometry-library-java:1.0.0 - https://github.com/sgr-io/s2-geometry-library-java)
+ * Jandex: Core (io.smallrye:jandex:3.1.2 - https://smallrye.io)
* swagger-annotations (io.swagger:swagger-annotations:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations)
* swagger-compat-spec-parser (io.swagger:swagger-compat-spec-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-compat-spec-parser)
* swagger-core (io.swagger:swagger-core:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-core)
* swagger-models (io.swagger:swagger-models:1.6.2 - https://github.com/swagger-api/swagger-core/modules/swagger-models)
* swagger-parser (io.swagger:swagger-parser:1.0.52 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser)
* swagger-annotations (io.swagger.core.v3:swagger-annotations:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-annotations)
+ * swagger-annotations-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-jakarta (io.swagger.core.v3:swagger-core-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-core-jakarta)
+ * swagger-integration-jakarta (io.swagger.core.v3:swagger-integration-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-integration-jakarta)
+ * swagger-jaxrs2-jakarta (io.swagger.core.v3:swagger-jaxrs2-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-jaxrs2-jakarta)
* swagger-models (io.swagger.core.v3:swagger-models:2.1.5 - https://github.com/swagger-api/swagger-core/modules/swagger-models)
+ * swagger-models-jakarta (io.swagger.core.v3:swagger-models-jakarta:2.2.21 - https://github.com/swagger-api/swagger-core/modules/swagger-models-jakarta)
* swagger-parser (io.swagger.parser.v3:swagger-parser:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser)
* swagger-parser (io.swagger.parser.v3:swagger-parser-core:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-core)
* swagger-parser-v2-converter (io.swagger.parser.v3:swagger-parser-v2-converter:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v2-converter)
* swagger-parser-v3 (io.swagger.parser.v3:swagger-parser-v3:2.0.23 - http://nexus.sonatype.org/oss-repository-hosting.html/swagger-parser-project/modules/swagger-parser-v3)
- * Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:2.0.2 - https://beanvalidation.org)
- * JSR107 API and SPI (javax.cache:cache-api:1.1.0 - https://github.com/jsr107/jsr107spec)
+ * Jakarta Dependency Injection (jakarta.inject:jakarta.inject-api:2.0.1 - https://github.com/eclipse-ee4j/injection-api)
+ * Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:3.0.2 - https://beanvalidation.org)
+ * JSR107 API and SPI (javax.cache:cache-api:1.1.1 - https://github.com/jsr107/jsr107spec)
* javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/)
- * Bean Validation API (javax.validation:validation-api:2.0.1.Final - http://beanvalidation.org)
+ * Bean Validation API (javax.validation:validation-api:1.1.0.Final - http://beanvalidation.org)
* jdbm (jdbm:jdbm:1.0 - no url defined)
- * Joda-Time (joda-time:joda-time:2.9.2 - http://www.joda.org/joda-time/)
+ * Joda-Time (joda-time:joda-time:2.12.5 - https://www.joda.org/joda-time/)
* Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.11.13 - https://bytebuddy.net/byte-buddy)
* Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.11.13 - https://bytebuddy.net/byte-buddy-agent)
* eigenbase-properties (net.hydromatic:eigenbase-properties:1.1.5 - http://github.com/julianhyde/eigenbase-properties)
* json-unit-core (net.javacrumbs.json-unit:json-unit-core:2.19.0 - https://github.com/lukas-krecan/JsonUnit/json-unit-core)
* "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:1.2 - http://www.minidev.net/)
- * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.4.7 - https://urielch.github.io/)
- * JSON Small and Fast Parser (net.minidev:json-smart:2.3 - http://www.minidev.net/)
- * JSON Small and Fast Parser (net.minidev:json-smart:2.4.7 - https://urielch.github.io/)
+ * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.5.0 - https://urielch.github.io/)
+ * JSON Small and Fast Parser (net.minidev:json-smart:2.5.0 - https://urielch.github.io/)
* 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)
- * Apache Ant Core (org.apache.ant:ant:1.10.11 - https://ant.apache.org/)
- * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.11 - https://ant.apache.org/)
- * Apache Commons BCEL (org.apache.bcel:bcel:6.6.0 - https://commons.apache.org/proper/commons-bcel)
- * Calcite Core (org.apache.calcite:calcite-core:1.27.0 - https://calcite.apache.org)
- * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.27.0 - https://calcite.apache.org)
- * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.18.0 - https://calcite.apache.org/avatica)
- * Apache Commons Collections (org.apache.commons:commons-collections4:4.1 - http://commons.apache.org/proper/commons-collections/)
- * Apache Commons Compress (org.apache.commons:commons-compress:1.21 - https://commons.apache.org/proper/commons-compress/)
- * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.8.0 - https://commons.apache.org/proper/commons-configuration/)
- * Apache Commons CSV (org.apache.commons:commons-csv:1.9.0 - https://commons.apache.org/proper/commons-csv/)
- * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.9.0 - https://commons.apache.org/dbcp/)
+ * Abdera Parser (org.apache.abdera:abdera-parser:1.1.3 - http://abdera.apache.org/abdera-parser)
+ * Apache Ant Core (org.apache.ant:ant:1.10.14 - https://ant.apache.org/)
+ * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.14 - https://ant.apache.org/)
+ * Apache Commons BCEL (org.apache.bcel:bcel:6.7.0 - https://commons.apache.org/proper/commons-bcel)
+ * Calcite Core (org.apache.calcite:calcite-core:1.35.0 - https://calcite.apache.org)
+ * Calcite Linq4j (org.apache.calcite:calcite-linq4j:1.35.0 - https://calcite.apache.org)
+ * Apache Calcite Avatica (org.apache.calcite.avatica:avatica-core:1.23.0 - https://calcite.apache.org/avatica)
+ * Apache Calcite Avatica Metrics (org.apache.calcite.avatica:avatica-metrics:1.23.0 - https://calcite.apache.org/avatica)
+ * Apache Commons Collections (org.apache.commons:commons-collections4:4.4 - https://commons.apache.org/proper/commons-collections/)
+ * Apache Commons Compress (org.apache.commons:commons-compress:1.26.0 - https://commons.apache.org/proper/commons-compress/)
+ * Apache Commons Configuration (org.apache.commons:commons-configuration2:2.10.1 - https://commons.apache.org/proper/commons-configuration/)
+ * Apache Commons CSV (org.apache.commons:commons-csv:1.10.0 - https://commons.apache.org/proper/commons-csv/)
+ * Apache Commons DBCP (org.apache.commons:commons-dbcp2:2.11.0 - https://commons.apache.org/dbcp/)
* Apache Commons Exec (org.apache.commons:commons-exec:1.3 - http://commons.apache.org/proper/commons-exec/)
- * Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/)
+ * 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 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.11.1 - https://commons.apache.org/proper/commons-pool/)
+ * Apache Commons Pool (org.apache.commons:commons-pool2:2.12.0 - https://commons.apache.org/proper/commons-pool/)
* Apache Commons Text (org.apache.commons:commons-text:1.10.0 - https://commons.apache.org/proper/commons-text)
* Curator Client (org.apache.curator:curator-client:2.13.0 - http://curator.apache.org/curator-client)
* Curator Framework (org.apache.curator:curator-framework:2.13.0 - http://curator.apache.org/curator-framework)
* Curator Recipes (org.apache.curator:curator-recipes:2.13.0 - http://curator.apache.org/curator-recipes)
- * Apache Hadoop Annotations (org.apache.hadoop:hadoop-annotations:3.2.2 - no url defined)
- * Apache Hadoop Auth (org.apache.hadoop:hadoop-auth:3.2.2 - no url defined)
- * Apache Hadoop Common (org.apache.hadoop:hadoop-common:3.2.2 - no url defined)
- * Apache Hadoop HDFS Client (org.apache.hadoop:hadoop-hdfs-client:3.2.2 - no url defined)
+ * Apache Hadoop Annotations (org.apache.hadoop:hadoop-annotations:3.2.4 - no url defined)
+ * Apache Hadoop Auth (org.apache.hadoop:hadoop-auth:3.2.4 - no url defined)
+ * Apache Hadoop Common (org.apache.hadoop:hadoop-common:3.2.4 - no url defined)
+ * Apache Hadoop HDFS Client (org.apache.hadoop:hadoop-hdfs-client:3.2.4 - no url defined)
* htrace-core4 (org.apache.htrace:htrace-core4:4.1.0-incubating - http://incubator.apache.org/projects/htrace.html)
- * Apache HttpClient (org.apache.httpcomponents:httpclient:4.5.13 - http://hc.apache.org/httpcomponents-client)
- * Apache HttpClient Cache (org.apache.httpcomponents:httpclient-cache:4.2.6 - http://hc.apache.org/httpcomponents-client)
- * Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.15 - http://hc.apache.org/httpcomponents-core-ga)
- * Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.13 - http://hc.apache.org/httpcomponents-client)
- * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.4 - http://james.apache.org/mime4j/apache-mime4j-core)
- * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.4 - http://james.apache.org/mime4j/apache-mime4j-dom)
- * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:2.13.0 - http://jena.apache.org/apache-jena-libs/)
- * Apache Jena - ARQ (SPARQL 1.1 Query Engine) (org.apache.jena:jena-arq:2.13.0 - http://jena.apache.org/jena-arq/)
- * Apache Jena - Core (org.apache.jena:jena-core:2.13.0 - http://jena.apache.org/jena-core/)
- * Apache Jena - IRI (org.apache.jena:jena-iri:1.1.2 - http://jena.apache.org/jena-iri/)
- * Apache Jena - TDB (Native Triple Store) (org.apache.jena:jena-tdb:1.1.2 - http://jena.apache.org/jena-tdb/)
+ * Apache HttpClient (org.apache.httpcomponents:httpclient:4.5.14 - http://hc.apache.org/httpcomponents-client-ga)
+ * Apache HttpClient Cache (org.apache.httpcomponents:httpclient-cache:4.5.14 - http://hc.apache.org/httpcomponents-client-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 (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 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/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 - https://hc.apache.org/httpcomponents-core-5.1.x/5.1.3/httpcore5-h2/)
+ * Apache HttpComponents Core HTTP/2 (org.apache.httpcomponents.core5:httpcore5-h2:5.2.4 - https://hc.apache.org/httpcomponents-core-5.2.x/5.2.4/httpcore5-h2/)
+ * Apache James :: Mime4j :: Core (org.apache.james:apache-mime4j-core:0.8.10 - http://james.apache.org/mime4j/apache-mime4j-core)
+ * Apache James :: Mime4j :: DOM (org.apache.james:apache-mime4j-dom:0.8.11 - http://james.apache.org/mime4j/apache-mime4j-dom)
+ * Apache Jena - Libraries POM (org.apache.jena:apache-jena-libs:4.9.0 - https://jena.apache.org/apache-jena-libs/)
+ * Apache Jena - ARQ (org.apache.jena:jena-arq:4.9.0 - https://jena.apache.org/jena-arq/)
+ * Apache Jena - Base (org.apache.jena:jena-base:4.9.0 - https://jena.apache.org/jena-base/)
+ * Apache Jena - Core (org.apache.jena:jena-core:4.9.0 - https://jena.apache.org/jena-core/)
+ * Apache Jena - DBOE Base (org.apache.jena:jena-dboe-base:4.9.0 - https://jena.apache.org/jena-dboe-base/)
+ * Apache Jena - DBOE Indexes (org.apache.jena:jena-dboe-index:4.9.0 - https://jena.apache.org/jena-dboe-index/)
+ * Apache Jena - DBOE Storage (org.apache.jena:jena-dboe-storage:4.9.0 - https://jena.apache.org/jena-dboe-storage/)
+ * Apache Jena - DBOE Transactional Datastructures (org.apache.jena:jena-dboe-trans-data:4.9.0 - https://jena.apache.org/jena-dboe-trans-data/)
+ * Apache Jena - DBOE Transactions (org.apache.jena:jena-dboe-transaction:4.9.0 - https://jena.apache.org/jena-dboe-transaction/)
+ * Apache Jena - IRI (org.apache.jena:jena-iri:4.9.0 - https://jena.apache.org/jena-iri/)
+ * Apache Jena - RDF Connection (org.apache.jena:jena-rdfconnection:4.9.0 - https://jena.apache.org/jena-rdfconnection/)
+ * Apache Jena - RDF Patch (org.apache.jena:jena-rdfpatch:4.9.0 - https://jena.apache.org/jena-rdfpatch/)
+ * Apache Jena - SHACL (org.apache.jena:jena-shacl:4.9.0 - https://jena.apache.org/jena-shacl/)
+ * Apache Jena - ShEx (org.apache.jena:jena-shex:4.9.0 - https://jena.apache.org/jena-shex/)
+ * Apache Jena - TDB1 (Native Triple Store) (org.apache.jena:jena-tdb:4.9.0 - https://jena.apache.org/jena-tdb/)
+ * Apache Jena - TDB2 (Native Triple Store) (org.apache.jena:jena-tdb2:4.9.0 - https://jena.apache.org/jena-tdb2/)
* Kerby-kerb core (org.apache.kerby:kerb-core:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-core)
* Kerby-kerb Util (org.apache.kerby:kerb-util:1.0.1 - http://directory.apache.org/kerby/kerby-kerb/kerb-util)
* Kerby ASN1 Project (org.apache.kerby:kerby-asn1:1.0.1 - http://directory.apache.org/kerby/kerby-common/kerby-asn1)
* Kerby PKIX Project (org.apache.kerby:kerby-pkix:1.0.1 - http://directory.apache.org/kerby/kerby-pkix)
- * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-1.2-api/)
- * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-api/)
- * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-core/)
- * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-jul/)
- * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.1 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/)
- * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/)
- * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.20.0 - https://logging.apache.org/log4j/2.x/log4j-web/)
- * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common)
- * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu)
- * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji)
- * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori)
- * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic)
- * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn)
- * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel)
- * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs)
- * Lucene Classification (org.apache.lucene:lucene-classification:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-classification)
- * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-codecs)
- * Lucene Core (org.apache.lucene:lucene-core:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-core)
- * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-expressions)
- * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-grouping)
- * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-highlighter)
- * Lucene Join (org.apache.lucene:lucene-join:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-join)
- * Lucene Memory (org.apache.lucene:lucene-memory:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-memory)
- * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-misc)
- * Lucene Queries (org.apache.lucene:lucene-queries:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-queries)
- * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-queryparser)
- * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-sandbox)
- * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras)
- * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-spatial3d)
- * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.2 - https://lucene.apache.org/lucene-parent/lucene-suggest)
- * Apache FontBox (org.apache.pdfbox:fontbox:2.0.28 - http://pdfbox.apache.org/)
+ * Apache Log4j 1.x Compatibility API (org.apache.logging.log4j:log4j-1.2-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-1.2-api/)
+ * Apache Log4j API (org.apache.logging.log4j:log4j-api:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-api/)
+ * Apache Log4j Core (org.apache.logging.log4j:log4j-core:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-core/)
+ * Apache Log4j JUL Adapter (org.apache.logging.log4j:log4j-jul:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-jul/)
+ * Apache Log4j Layout for JSON template (org.apache.logging.log4j:log4j-layout-template-json:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-layout-template-json/)
+ * Apache Log4j SLF4J Binding (org.apache.logging.log4j:log4j-slf4j-impl:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j-impl/)
+ * Apache Log4j SLF4J 2.0 Binding (org.apache.logging.log4j:log4j-slf4j2-impl:2.21.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-slf4j2-impl/)
+ * Apache Log4j Web (org.apache.logging.log4j:log4j-web:2.23.1 - https://logging.apache.org/log4j/2.x/log4j/log4j-web/)
+ * Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-common)
+ * Lucene ICU Analysis Components (org.apache.lucene:lucene-analyzers-icu:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-icu)
+ * Lucene Kuromoji Japanese Morphological Analyzer (org.apache.lucene:lucene-analyzers-kuromoji:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-kuromoji)
+ * Lucene Nori Korean Morphological Analyzer (org.apache.lucene:lucene-analyzers-nori:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-nori)
+ * Lucene Phonetic Filters (org.apache.lucene:lucene-analyzers-phonetic:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-phonetic)
+ * Lucene Smart Chinese Analyzer (org.apache.lucene:lucene-analyzers-smartcn:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-smartcn)
+ * Lucene Stempel Analyzer (org.apache.lucene:lucene-analyzers-stempel:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-analyzers-stempel)
+ * Lucene Memory (org.apache.lucene:lucene-backward-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-backward-codecs)
+ * Lucene Classification (org.apache.lucene:lucene-classification:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-classification)
+ * Lucene codecs (org.apache.lucene:lucene-codecs:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-codecs)
+ * Lucene Core (org.apache.lucene:lucene-core:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-core)
+ * Lucene Expressions (org.apache.lucene:lucene-expressions:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-expressions)
+ * Lucene Grouping (org.apache.lucene:lucene-grouping:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-grouping)
+ * Lucene Highlighter (org.apache.lucene:lucene-highlighter:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-highlighter)
+ * Lucene Join (org.apache.lucene:lucene-join:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-join)
+ * Lucene Memory (org.apache.lucene:lucene-memory:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-memory)
+ * Lucene Miscellaneous (org.apache.lucene:lucene-misc:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-misc)
+ * Lucene Queries (org.apache.lucene:lucene-queries:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queries)
+ * Lucene QueryParsers (org.apache.lucene:lucene-queryparser:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-queryparser)
+ * Lucene Sandbox (org.apache.lucene:lucene-sandbox:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-sandbox)
+ * Lucene Spatial Extras (org.apache.lucene:lucene-spatial-extras:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial-extras)
+ * Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-spatial3d)
+ * Lucene Suggest (org.apache.lucene:lucene-suggest:8.11.3 - https://lucene.apache.org/lucene-parent/lucene-suggest)
+ * Apache FontBox (org.apache.pdfbox:fontbox:2.0.31 - http://pdfbox.apache.org/)
* PDFBox JBIG2 ImageIO plugin (org.apache.pdfbox:jbig2-imageio:3.0.4 - https://www.apache.org/jbig2-imageio/)
* Apache JempBox (org.apache.pdfbox:jempbox:1.8.17 - http://www.apache.org/pdfbox-parent/jempbox/)
- * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.28 - https://www.apache.org/pdfbox-parent/pdfbox/)
- * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.27 - https://www.apache.org/pdfbox-parent/pdfbox-tools/)
- * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.27 - https://www.apache.org/pdfbox-parent/xmpbox/)
- * Apache POI - Common (org.apache.poi:poi:5.2.3 - https://poi.apache.org/)
- * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.3 - https://poi.apache.org/)
- * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.3 - https://poi.apache.org/)
- * Apache POI (org.apache.poi:poi-scratchpad:5.2.3 - https://poi.apache.org/)
- * Apache Solr Core (org.apache.solr:solr-core:8.11.2 - https://lucene.apache.org/solr-parent/solr-core)
- * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.2 - https://lucene.apache.org/solr-parent/solr-solrj)
+ * Apache PDFBox (org.apache.pdfbox:pdfbox:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox/)
+ * Apache PDFBox tools (org.apache.pdfbox:pdfbox-tools:2.0.31 - https://www.apache.org/pdfbox-parent/pdfbox-tools/)
+ * Apache XmpBox (org.apache.pdfbox:xmpbox:2.0.31 - https://www.apache.org/pdfbox-parent/xmpbox/)
+ * Apache POI - Common (org.apache.poi:poi:5.2.5 - https://poi.apache.org/)
+ * Apache POI - API based on OPC and OOXML schemas (org.apache.poi:poi-ooxml:5.2.5 - https://poi.apache.org/)
+ * Apache POI (org.apache.poi:poi-ooxml-lite:5.2.5 - https://poi.apache.org/)
+ * Apache POI (org.apache.poi:poi-scratchpad:5.2.5 - https://poi.apache.org/)
+ * Apache Solr Core (org.apache.solr:solr-core:8.11.3 - https://lucene.apache.org/solr-parent/solr-core)
+ * Apache Solr Solrj (org.apache.solr:solr-solrj:8.11.3 - https://lucene.apache.org/solr-parent/solr-solrj)
* Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl)
* Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec)
- * Apache Thrift (org.apache.thrift:libthrift:0.9.2 - http://thrift.apache.org)
- * Apache Tika core (org.apache.tika:tika-core:2.5.0 - https://tika.apache.org/)
- * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.5.0 - https://tika.apache.org/tika-parser-apple-module/)
- * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.5.0 - https://tika.apache.org/tika-parser-audiovideo-module/)
- * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.5.0 - https://tika.apache.org/tika-parser-cad-module/)
- * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.5.0 - https://tika.apache.org/tika-parser-code-module/)
- * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.5.0 - https://tika.apache.org/tika-parser-crypto-module/)
- * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.5.0 - https://tika.apache.org/tika-parser-digest-commons/)
- * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.5.0 - https://tika.apache.org/tika-parser-font-module/)
- * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.5.0 - https://tika.apache.org/tika-parser-html-module/)
- * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.5.0 - https://tika.apache.org/tika-parser-image-module/)
- * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.5.0 - https://tika.apache.org/tika-parser-mail-commons/)
- * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.5.0 - https://tika.apache.org/tika-parser-mail-module/)
- * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.5.0 - https://tika.apache.org/tika-parser-microsoft-module/)
- * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.5.0 - https://tika.apache.org/tika-parser-miscoffice-module/)
- * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.5.0 - https://tika.apache.org/tika-parser-news-module/)
- * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.5.0 - https://tika.apache.org/tika-parser-ocr-module/)
- * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.5.0 - https://tika.apache.org/tika-parser-pdf-module/)
- * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.5.0 - https://tika.apache.org/tika-parser-pkg-module/)
- * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.5.0 - https://tika.apache.org/tika-parser-text-module/)
- * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.5.0 - https://tika.apache.org/tika-parser-webarchive-module/)
- * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.5.0 - https://tika.apache.org/tika-parser-xml-module/)
- * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.5.0 - https://tika.apache.org/tika-parser-xmp-commons/)
- * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.5.0 - https://tika.apache.org/tika-parser-zip-commons/)
- * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.5.0 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/)
- * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:9.0.75 - https://tomcat.apache.org/)
- * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:9.0.75 - https://tomcat.apache.org/)
- * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:9.0.75 - https://tomcat.apache.org/)
+ * Apache Thrift (org.apache.thrift:libthrift:0.18.1 - http://thrift.apache.org)
+ * Apache Tika core (org.apache.tika:tika-core:2.9.2 - https://tika.apache.org/)
+ * Apache Tika Apple parser module (org.apache.tika:tika-parser-apple-module:2.9.2 - https://tika.apache.org/tika-parser-apple-module/)
+ * Apache Tika audiovideo parser module (org.apache.tika:tika-parser-audiovideo-module:2.9.2 - https://tika.apache.org/tika-parser-audiovideo-module/)
+ * Apache Tika cad parser module (org.apache.tika:tika-parser-cad-module:2.9.2 - https://tika.apache.org/tika-parser-cad-module/)
+ * Apache Tika code parser module (org.apache.tika:tika-parser-code-module:2.9.2 - https://tika.apache.org/tika-parser-code-module/)
+ * Apache Tika crypto parser module (org.apache.tika:tika-parser-crypto-module:2.9.2 - https://tika.apache.org/tika-parser-crypto-module/)
+ * Apache Tika digest commons (org.apache.tika:tika-parser-digest-commons:2.9.2 - https://tika.apache.org/tika-parser-digest-commons/)
+ * Apache Tika font parser module (org.apache.tika:tika-parser-font-module:2.9.2 - https://tika.apache.org/tika-parser-font-module/)
+ * Apache Tika html parser module (org.apache.tika:tika-parser-html-module:2.9.2 - https://tika.apache.org/tika-parser-html-module/)
+ * Apache Tika image parser module (org.apache.tika:tika-parser-image-module:2.9.2 - https://tika.apache.org/tika-parser-image-module/)
+ * Apache Tika mail commons (org.apache.tika:tika-parser-mail-commons:2.9.2 - https://tika.apache.org/tika-parser-mail-commons/)
+ * Apache Tika mail parser module (org.apache.tika:tika-parser-mail-module:2.9.2 - https://tika.apache.org/tika-parser-mail-module/)
+ * Apache Tika Microsoft parser module (org.apache.tika:tika-parser-microsoft-module:2.9.2 - https://tika.apache.org/tika-parser-microsoft-module/)
+ * Apache Tika miscellaneous office format parser module (org.apache.tika:tika-parser-miscoffice-module:2.9.2 - https://tika.apache.org/tika-parser-miscoffice-module/)
+ * Apache Tika news parser module (org.apache.tika:tika-parser-news-module:2.9.2 - https://tika.apache.org/tika-parser-news-module/)
+ * Apache Tika OCR parser module (org.apache.tika:tika-parser-ocr-module:2.9.2 - https://tika.apache.org/tika-parser-ocr-module/)
+ * Apache Tika PDF parser module (org.apache.tika:tika-parser-pdf-module:2.9.2 - https://tika.apache.org/tika-parser-pdf-module/)
+ * Apache Tika package parser module (org.apache.tika:tika-parser-pkg-module:2.9.2 - https://tika.apache.org/tika-parser-pkg-module/)
+ * Apache Tika text parser module (org.apache.tika:tika-parser-text-module:2.9.2 - https://tika.apache.org/tika-parser-text-module/)
+ * Apache Tika WARC parser module (org.apache.tika:tika-parser-webarchive-module:2.9.2 - https://tika.apache.org/tika-parser-webarchive-module/)
+ * Apache Tika XML parser module (org.apache.tika:tika-parser-xml-module:2.9.2 - https://tika.apache.org/tika-parser-xml-module/)
+ * Apache Tika XMP commons (org.apache.tika:tika-parser-xmp-commons:2.9.2 - https://tika.apache.org/tika-parser-xmp-commons/)
+ * Apache Tika ZIP commons (org.apache.tika:tika-parser-zip-commons:2.9.2 - https://tika.apache.org/tika-parser-zip-commons/)
+ * Apache Tika standard parser package (org.apache.tika:tika-parsers-standard-package:2.9.2 - https://tika.apache.org/tika-parsers/tika-parsers-standard/tika-parsers-standard-package/)
+ * tomcat-embed-core (org.apache.tomcat.embed:tomcat-embed-core:10.1.24 - https://tomcat.apache.org/)
+ * tomcat-embed-el (org.apache.tomcat.embed:tomcat-embed-el:10.1.24 - https://tomcat.apache.org/)
+ * tomcat-embed-websocket (org.apache.tomcat.embed:tomcat-embed-websocket:10.1.24 - https://tomcat.apache.org/)
* Apache Velocity - Engine (org.apache.velocity:velocity-engine-core:2.3 - http://velocity.apache.org/engine/devel/velocity-engine-core/)
* Apache Velocity - JSR 223 Scripting (org.apache.velocity:velocity-engine-scripting:2.2 - http://velocity.apache.org/engine/devel/velocity-engine-scripting/)
- * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.22 - http://ws.apache.org/axiom/)
- * Abdera Model (FOM) Implementation (org.apache.ws.commons.axiom:fom-impl:1.2.22 - http://ws.apache.org/axiom/implementations/fom-impl/)
- * XmlBeans (org.apache.xmlbeans:xmlbeans:5.1.1 - https://xmlbeans.apache.org/)
+ * Axiom API (org.apache.ws.commons.axiom:axiom-api:1.2.14 - http://ws.apache.org/axiom/)
+ * Axiom Impl (org.apache.ws.commons.axiom:axiom-impl:1.2.14 - http://ws.apache.org/axiom/)
+ * XmlBeans (org.apache.xmlbeans:xmlbeans:5.2.0 - https://xmlbeans.apache.org/)
* Apache ZooKeeper - Server (org.apache.zookeeper:zookeeper:3.6.2 - http://zookeeper.apache.org/zookeeper)
* Apache ZooKeeper - Jute (org.apache.zookeeper:zookeeper-jute:3.6.2 - http://zookeeper.apache.org/zookeeper-jute)
- * org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.0 - https://github.com/apiguardian-team/apiguardian)
- * AssertJ fluent assertions (org.assertj:assertj-core:3.22.0 - https://assertj.github.io/doc/assertj-core/)
+ * 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)
* Evo Inflector (org.atteo:evo-inflector:1.3 - http://atteo.org/static/evo-inflector)
+ * Awaitility (org.awaitility:awaitility:4.2.1 - http://awaitility.org)
* jose4j (org.bitbucket.b_c:jose4j:0.6.5 - https://bitbucket.org/b_c/jose4j/)
* TagSoup (org.ccil.cowan.tagsoup:tagsoup:1.2.1 - http://home.ccil.org/~cowan/XML/tagsoup/)
+ * Woodstox (org.codehaus.woodstox:wstx-asl:3.2.6 - http://woodstox.codehaus.org)
* jems (org.dmfs:jems:1.18 - https://github.com/dmfs/jems)
* rfc3986-uri (org.dmfs:rfc3986-uri:0.8.1 - https://github.com/dmfs/uri-toolkit)
* Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/)
- * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client)
- * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client)
- * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server)
- * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server)
- * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server)
+ * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client)
+ * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client)
+ * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.15.v20190215 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server)
+ * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server)
+ * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.15.v20190215 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server)
+ * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server)
* Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty)
- * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-client)
- * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-continuation)
- * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-continuation)
- * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-deploy)
- * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-http)
- * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-io)
- * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-jmx)
+ * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client)
+ * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.15.v20190215 - http://www.eclipse.org/jetty)
+ * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation)
+ * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy)
+ * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http)
+ * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io)
+ * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx)
* Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty)
- * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-rewrite)
- * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-security)
- * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-security)
- * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-server)
- * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlet)
- * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlets)
- * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util)
- * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util-ajax)
- * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-webapp)
- * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-xml)
- * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-client)
- * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-common)
- * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-hpack)
- * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport)
- * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-server)
+ * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite)
+ * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security)
+ * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security)
+ * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server)
+ * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet)
+ * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.15.v20190215 - http://www.eclipse.org/jetty)
+ * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-servlets)
+ * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util)
+ * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax)
+ * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp)
+ * Jetty :: 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 :: ALPN :: API (org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 - http://www.eclipse.org/jetty/alpn-api)
+ * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client)
+ * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common)
+ * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack)
+ * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport)
+ * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.15.v20190215 - https://eclipse.org/jetty/http2-parent/http2-server)
+ * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-server)
* Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas)
- * Ehcache (org.ehcache:ehcache:3.4.0 - http://ehcache.org)
- * flyway-core (org.flywaydb:flyway-core:8.4.4 - https://flywaydb.org/flyway-core)
+ * Ehcache (org.ehcache:ehcache:3.10.8 - http://ehcache.org)
+ * flyway-core (org.flywaydb:flyway-core:10.10.0 - https://flywaydb.org/flyway-core)
+ * flyway-database-postgresql (org.flywaydb:flyway-database-postgresql:10.10.0 - https://flywaydb.org/flyway-database-postgresql)
* Ogg and Vorbis for Java, Core (org.gagravarr:vorbis-java-core:0.8 - https://github.com/Gagravarr/VorbisJava)
* Apache Tika plugin for Ogg, Vorbis and FLAC (org.gagravarr:vorbis-java-tika:0.8 - https://github.com/Gagravarr/VorbisJava)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
- * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.2.5.Final - http://hibernate.org/validator/hibernate-validator)
- * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:6.2.5.Final - http://hibernate.org/validator/hibernate-validator-cdi)
+ * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
+ * Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:8.0.1.Final - http://hibernate.org/validator/hibernate-validator)
+ * Hibernate Validator Portable Extension (org.hibernate.validator:hibernate-validator-cdi:8.0.1.Final - http://hibernate.org/validator/hibernate-validator-cdi)
+ * org.immutables.value-annotations (org.immutables:value-annotations:2.9.2 - http://immutables.org/value-annotations)
* leveldb (org.iq80.leveldb:leveldb:0.12 - http://github.com/dain/leveldb/leveldb)
* leveldb-api (org.iq80.leveldb:leveldb-api:0.12 - http://github.com/dain/leveldb/leveldb-api)
- * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/)
- * Java Annotation Indexer (org.jboss:jandex:2.4.2.Final - http://www.jboss.org/jandex)
+ * Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/)
* JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.org)
* 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)
@@ -335,115 +380,108 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* jtwig-spring (org.jtwig:jtwig-spring:5.87.0.RELEASE - http://jtwig.org)
* jtwig-spring-boot-starter (org.jtwig:jtwig-spring-boot-starter:5.87.0.RELEASE - http://jtwig.org)
* jtwig-web (org.jtwig:jtwig-web:5.87.0.RELEASE - http://jtwig.org)
+ * Proj4J (org.locationtech.proj4j:proj4j:1.1.5 - https://github.com/locationtech/proj4j)
* Spatial4J (org.locationtech.spatial4j:spatial4j:0.7 - https://projects.eclipse.org/projects/locationtech.spatial4j)
* MockServer Java Client (org.mock-server:mockserver-client-java:5.11.2 - http://www.mock-server.com)
* MockServer Core (org.mock-server:mockserver-core:5.11.2 - http://www.mock-server.com)
* MockServer JUnit 4 Integration (org.mock-server:mockserver-junit-rule:5.11.2 - http://www.mock-server.com)
* MockServer & Proxy Netty (org.mock-server:mockserver-netty:5.11.2 - http://www.mock-server.com)
- * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:apache-el:8.5.35.1 - https://github.com/jetty-project/jasper-jsp/apache-el)
- * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:8.5.35.1 - https://github.com/jetty-project/jasper-jsp/apache-jsp)
* Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty)
* Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester)
* Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util)
* Servlet Specification API (org.mortbay.jetty:servlet-api:2.5-20081211 - http://jetty.mortbay.org/servlet-api)
- * jwarc (org.netpreserve:jwarc:0.19.0 - https://github.com/iipc/jwarc)
+ * jwarc (org.netpreserve:jwarc:0.29.0 - https://github.com/iipc/jwarc)
* Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis)
* parboiled-core (org.parboiled:parboiled-core:1.3.1 - http://parboiled.org)
* parboiled-java (org.parboiled:parboiled-java:1.3.1 - http://parboiled.org)
+ * org.roaringbitmap:RoaringBitmap (org.roaringbitmap:RoaringBitmap:0.9.45 - https://github.com/RoaringBitmap/RoaringBitmap)
+ * org.roaringbitmap:shims (org.roaringbitmap:shims:0.9.45 - https://github.com/RoaringBitmap/RoaringBitmap)
* RRD4J (org.rrd4j:rrd4j:3.5 - https://github.com/rrd4j/rrd4j/)
- * Scala Library (org.scala-lang:scala-library:2.13.9 - https://www.scala-lang.org/)
+ * Scala Library (org.scala-lang:scala-library:2.13.11 - https://www.scala-lang.org/)
* Scala Compiler (org.scala-lang:scala-reflect:2.13.0 - https://www.scala-lang.org/)
* scala-collection-compat (org.scala-lang.modules:scala-collection-compat_2.13:2.1.6 - http://www.scala-lang.org/)
* scala-java8-compat (org.scala-lang.modules:scala-java8-compat_2.13:0.9.0 - http://www.scala-lang.org/)
* scala-parser-combinators (org.scala-lang.modules:scala-parser-combinators_2.13:1.1.2 - http://www.scala-lang.org/)
* scala-xml (org.scala-lang.modules:scala-xml_2.13:1.3.0 - http://www.scala-lang.org/)
* JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert)
- * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.36 - http://www.slf4j.org)
- * Spring AOP (org.springframework:spring-aop:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Beans (org.springframework:spring-beans:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Context (org.springframework:spring-context:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Context Support (org.springframework:spring-context-support:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Core (org.springframework:spring-core:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring JDBC (org.springframework:spring-jdbc:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Object/Relational Mapping (org.springframework:spring-orm:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring TestContext Framework (org.springframework:spring-test:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Transaction (org.springframework:spring-tx:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Web (org.springframework:spring-web:5.3.27 - https://github.com/spring-projects/spring-framework)
- * Spring Web MVC (org.springframework:spring-webmvc:5.3.27 - https://github.com/spring-projects/spring-framework)
- * spring-boot (org.springframework.boot:spring-boot:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot)
+ * JCL 1.2 implemented over SLF4J (org.slf4j:jcl-over-slf4j:2.0.11 - http://www.slf4j.org)
+ * Spring AOP (org.springframework:spring-aop:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Beans (org.springframework:spring-beans:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Context (org.springframework:spring-context:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Context Support (org.springframework:spring-context-support:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Core (org.springframework:spring-core:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Expression Language (SpEL) (org.springframework:spring-expression:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Commons Logging Bridge (org.springframework:spring-jcl:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring JDBC (org.springframework:spring-jdbc:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Object/Relational Mapping (org.springframework:spring-orm:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring TestContext Framework (org.springframework:spring-test:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Transaction (org.springframework:spring-tx:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Web (org.springframework:spring-web:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * Spring Web MVC (org.springframework:spring-webmvc:6.1.8 - https://github.com/spring-projects/spring-framework)
+ * spring-boot (org.springframework.boot:spring-boot:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-actuator (org.springframework.boot:spring-boot-actuator:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-actuator-autoconfigure (org.springframework.boot:spring-boot-actuator-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot)
* Spring Boot Configuration Processor (org.springframework.boot:spring-boot-configuration-processor:2.0.0.RELEASE - https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-tools/spring-boot-configuration-processor)
- * spring-boot-starter (org.springframework.boot:spring-boot-starter:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-test (org.springframework.boot:spring-boot-test:2.7.12 - https://spring.io/projects/spring-boot)
- * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:2.7.12 - https://spring.io/projects/spring-boot)
- * Spring Data Core (org.springframework.data:spring-data-commons:2.7.12 - https://www.spring.io/spring-data/spring-data-commons)
- * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:3.7.12 - 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:3.7.12 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc)
- * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:1.5.4 - https://github.com/spring-projects/spring-hateoas)
- * Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE - https://github.com/spring-projects/spring-plugin/spring-plugin-core)
- * spring-security-config (org.springframework.security:spring-security-config:5.7.8 - https://spring.io/projects/spring-security)
- * spring-security-core (org.springframework.security:spring-security-core:5.7.8 - https://spring.io/projects/spring-security)
- * spring-security-crypto (org.springframework.security:spring-security-crypto:5.7.8 - https://spring.io/projects/spring-security)
- * spring-security-test (org.springframework.security:spring-security-test:5.7.8 - https://spring.io/projects/spring-security)
- * spring-security-web (org.springframework.security:spring-security-web:5.7.8 - https://spring.io/projects/spring-security)
- * SWORD v2 :: Common Server Library (org.swordapp:sword2-server:1.0 - http://www.swordapp.org/)
- * snappy-java (org.xerial.snappy:snappy-java:1.1.7.6 - https://github.com/xerial/snappy-java)
+ * spring-boot-starter (org.springframework.boot:spring-boot-starter:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-cache (org.springframework.boot:spring-boot-starter-cache:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-data-rest (org.springframework.boot:spring-boot-starter-data-rest:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-log4j2 (org.springframework.boot:spring-boot-starter-log4j2:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-tomcat (org.springframework.boot:spring-boot-starter-tomcat:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-test (org.springframework.boot:spring-boot-test:3.2.6 - https://spring.io/projects/spring-boot)
+ * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:3.2.6 - https://spring.io/projects/spring-boot)
+ * Spring Data Core (org.springframework.data:spring-data-commons:3.2.6 - https://spring.io/projects/spring-data)
+ * Spring Data REST - Core (org.springframework.data:spring-data-rest-core:4.2.6 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-core)
+ * Spring Data REST - WebMVC (org.springframework.data:spring-data-rest-webmvc:4.2.6 - https://www.spring.io/spring-data/spring-data-rest-parent/spring-data-rest-webmvc)
+ * Spring HATEOAS (org.springframework.hateoas:spring-hateoas:2.2.2 - https://github.com/spring-projects/spring-hateoas)
+ * Spring Plugin - Core (org.springframework.plugin:spring-plugin-core:3.0.0 - https://github.com/spring-projects/spring-plugin/spring-plugin-core)
+ * spring-security-config (org.springframework.security:spring-security-config:6.2.4 - https://spring.io/projects/spring-security)
+ * spring-security-core (org.springframework.security:spring-security-core:6.2.4 - https://spring.io/projects/spring-security)
+ * spring-security-crypto (org.springframework.security:spring-security-crypto:6.2.4 - https://spring.io/projects/spring-security)
+ * spring-security-test (org.springframework.security:spring-security-test:6.2.4 - https://spring.io/projects/spring-security)
+ * spring-security-web (org.springframework.security:spring-security-web:6.2.4 - https://spring.io/projects/spring-security)
+ * snappy-java (org.xerial.snappy:snappy-java:1.1.10.1 - https://github.com/xerial/snappy-java)
* xml-matchers (org.xmlmatchers:xml-matchers:0.10 - http://code.google.com/p/xml-matchers/)
- * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.8.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.8.0 - https://www.xmlunit.org/xmlunit-placeholders/)
- * SnakeYAML (org.yaml:snakeyaml:1.30 - 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/)
- * Xalan Java Serializer (xalan:serializer:2.7.2 - http://xml.apache.org/xalan-j/)
- * xalan (xalan:xalan:2.7.0 - no url defined)
- * Xalan Java (xalan:xalan:2.7.2 - http://xml.apache.org/xalan-j/)
* Xerces2-j (xerces:xercesImpl:2.12.2 - https://xerces.apache.org/xerces2-j/)
- * XML Commons External Components XML APIs (xml-apis:xml-apis:1.4.01 - http://xml.apache.org/commons/components/external/)
BSD License:
- * AntLR Parser Generator (antlr:antlr:2.7.7 - http://www.antlr.org/)
* Adobe XMPCore (com.adobe.xmp:xmpcore:6.1.11 - https://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html)
* coverity-escapers (com.coverity.security:coverity-escapers:1.1.1 - http://coverity.com/security)
* Java Advanced Imaging Image I/O Tools API core (standalone) (com.github.jai-imageio:jai-imageio-core:1.4.0 - https://github.com/jai-imageio/jai-imageio-core)
- * JSONLD Java :: Core (com.github.jsonld-java:jsonld-java:0.5.1 - http://github.com/jsonld-java/jsonld-java/jsonld-java/)
- * curvesapi (com.github.virtuald:curvesapi:1.07 - https://github.com/virtuald/curvesapi)
- * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.11.0 - https://developers.google.com/protocol-buffers/protobuf-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)
+ * 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/)
* JZlib (com.jcraft:jzlib:1.1.3 - http://www.jcraft.com/jzlib/)
- * dnsjava (dnsjava:dnsjava:2.1.7 - http://www.dnsjava.org)
- * jaxen (jaxen:jaxen:1.1.6 - http://jaxen.codehaus.org/)
- * ANTLR 4 Runtime (org.antlr:antlr4-runtime:4.5.1-1 - http://www.antlr.org/antlr4-runtime)
- * commons-compiler (org.codehaus.janino:commons-compiler:3.0.9 - http://janino-compiler.github.io/commons-compiler/)
- * janino (org.codehaus.janino:janino:3.0.9 - http://janino-compiler.github.io/janino/)
+ * dnsjava (dnsjava:dnsjava:2.1.9 - http://www.dnsjava.org)
+ * 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/)
+ * commons-compiler (org.codehaus.janino:commons-compiler:3.1.8 - http://janino-compiler.github.io/commons-compiler/)
+ * janino (org.codehaus.janino:janino:3.1.8 - http://janino-compiler.github.io/janino/)
* Stax2 API (org.codehaus.woodstox:stax2-api:4.2.1 - http://github.com/FasterXML/stax2-api)
- * Hamcrest Date (org.exparity:hamcrest-date:2.0.7 - https://github.com/exparity/hamcrest-date)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * Hamcrest Date (org.exparity:hamcrest-date:2.0.8 - https://github.com/exparity/hamcrest-date)
* Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/)
- * Hamcrest All (org.hamcrest:hamcrest-all:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-all)
- * Hamcrest Core (org.hamcrest:hamcrest-core:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-core)
+ * Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/)
* HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.12 - http://hdrhistogram.github.io/HdrHistogram/)
* JBibTeX (org.jbibtex:jbibtex:1.0.20 - http://www.jbibtex.org)
* asm (org.ow2.asm:asm:8.0.1 - http://asm.ow2.io/)
- * asm-analysis (org.ow2.asm:asm-analysis:7.1 - http://asm.ow2.org/)
+ * asm-analysis (org.ow2.asm:asm-analysis:8.0.1 - http://asm.ow2.io/)
* asm-commons (org.ow2.asm:asm-commons:8.0.1 - http://asm.ow2.io/)
- * asm-tree (org.ow2.asm:asm-tree:7.1 - http://asm.ow2.org/)
+ * 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.6.0 - 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)
* JMatIO (org.tallison:jmatio:1.5 - https://github.com/tballison/jmatio)
* XMLUnit for Java (xmlunit:xmlunit:1.3 - http://xmlunit.sourceforge.net/)
@@ -454,101 +492,121 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Common Development and Distribution License (CDDL):
- * istack common utility code runtime (com.sun.istack:istack-commons-runtime:3.0.7 - http://java.net/istack-commons/istack-commons-runtime/)
- * JavaMail API (com.sun.mail:javax.mail:1.6.2 - http://javaee.github.io/javamail/javax.mail)
* JavaMail API (no providers) (com.sun.mail:mailapi:1.6.2 - http://javaee.github.io/javamail/mailapi)
* Old JAXB Core (com.sun.xml.bind:jaxb-core:2.3.0.1 - http://jaxb.java.net/jaxb-bundles/jaxb-core)
* 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:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca)
- * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api)
- * JavaBeans Activation Framework (JAF) (javax.activation:activation:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp)
+ * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca)
+ * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
+ * Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.0.0 - https://projects.eclipse.org/projects/ee4j.servlet)
+ * jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta)
* JavaBeans Activation Framework API jar (javax.activation:javax.activation-api:1.2.0 - http://java.net/all/javax.activation-api/)
- * javax.annotation API (javax.annotation:javax.annotation-api:1.3.2 - 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)
* Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net)
* javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net)
* 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)
- * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
- * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
- * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
+ * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
+ * HK2 API module (org.glassfish.hk2:hk2-api:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
+ * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
+ * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
* 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:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
- * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject)
- * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:2.3.1 - http://jaxb.java.net/jaxb-runtime-parent/jaxb-runtime)
- * TXW2 Runtime (org.glassfish.jaxb:txw2:2.3.1 - http://jaxb.java.net/jaxb-txw-parent/txw2)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
- * Java Transaction API (org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final - http://www.jboss.org/jboss-transaction-api_1.2_spec)
- * Extended StAX API (org.jvnet.staxex:stax-ex:1.8 - http://stax-ex.java.net/)
+ * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * 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:
- * net.cnri:cnri-servlet-container (net.cnri:cnri-servlet-container:3.0.0 - https://gitlab.com/cnri/cnri-servlet-container)
- * net.cnri:cnri-servlet-container-lib (net.cnri:cnri-servlet-container-lib:3.0.0 - https://gitlab.com/cnri/cnri-servlet-container)
+ * net.cnri:cnri-servlet-container-lib (net.cnri:cnri-servlet-container-lib:3.1.0 - https://gitlab.com/cnri/cnri-servlet-container)
* net.cnri:cnriutil (net.cnri:cnriutil:2.0 - https://gitlab.com/cnri/cnriutil)
+ Cordra (Version 2.5.0) License Agreement:
+
+ * net.cnri:cnri-servlet-container (net.cnri:cnri-servlet-container:3.1.0 - https://gitlab.com/cnri/cnri-servlet-container)
+
Eclipse Distribution License, Version 1.0:
- * Jakarta Activation API jar (jakarta.activation:jakarta.activation-api:1.2.2 - https://github.com/eclipse-ee4j/jaf/jakarta.activation-api)
- * Jakarta XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 - https://github.com/eclipse-ee4j/jaxb-api/jakarta.xml.bind-api)
- * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
- * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org)
+ * istack common utility code runtime (com.sun.istack:istack-commons-runtime:4.1.2 - https://projects.eclipse.org/projects/ee4j/istack-commons/istack-commons-runtime)
+ * Jakarta Activation API (jakarta.activation:jakarta.activation-api:2.1.3 - https://github.com/jakartaee/jaf-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 XML Binding API (jakarta.xml.bind:jakarta.xml.bind-api:4.0.2 - https://github.com/jakartaee/jaxb-api/jakarta.xml.bind-api)
+ * Angus Activation Registries (org.eclipse.angus:angus-activation:2.0.2 - https://github.com/eclipse-ee4j/angus-activation/angus-activation)
+ * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
+ * JAXB Core (org.glassfish.jaxb:jaxb-core:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
+ * JAXB Runtime (org.glassfish.jaxb:jaxb-runtime:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
+ * TXW2 Runtime (org.glassfish.jaxb:txw2:4.0.5 - https://eclipse-ee4j.github.io/jaxb-ri/)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
+ * MIME streaming extension (org.jvnet.mimepull:mimepull:1.9.15 - https://github.com/eclipse-ee4j/metro-mimepull)
+ * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core)
+ * 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)
Eclipse Public License:
* System Rules (com.github.stefanbirkner:system-rules:1.19.0 - http://stefanbirkner.github.io/system-rules/)
- * H2 Database Engine (com.h2database:h2:2.1.210 - https://h2database.com)
- * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca)
- * jakarta.ws.rs-api (jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 - https://github.com/eclipse-ee4j/jaxrs-api)
- * javax.persistence-api (javax.persistence:javax.persistence-api:2.2 - https://github.com/javaee/jpa-spec)
- * JUnit (junit:junit:4.13.1 - http://junit.org)
- * AspectJ Weaver (org.aspectj:aspectjweaver:1.9.7 - https://www.eclipse.org/aspectj/)
- * Eclipse Compiler for Java(TM) (org.eclipse.jdt:ecj:3.14.0 - http://www.eclipse.org/jdt)
+ * H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com)
+ * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca)
+ * Jakarta Expression Language API (jakarta.el:jakarta.el-api:5.0.1 - https://projects.eclipse.org/projects/ee4j.el)
+ * Jakarta Mail API (jakarta.mail:jakarta.mail-api:2.1.3 - https://projects.eclipse.org/projects/ee4j/jakarta.mail-api)
+ * Jakarta Persistence API (jakarta.persistence:jakarta.persistence-api:3.1.0 - https://github.com/eclipse-ee4j/jpa-api)
+ * Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.0.0 - https://projects.eclipse.org/projects/ee4j.servlet)
+ * jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta)
+ * Jakarta RESTful WS API (jakarta.ws.rs:jakarta.ws.rs-api:3.1.0 - https://github.com/eclipse-ee4j/jaxrs-api)
+ * JUnit (junit:junit:4.13.2 - http://junit.org)
+ * AspectJ Weaver (org.aspectj:aspectjweaver:1.9.22 - https://www.eclipse.org/aspectj/)
+ * Angus Mail default provider (org.eclipse.angus:jakarta.mail:2.0.3 - http://eclipse-ee4j.github.io/angus-mail/jakarta.mail)
* Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.15.v20190215 - http://tomcat.apache.org/taglibs/standard/)
- * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client)
- * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client)
- * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server)
- * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server)
- * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server)
+ * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-client)
+ * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-client)
+ * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.15.v20190215 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server)
+ * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-java-server)
+ * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.15.v20190215 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server)
+ * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-alpn-parent/jetty-alpn-server)
* Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.15.v20190215 - http://www.eclipse.org/jetty)
- * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-client)
- * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-continuation)
- * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-continuation)
- * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-deploy)
- * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-http)
- * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-io)
- * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-jmx)
+ * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-client)
+ * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.15.v20190215 - http://www.eclipse.org/jetty)
+ * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-continuation)
+ * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-deploy)
+ * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-http)
+ * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-io)
+ * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-jmx)
* Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.15.v20190215 - http://www.eclipse.org/jetty)
* Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.15.v20190215 - http://www.eclipse.org/jetty)
- * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-rewrite)
- * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.44.v20210927 - https://eclipse.org/jetty/jetty-security)
- * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-security)
- * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-server)
- * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlet)
- * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-servlets)
- * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util)
- * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-util-ajax)
- * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-webapp)
- * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.51.v20230217 - https://eclipse.org/jetty/jetty-xml)
- * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-client)
- * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-common)
- * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-hpack)
- * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.44.v20210927 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport)
- * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.51.v20230217 - https://eclipse.org/jetty/http2-parent/http2-server)
+ * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-rewrite)
+ * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-security)
+ * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-security)
+ * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-server)
+ * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-servlet)
+ * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.15.v20190215 - http://www.eclipse.org/jetty)
+ * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.53.v20231009 - https://eclipse.org/jetty/jetty-servlets)
+ * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util)
+ * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-util-ajax)
+ * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.54.v20240208 - https://eclipse.org/jetty/jetty-webapp)
+ * Jetty :: 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 :: ALPN :: API (org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 - http://www.eclipse.org/jetty/alpn-api)
+ * Jetty :: HTTP2 :: Client (org.eclipse.jetty.http2:http2-client:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-client)
+ * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.54.v20240208 - https://eclipse.org/jetty/http2-parent/http2-common)
+ * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-hpack)
+ * Jetty :: HTTP2 :: HTTP Client Transport (org.eclipse.jetty.http2:http2-http-client-transport:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-http-client-transport)
+ * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.15.v20190215 - https://eclipse.org/jetty/http2-parent/http2-server)
+ * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.53.v20231009 - https://eclipse.org/jetty/http2-parent/http2-server)
* Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1.2 - https://eclipse.org/jetty/jetty-schemas)
- * HK2 API module (org.glassfish.hk2:hk2-api:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
- * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
- * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
+ * JSON-P Default Provider (org.glassfish:jakarta.json:2.0.1 - https://github.com/eclipse-ee4j/jsonp)
+ * HK2 API module (org.glassfish.hk2:hk2-api:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-api)
+ * ServiceLocator Default Implementation (org.glassfish.hk2:hk2-locator:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-locator)
+ * HK2 Implementation Utilities (org.glassfish.hk2:hk2-utils:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/hk2-utils)
* 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:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
- * javax.inject:1 as OSGi bundle (org.glassfish.hk2.external:jakarta.inject:2.6.1 - https://github.com/eclipse-ee4j/glassfish-hk2/external/jakarta.inject)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
- * Java Persistence API, Version 2.1 (org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final - http://hibernate.org)
+ * aopalliance version 1.0 repackaged as a module (org.glassfish.hk2.external:aopalliance-repackaged:3.0.5 - https://github.com/eclipse-ee4j/glassfish-hk2/external/aopalliance-repackaged)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
+ * org.locationtech.jts:jts-core (org.locationtech.jts:jts-core:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-core)
+ * org.locationtech.jts.io:jts-io-common (org.locationtech.jts.io:jts-io-common:1.19.0 - https://www.locationtech.org/projects/technology.jts/jts-modules/jts-io/jts-io-common)
* Jetty Server (org.mortbay.jetty:jetty:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/modules/jetty)
* Jetty Servlet Tester (org.mortbay.jetty:jetty-servlet-tester:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-servlet-tester)
* Jetty Utilities (org.mortbay.jetty:jetty-util:6.1.26 - http://www.eclipse.org/jetty/jetty-parent/project/jetty-util)
@@ -564,14 +622,13 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
* uri-template (com.github.java-json-tools:uri-template:0.10 - https://github.com/java-json-tools/uri-template)
* 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)
- * Hibernate ORM - hibernate-core (org.hibernate:hibernate-core:5.6.15.Final - https://hibernate.org/orm)
- * Hibernate ORM - hibernate-jcache (org.hibernate:hibernate-jcache:5.6.15.Final - https://hibernate.org/orm)
- * Hibernate ORM - hibernate-jpamodelgen (org.hibernate:hibernate-jpamodelgen:5.6.15.Final - https://hibernate.org/orm)
- * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:5.1.2.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-jcache (org.hibernate.orm:hibernate-jcache:6.4.8.Final - https://hibernate.org/orm)
+ * Hibernate ORM - hibernate-jpamodelgen (org.hibernate.orm:hibernate-jpamodelgen:6.4.8.Final - https://hibernate.org/orm)
* im4java (org.im4java:im4java:1.4.0 - http://sourceforge.net/projects/im4java/)
- * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/)
- * XOM (xom:xom:1.2.5 - http://xom.nu)
- * XOM (xom:xom:1.3.7 - https://xom.nu)
+ * Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/)
+ * XOM (xom:xom:1.3.9 - https://xom.nu)
Go License:
@@ -579,63 +636,70 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
Handle.Net Public License Agreement (Ver.2):
- * Handle Server (net.handle:handle:9.3.0 - https://www.handle.net)
+ * Handle Server (net.handle:handle:9.3.1 - https://www.handle.net)
+
+ ISC License:
+
+ * Simple Magic (com.j256.simplemagic:simplemagic:1.17 - https://256stuff.com/sources/simplemagic/)
MIT License:
+ * dexx (com.github.andrewoma.dexx:collection:0.7 - https://github.com/andrewoma/dexx)
* better-files (com.github.pathikrit:better-files_2.13:3.9.1 - https://github.com/pathikrit/better-files)
* Java SemVer (com.github.zafarkhaja:java-semver:0.9.0 - https://github.com/zafarkhaja/jsemver)
- * dd-plist (com.googlecode.plist:dd-plist:1.25 - http://www.github.com/3breadt/dd-plist)
- * DigitalCollections: IIIF API Library (de.digitalcollections.iiif:iiif-apis:0.3.9 - https://github.com/dbmdz/iiif-apis)
+ * 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)
* s3mock (io.findify:s3mock_2.13:0.2.6 - https://github.com/findify/s3mock)
+ * ClassGraph (io.github.classgraph:classgraph:4.8.165 - https://github.com/classgraph/classgraph)
* JOpt Simple (net.sf.jopt-simple:jopt-simple:5.0.4 - http://jopt-simple.github.io/jopt-simple)
- * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk15on:1.70 - https://www.bouncycastle.org/java.html)
- * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.70 - https://www.bouncycastle.org/java.html)
- * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.70 - https://www.bouncycastle.org/java.html)
- * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk15on:1.70 - https://www.bouncycastle.org/java.html)
+ * Bouncy Castle S/MIME API (org.bouncycastle:bcmail-jdk18on:1.77 - https://www.bouncycastle.org/java.html)
+ * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.67 - http://www.bouncycastle.org/java.html)
+ * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html)
+ * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.67 - http://www.bouncycastle.org/java.html)
+ * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html)
+ * Bouncy Castle ASN.1 Extension and Utility APIs (org.bouncycastle:bcutil-jdk18on:1.78.1 - https://www.bouncycastle.org/java.html)
* org.brotli:dec (org.brotli:dec:0.1.2 - http://brotli.org/dec)
- * Checker Qual (org.checkerframework:checker-qual:3.10.0 - https://checkerframework.org)
* Checker Qual (org.checkerframework:checker-qual:3.31.0 - https://checkerframework.org)
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
* mockito-core (org.mockito:mockito-core:3.12.4 - https://github.com/mockito/mockito)
* mockito-inline (org.mockito:mockito-inline:3.12.4 - https://github.com/mockito/mockito)
- * ORCID - Model (org.orcid:orcid-model:3.0.2 - http://github.com/ORCID/orcid-model)
- * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.36 - http://www.slf4j.org)
- * SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org)
+ * SLF4J API Module (org.slf4j:slf4j-api:2.0.11 - http://www.slf4j.org)
* SLF4J Extensions Module (org.slf4j:slf4j-ext:1.7.28 - http://www.slf4j.org)
* HAL Browser (org.webjars:hal-browser:ad9b865 - http://webjars.org)
* toastr (org.webjars.bowergithub.codeseven:toastr:2.1.4 - http://webjars.org)
* backbone (org.webjars.bowergithub.jashkenas:backbone:1.4.1 - https://www.webjars.org)
* underscore (org.webjars.bowergithub.jashkenas:underscore:1.13.2 - https://www.webjars.org)
- * jquery (org.webjars.bowergithub.jquery:jquery-dist:3.6.0 - https://www.webjars.org)
- * urijs (org.webjars.bowergithub.medialize:uri.js:1.19.10 - https://www.webjars.org)
- * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.1 - https://www.webjars.org)
- * core-js (org.webjars.npm:core-js:3.30.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)
+ * bootstrap (org.webjars.bowergithub.twbs:bootstrap:4.6.2 - https://www.webjars.org)
+ * core-js (org.webjars.npm:core-js:3.37.1 - https://www.webjars.org)
* @json-editor/json-editor (org.webjars.npm:json-editor__json-editor:2.6.1 - https://www.webjars.org)
Mozilla Public License:
- * juniversalchardet (com.googlecode.juniversalchardet:juniversalchardet:1.0.3 - http://juniversalchardet.googlecode.com/)
- * H2 Database Engine (com.h2database:h2:2.1.210 - https://h2database.com)
+ * juniversalchardet (com.github.albfernandez:juniversalchardet:2.4.0 - https://github.com/albfernandez/juniversalchardet)
+ * H2 Database Engine (com.h2database:h2:2.2.224 - https://h2database.com)
* Saxon-HE (net.sf.saxon:Saxon-HE:9.8.0-14 - http://www.saxonica.com/)
- * Javassist (org.javassist:javassist:3.25.0-GA - http://www.javassist.org/)
+ * Javassist (org.javassist:javassist:3.29.2-GA - http://www.javassist.org/)
* Mozilla Rhino (org.mozilla:rhino:1.7.7.2 - https://developer.mozilla.org/en/Rhino)
Public Domain:
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-core-common (org.glassfish.jersey.core:jersey-common:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-core-common (org.glassfish.jersey.core:jersey-common:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-common)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * 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/)
- * JSON in Java (org.json:json:20230227 - 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/)
* 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:
- * Java Unrar (com.github.junrar:junrar:7.5.3 - https://github.com/junrar/junrar)
+ * Java Unrar (com.github.junrar:junrar:7.5.5 - https://github.com/junrar/junrar)
Unicode/ICU License:
@@ -643,10 +707,12 @@ https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines
W3C license:
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
jQuery license:
- * jersey-core-client (org.glassfish.jersey.core:jersey-client:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
- * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:2.35 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-core-client (org.glassfish.jersey.core:jersey-client:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/jersey-client)
+ * jersey-inject-hk2 (org.glassfish.jersey.inject:jersey-hk2:3.1.5 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-hk2)
+ * jersey-media-multipart (org.glassfish.jersey.media:jersey-media-multipart:3.1.3 - https://projects.eclipse.org/projects/ee4j.jersey/project/jersey-media-multipart)
diff --git a/README.md b/README.md
index af9158eff361..1d93abe49948 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ DSpace consists of both a Java-based backend and an Angular-based frontend.
* The REST Contract is at https://github.com/DSpace/RestContract
* Frontend (https://github.com/DSpace/dspace-angular/) is the User Interface built on the REST API
-Prior versions of DSpace (v6.x and below) used two different UIs (XMLUI and JSPUI). Those UIs are no longer supported in v7 (and above).
+Prior versions of DSpace (v6.x and below) used two different UIs (XMLUI and JSPUI). Those UIs are no longer supported in v7 and above.
* A maintenance branch for older versions is still available, see `dspace-6_x` for 6.x maintenance.
## Downloads
@@ -33,18 +33,18 @@ Prior versions of DSpace (v6.x and below) used two different UIs (XMLUI and JSPU
Documentation for each release may be viewed online or downloaded via our [Documentation Wiki](https://wiki.lyrasis.org/display/DSDOC/).
The latest DSpace Installation instructions are available at:
-https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace
+https://wiki.lyrasis.org/display/DSDOC8x/Installing+DSpace
Please be aware that, as a Java web application, DSpace requires a database (PostgreSQL)
and a servlet container (usually Tomcat) in order to function.
More information about these and all other prerequisites can be found in the Installation instructions above.
-## Running DSpace 7 in Docker
+## Running DSpace 8 in Docker
NOTE: At this time, we do not have production-ready Docker images for DSpace.
That said, we do have quick-start Docker Compose scripts for development or testing purposes.
-See [Running DSpace 7 with Docker Compose](dspace/src/main/docker-compose/README.md)
+See [Running DSpace 8 with Docker Compose](dspace/src/main/docker-compose/README.md)
## Contributing
@@ -64,7 +64,7 @@ Great Q&A is also available under the [DSpace tag on Stackoverflow](http://stack
Additional support options are at https://wiki.lyrasis.org/display/DSPACE/Support
DSpace also has an active service provider network. If you'd rather hire a service provider to
-install, upgrade, customize or host DSpace, then we recommend getting in touch with one of our
+install, upgrade, customize, or host DSpace, then we recommend getting in touch with one of our
[Registered Service Providers](http://www.dspace.org/service-providers).
## Issue Tracker
@@ -112,7 +112,7 @@ run automatically by [GitHub Actions](https://github.com/DSpace/DSpace/actions?q
```
* How to run only tests of a specific DSpace module
```
- # Before you can run only one module's tests, other modules may need installing into your ~/.m2
+ # Before you can run only one module's tests, other modules may need to be installed into your ~/.m2
cd [dspace-src]
mvn clean install
diff --git a/checkstyle.xml b/checkstyle.xml
index e0fa808d83cb..a33fc4831950 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -92,7 +92,7 @@ For more information on CheckStyle configurations below, see: http://checkstyle.
-
+
diff --git a/docker-compose-cli.yml b/docker-compose-cli.yml
index 9c66fed6835b..3e2c9ba6a50a 100644
--- a/docker-compose-cli.yml
+++ b/docker-compose-cli.yml
@@ -1,8 +1,12 @@
-version: "3.7"
-
+networks:
+ # Default to using network named 'dspacenet' from docker-compose.yml.
+ # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet")
+ default:
+ name: ${COMPOSE_PROJECT_NAME}_dspacenet
+ external: true
services:
dspace-cli:
- image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
+ image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}"
container_name: dspace-cli
build:
context: .
@@ -26,13 +30,8 @@ services:
- ./dspace/config:/dspace/config
entrypoint: /dspace/bin/dspace
command: help
- networks:
- - dspacenet
tty: true
stdin_open: true
volumes:
assetstore:
-
-networks:
- dspacenet:
diff --git a/docker-compose.yml b/docker-compose.yml
index 36ba6af2c981..ab4f8adc98c0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,3 @@
-version: '3.7'
networks:
dspacenet:
ipam:
@@ -28,19 +27,18 @@ services:
# proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
proxies__P__trusted__P__ipranges: '172.23.0'
- image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}"
+ LOGGING_CONFIG: /dspace/config/log4j2-container.xml
+ image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
build:
context: .
dockerfile: Dockerfile.test
depends_on:
- dspacedb
networks:
- dspacenet:
+ - dspacenet
ports:
- published: 8080
target: 8080
- - published: 8009
- target: 8009
- published: 8000
target: 8000
stdin_open: true
@@ -54,19 +52,19 @@ services:
# Ensure that the database is ready BEFORE starting tomcat
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
# 2. Then, run database migration to init database tables
- # 3. Finally, start Tomcat
+ # 3. Finally, start DSpace
entrypoint:
- /bin/bash
- '-c'
- |
while (! /dev/null 2>&1; do sleep 1; done;
/dspace/bin/dspace database migrate
- catalina.sh run
+ java -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace
# DSpace PostgreSQL database container
dspacedb:
container_name: dspacedb
# Uses a custom Postgres image with pgcrypto installed
- image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}"
+ image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}"
build:
# Must build out of subdirectory to have access to install script for pgcrypto
context: ./dspace/src/main/docker/dspace-postgres-pgcrypto/
@@ -86,10 +84,12 @@ services:
# DSpace Solr container
dspacesolr:
container_name: dspacesolr
- image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}"
+ image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
build:
- context: .
- dockerfile: ./dspace/src/main/docker/dspace-solr/Dockerfile
+ context: ./dspace/src/main/docker/dspace-solr/
+ # Provide path to Solr configs necessary to build Docker image
+ additional_contexts:
+ solrconfigs: ./dspace/solr/
args:
SOLR_VERSION: "${SOLR_VER:-8.11}"
networks:
@@ -120,6 +120,10 @@ services:
cp -r /opt/solr/server/solr/configsets/search/* search
precreate-core statistics /opt/solr/server/solr/configsets/statistics
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
+ precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
+ cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
+ precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
+ cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
exec solr -f
volumes:
assetstore:
diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml
index d0d2bf8608ae..599303275f9f 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -12,7 +12,7 @@
org.dspace
dspace-parent
- 7.6
+ 9.0-SNAPSHOT
..
@@ -54,21 +54,21 @@
- org.hibernate
+ org.hibernate.orm
hibernate-jpamodelgen
${hibernate.version}
- javax.xml.bind
- jaxb-api
+ jakarta.xml.bind
+ jakarta.xml.bind-api
${jaxb-api.version}
- javax.annotation
- javax.annotation-api
- ${javax-annotation.version}
+ jakarta.annotation
+ jakarta.annotation-api
+ ${jakarta-annotation.version}
@@ -102,7 +102,7 @@
org.codehaus.mojo
build-helper-maven-plugin
- 3.0.0
+ 3.6.0
validate
@@ -116,7 +116,10 @@
org.codehaus.mojo
buildnumber-maven-plugin
- 1.4
+ 3.2.1
+
+ UNKNOWN_REVISION
+
validate
@@ -174,7 +177,7 @@
org.codehaus.mojo
jaxb2-maven-plugin
- 2.5.0
+ 3.2.0
workflow-curation
@@ -262,7 +265,7 @@
- ${agnostic.build.dir}/testing/dspace/
+ ${agnostic.build.dir}/testing/dspace
true
${agnostic.build.dir}/testing/dspace/solr/
@@ -321,7 +324,7 @@
- ${agnostic.build.dir}/testing/dspace/
+ ${agnostic.build.dir}/testing/dspace
true
${agnostic.build.dir}/testing/dspace/solr/
@@ -339,18 +342,23 @@
log4j-api
- org.hibernate
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+
+
+ org.hibernate.orm
hibernate-core
-
-
-
- org.javassist
- javassist
-
-
- org.hibernate
+ org.hibernate.orm
+ hibernate-jpamodelgen
+
+
+ org.hibernate.orm
hibernate-jcache
@@ -372,43 +380,49 @@
- javax.cache
- cache-api
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
- org.hibernate
- hibernate-jpamodelgen
+ javax.cache
+ cache-api
org.hibernate.validator
hibernate-validator-cdi
${hibernate-validator.version}
-
- org.hibernate.javax.persistence
- hibernate-jpa-2.1-api
- 1.0.2.Final
-
org.springframework
spring-orm
+
+
+
+ org.springframework
+ spring-jcl
+
+
net.handle
handle
+
net.cnri
cnri-servlet-container
+ runtime
-
+
- org.ow2.asm
- asm-commons
+ org.mortbay.jasper
+ apache-jsp
-
+
org.bouncycastle
bcpkix-jdk15on
@@ -419,10 +433,12 @@
-
+
+
org.eclipse.jetty
jetty-server
+ runtime
org.dspace
@@ -432,12 +448,6 @@
org.apache.jena
apache-jena-libs
pom
-
-
- log4j
- log4j
-
-
commons-cli
@@ -455,10 +465,6 @@
org.apache.commons
commons-dbcp2
-
- commons-fileupload
- commons-fileupload
-
commons-io
@@ -477,27 +483,30 @@
commons-validator
- com.sun.mail
- javax.mail
+ jakarta.mail
+ jakarta.mail-api
+ provided
- javax.servlet
- javax.servlet-api
+ org.eclipse.angus
+ jakarta.mail
+
+
+ jakarta.servlet
+ jakarta.servlet-api
provided
- javax.annotation
- javax.annotation-api
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+ jakarta.el
+ jakarta.el-api
jaxen
jaxen
-
-
- xom
- xom
-
-
org.jdom
@@ -531,7 +540,7 @@
org.hamcrest
- hamcrest-all
+ hamcrest
test
@@ -589,6 +598,13 @@
solr-core
test
${solr.client.version}
+
+
+
+ org.antlr
+ antlr4-runtime
+
+
org.apache.lucene
@@ -623,7 +639,7 @@
com.maxmind.geoip2
geoip2
- 2.11.0
+ 2.17.0
org.apache.ant
@@ -632,7 +648,7 @@
dnsjava
dnsjava
- 2.1.7
+ 3.6.2
@@ -668,7 +684,12 @@
org.flywaydb
flyway-core
- 8.4.4
+ ${flyway.version}
+
+
+ org.flywaydb
+ flyway-database-postgresql
+ ${flyway.version}
@@ -676,22 +697,6 @@
com.google.apis
google-api-services-analytics
-
- com.google.api-client
- google-api-client
-
-
- com.google.http-client
- google-http-client
-
-
- com.google.http-client
- google-http-client-jackson2
-
-
- com.google.oauth-client
- google-oauth-client
-
@@ -704,27 +709,21 @@
- joda-time
- joda-time
-
-
- javax.inject
- javax.inject
- 1
- jar
+ jakarta.inject
+ jakarta.inject-api
- javax.xml.bind
- jaxb-api
+ jakarta.xml.bind
+ jakarta.xml.bind-api
org.glassfish.jaxb
jaxb-runtime
-
+
org.glassfish.jersey.core
jersey-client
@@ -742,41 +741,28 @@
com.amazonaws
aws-java-sdk-s3
- 1.12.261
+ 1.12.779
+
+
- org.orcid
- orcid-model
- 3.0.2
+ org.dspace
+ orcid-model-jakarta
+ 3.3.0
-
- javax.validation
- validation-api
-
-
- com.fasterxml.jackson.jaxrs
- jackson-jaxrs-json-provider
-
-
- org.yaml
- snakeyaml
-
org.javassist
javassist
-
- io.swagger
- swagger-jersey-jaxrs
-
org.json
json
- 20230227
+ 20231013
@@ -791,25 +777,27 @@
com.opencsv
opencsv
- 5.6
+ 5.9
org.apache.velocity
velocity-engine-core
+ 2.4.1
org.xmlunit
xmlunit-core
+ 2.10.0
test
org.apache.bcel
bcel
- 6.6.0
+ 6.10.0
test
@@ -818,12 +806,25 @@
eu.openaire
funders-model
2.0.0
+
+
+
+ org.javassist
+ javassist
+
+
+
+
+
+ eu.openaire
+ broker-client
+ 1.1.2
org.mock-server
mockserver-junit-rule
- 5.11.2
+ 5.15.0
test
@@ -831,6 +832,20 @@
org.yaml
snakeyaml
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+
+ javax.servlet
+ javax.servlet-api
+
@@ -850,77 +865,5 @@
-
-
-
-
-
-
-
- io.netty
- netty-buffer
- 4.1.68.Final
-
-
- io.netty
- netty-transport
- 4.1.68.Final
-
-
- io.netty
- netty-common
- 4.1.68.Final
-
-
- io.netty
- netty-handler
- 4.1.68.Final
-
-
- io.netty
- netty-codec
- 4.1.68.Final
-
-
- org.apache.velocity
- velocity-engine-core
- 2.3
-
-
- org.xmlunit
- xmlunit-core
- 2.8.0
- test
-
-
- com.github.java-json-tools
- json-schema-validator
- 2.2.14
-
-
- jakarta.xml.bind
- jakarta.xml.bind-api
- 2.3.3
-
-
- javax.validation
- validation-api
- 2.0.1.Final
-
-
- io.swagger
- swagger-core
- 1.6.2
-
-
- org.scala-lang
- scala-library
- 2.13.9
- test
-
-
-
-
diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java
index 1cacbf6aedf6..2d782dc3b82a 100644
--- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java
+++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusHelper.java
@@ -22,9 +22,21 @@ public interface AccessStatusHelper {
*
* @param context the DSpace context
* @param item the item
+ * @param threshold the embargo threshold date
* @return an access status value
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getAccessStatusFromItem(Context context, Item item, Date threshold)
throws SQLException;
+
+ /**
+ * Retrieve embargo information for the item
+ *
+ * @param context the DSpace context
+ * @param item the item to check for embargo information
+ * @param threshold the embargo threshold date
+ * @return an embargo date
+ * @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;
}
diff --git a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java
index 544dc99cb4dd..01b370747932 100644
--- a/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/access/status/AccessStatusServiceImpl.java
@@ -8,6 +8,8 @@
package org.dspace.access.status;
import java.sql.SQLException;
+import java.time.LocalDate;
+import java.time.ZoneId;
import java.util.Date;
import org.dspace.access.status.service.AccessStatusService;
@@ -15,7 +17,6 @@
import org.dspace.core.Context;
import org.dspace.core.service.PluginService;
import org.dspace.services.ConfigurationService;
-import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
/**
@@ -55,7 +56,10 @@ public void init() throws Exception {
int month = configurationService.getIntProperty("access.status.embargo.forever.month");
int day = configurationService.getIntProperty("access.status.embargo.forever.day");
- forever_date = new LocalDate(year, month, day).toDate();
+ forever_date = Date.from(LocalDate.of(year, month, day)
+ .atStartOfDay()
+ .atZone(ZoneId.systemDefault())
+ .toInstant());
}
}
@@ -63,4 +67,9 @@ public void init() throws Exception {
public String getAccessStatus(Context context, Item item) throws SQLException {
return helper.getAccessStatusFromItem(context, item, forever_date);
}
+
+ @Override
+ public String getEmbargoFromItem(Context context, Item item) throws SQLException {
+ return helper.getEmbargoFromItem(context, item, forever_date);
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java
index a67fa67af3b9..5f0e6d8b259b 100644
--- a/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java
+++ b/dspace-api/src/main/java/org/dspace/access/status/DefaultAccessStatusHelper.java
@@ -26,6 +26,7 @@
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.Group;
+import org.joda.time.LocalDate;
/**
* Default plugin implementation of the access status helper.
@@ -33,6 +34,11 @@
* calculate the access status of an item based on the policies of
* the primary or the first bitstream in the original bundle.
* Users can override this method for enhanced functionality.
+ *
+ * The getEmbargoInformationFromItem method provides a simple logic to
+ * * retrieve embargo information of bitstreams from an item based on the policies of
+ * * the primary or the first bitstream in the original bundle.
+ * * Users can override this method for enhanced functionality.
*/
public class DefaultAccessStatusHelper implements AccessStatusHelper {
public static final String EMBARGO = "embargo";
@@ -54,12 +60,12 @@ public DefaultAccessStatusHelper() {
/**
* Look at the item's policies to determine an access status value.
- * It is also considering a date threshold for embargos and restrictions.
+ * It is also considering a date threshold for embargoes and restrictions.
*
* If the item is null, simply returns the "unknown" value.
*
* @param context the DSpace context
- * @param item the item to embargo
+ * @param item the item to check for embargoes
* @param threshold the embargo threshold date
* @return an access status value
*/
@@ -86,7 +92,7 @@ public String getAccessStatusFromItem(Context context, Item item, Date threshold
.findFirst()
.orElse(null);
}
- return caculateAccessStatusForDso(context, bitstream, threshold);
+ return calculateAccessStatusForDso(context, bitstream, threshold);
}
/**
@@ -104,7 +110,7 @@ public String getAccessStatusFromItem(Context context, Item item, Date threshold
* @param threshold the embargo threshold date
* @return an access status value
*/
- private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
+ private String calculateAccessStatusForDso(Context context, DSpaceObject dso, Date threshold)
throws SQLException {
if (dso == null) {
return METADATA_ONLY;
@@ -156,4 +162,87 @@ private String caculateAccessStatusForDso(Context context, DSpaceObject dso, Dat
}
return RESTRICTED;
}
+
+ /**
+ * Look at the policies of the primary (or first) bitstream of the item to retrieve its embargo.
+ *
+ * If the item is null, simply returns an empty map with no embargo information.
+ *
+ * @param context the DSpace context
+ * @param item the item to embargo
+ * @return an access status value
+ */
+ @Override
+ public String getEmbargoFromItem(Context context, Item item, Date threshold)
+ throws SQLException {
+ Date embargoDate;
+
+ // If Item status is not "embargo" then return a null embargo date.
+ String accessStatus = getAccessStatusFromItem(context, item, threshold);
+
+ if (item == null || !accessStatus.equals(EMBARGO)) {
+ return null;
+ }
+ // Consider only the original bundles.
+ List 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 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(LocalDate.now().toDate())) {
+ // 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;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java
index 43de5e3c47f1..e86c5a69f4cf 100644
--- a/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java
+++ b/dspace-api/src/main/java/org/dspace/access/status/service/AccessStatusService.java
@@ -18,7 +18,7 @@
* Configuration properties: (with examples)
* {@code
* # values for the forever embargo date threshold
- * # This threshold date is used in the default access status helper to dermine if an item is
+ * # This threshold date is used in the default access status helper to determine if an item is
* # restricted or embargoed based on the start date of the primary (or first) file policies.
* # In this case, if the policy start date is inferior to the threshold date, the status will
* # be embargo, else it will be restricted.
@@ -40,7 +40,18 @@ public interface AccessStatusService {
*
* @param context the DSpace context
* @param item the item
+ * @return an access status value
* @throws SQLException An exception that provides information on a database access error or other errors.
*/
public String getAccessStatus(Context context, Item item) throws SQLException;
+
+ /**
+ * Retrieve embargo information for the item
+ *
+ * @param context the DSpace context
+ * @param item the item to check for embargo information
+ * @return an embargo date
+ * @throws SQLException An exception that provides information on a database access error or other errors.
+ */
+ public String getEmbargoFromItem(Context context, Item item) throws SQLException;
}
diff --git a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java
index 81250e9c8259..58b85493915a 100644
--- a/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java
+++ b/dspace-api/src/main/java/org/dspace/administer/CreateAdministrator.java
@@ -116,6 +116,17 @@ public static void main(String[] argv)
protected CreateAdministrator()
throws Exception {
context = new Context();
+ try {
+ context.getDBConfig();
+ } catch (NullPointerException npr) {
+ // if database is null, there is no point in continuing. Prior to this exception and catch,
+ // NullPointerException was thrown, that wasn't very helpful.
+ throw new IllegalStateException("Problem connecting to database. This " +
+ "indicates issue with either network or version (or possibly some other). " +
+ "If you are running this in docker-compose, please make sure dspace-cli was " +
+ "built from the same sources as running dspace container AND that they are in " +
+ "the same project/network.");
+ }
groupService = EPersonServiceFactory.getInstance().getGroupService();
ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
}
diff --git a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java
index 2677cb20501f..80bda610c7dd 100644
--- a/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java
+++ b/dspace-api/src/main/java/org/dspace/administer/MetadataImporter.java
@@ -21,6 +21,8 @@
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.MetadataField;
import org.dspace.content.MetadataSchema;
@@ -30,8 +32,6 @@
import org.dspace.content.service.MetadataFieldService;
import org.dspace.content.service.MetadataSchemaService;
import org.dspace.core.Context;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -40,9 +40,9 @@
/**
* @author Richard Jones
*
- * This class takes an xml document as passed in the arguments and
+ * This class takes an XML document as passed in the arguments and
* uses it to create metadata elements in the Metadata Registry if
- * they do not already exist
+ * they do not already exist.
*
* The format of the XML file is as follows:
*
@@ -69,7 +69,7 @@ public class MetadataImporter {
/**
* logging category
*/
- private static final Logger log = LoggerFactory.getLogger(MetadataImporter.class);
+ private static final Logger log = LogManager.getLogger();
/**
* Default constructor
@@ -89,6 +89,7 @@ private MetadataImporter() { }
* @throws SAXException if parser error
* @throws NonUniqueMetadataException if duplicate metadata
* @throws RegistryImportException if import fails
+ * @throws XPathExpressionException passed through
**/
public static void main(String[] args)
throws ParseException, SQLException, IOException, TransformerException,
@@ -125,6 +126,7 @@ public static void main(String[] args)
* @throws SAXException if parser error
* @throws NonUniqueMetadataException if duplicate metadata
* @throws RegistryImportException if import fails
+ * @throws XPathExpressionException passed through
*/
public static void loadRegistry(String file, boolean forceUpdate)
throws SQLException, IOException, TransformerException, ParserConfigurationException, AuthorizeException,
@@ -203,7 +205,7 @@ private static void loadSchema(Context context, Node node, boolean updateExistin
if (s == null) {
// Schema does not exist - create
- log.info("Registering Schema " + name + " (" + namespace + ")");
+ log.info("Registering Schema {}({})", name, namespace);
metadataSchemaService.create(context, name, namespace);
} else {
// Schema exists - if it's the same namespace, allow the type imports to continue
@@ -215,7 +217,7 @@ private static void loadSchema(Context context, Node node, boolean updateExistin
// It's a different namespace - have we been told to update?
if (updateExisting) {
// Update the existing schema namespace and continue to type import
- log.info("Updating Schema " + name + ": New namespace " + namespace);
+ log.info("Updating Schema {}: New namespace {}", name, namespace);
s.setNamespace(namespace);
metadataSchemaService.update(context, s);
} else {
@@ -274,7 +276,7 @@ private static void loadType(Context context, Node node)
if (qualifier == null) {
fieldName = schema + "." + element;
}
- log.info("Registering metadata field " + fieldName);
+ log.info("Registering metadata field {}", fieldName);
MetadataField field = metadataFieldService.create(context, schemaObj, element, qualifier, scopeNote);
metadataFieldService.update(context, field);
}
diff --git a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java
index 13a1b3b5bbf8..8bbcfe0ff753 100644
--- a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java
+++ b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java
@@ -802,7 +802,7 @@ private static Element[] handleCollections(Context context,
// default the short description to the empty string
collectionService.setMetadataSingleValue(context, collection,
- MD_SHORT_DESCRIPTION, Item.ANY, " ");
+ MD_SHORT_DESCRIPTION, null, " ");
// import the rest of the metadata
for (Map.Entry entry : collectionMap.entrySet()) {
diff --git a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java
index f56cbdcce9e9..432c633ea591 100644
--- a/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java
+++ b/dspace-api/src/main/java/org/dspace/alerts/SystemWideAlert.java
@@ -8,17 +8,17 @@
package org.dspace.alerts;
import java.util.Date;
-import javax.persistence.Cacheable;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.SequenceGenerator;
-import javax.persistence.Table;
-import javax.persistence.Temporal;
-import javax.persistence.TemporalType;
+import jakarta.persistence.Cacheable;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.SequenceGenerator;
+import jakarta.persistence.Table;
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.dspace.core.ReloadableEntity;
diff --git a/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java b/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java
index 13a0e0af236a..79dc1bcf27a3 100644
--- a/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/alerts/dao/impl/SystemWideAlertDAOImpl.java
@@ -9,10 +9,10 @@
import java.sql.SQLException;
import java.util.List;
-import javax.persistence.criteria.CriteriaBuilder;
-import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.Root;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Root;
import org.dspace.alerts.SystemWideAlert;
import org.dspace.alerts.SystemWideAlert_;
import org.dspace.alerts.dao.SystemWideAlertDAO;
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java
index 50e1022dbe37..7bef232f0450 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkaccesscontrol/BulkAccessControl.java
@@ -464,7 +464,7 @@ private void setItemPolicies(Item item, BulkAccessControlInput accessControl)
.forEach(accessCondition -> createResourcePolicy(item, accessCondition,
itemAccessConditions.get(accessCondition.getName())));
- itemService.adjustItemPolicies(context, item, item.getOwningCollection());
+ itemService.adjustItemPolicies(context, item, item.getOwningCollection(), false);
}
/**
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
index cbc052b5573f..ecd6a24287d1 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
@@ -188,6 +188,15 @@ public DSpaceCSV(InputStream inputStream, Context c) throws Exception {
// Verify that the heading is valid in the metadata registry
String[] clean = element.split("\\[");
String[] parts = clean[0].split("\\.");
+ // Check language if present, if it's ANY then throw an exception
+ if (clean.length > 1 && clean[1].equals(Item.ANY + "]")) {
+ throw new MetadataImportInvalidHeadingException("Language ANY (*) was found in the heading " +
+ "of the metadata value to import, " +
+ "this should never be the case",
+ MetadataImportInvalidHeadingException.ENTRY,
+ columnCounter);
+
+ }
if (parts.length < 2) {
throw new MetadataImportInvalidHeadingException(element,
@@ -223,6 +232,15 @@ public DSpaceCSV(InputStream inputStream, Context c) throws Exception {
}
}
+ // Verify there isn’t already a header that is the same; if it already exists,
+ // throw MetadataImportInvalidHeadingException
+ String header = authorityPrefix + element;
+ if (headings.contains(header)) {
+ throw new MetadataImportInvalidHeadingException("Duplicate heading found: " + header,
+ MetadataImportInvalidHeadingException.ENTRY,
+ columnCounter);
+ }
+
// Store the heading
headings.add(authorityPrefix + element);
}
@@ -457,7 +475,7 @@ public final void addItem(Item i) throws Exception {
key = key + "." + metadataField.getQualifier();
}
- // Add the language if there is one (schema.element.qualifier[langauge])
+ // Add the language if there is one (schema.element.qualifier[language])
//if ((value.language != null) && (!"".equals(value.language)))
if (value.getLanguage() != null) {
key = key + "[" + value.getLanguage() + "]";
diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java
index 4161bbb4d817..988768864ed4 100644
--- a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java
+++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java
@@ -20,8 +20,8 @@
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
-import javax.annotation.Nullable;
+import jakarta.annotation.Nullable;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
@@ -253,7 +253,7 @@ public void internalRun() throws Exception {
displayChanges(changes, true);
}
- // Finsh off and tidy up
+ // Finish off and tidy up
c.restoreAuthSystemState();
c.complete();
} catch (Exception e) {
@@ -578,6 +578,10 @@ public List runImport(Context c, boolean change,
wfItem = workflowService.startWithoutNotify(c, wsItem);
}
} else {
+ // Add provenance info
+ String provenance = installItemService.getSubmittedByProvenanceMessage(c, wsItem.getItem());
+ itemService.addMetadata(c, item, MetadataSchemaEnum.DC.getName(),
+ "description", "provenance", "en", provenance);
// Install the item
installItemService.installItem(c, wsItem);
}
@@ -821,8 +825,10 @@ protected void compareAndUpdate(Context c, Item item, String[] fromCSV, boolean
addRelationships(c, item, element, values);
} else {
itemService.clearMetadata(c, item, schema, element, qualifier, language);
- itemService.addMetadata(c, item, schema, element, qualifier,
- language, values, authorities, confidences);
+ if (!values.isEmpty()) {
+ itemService.addMetadata(c, item, schema, element, qualifier,
+ language, values, authorities, confidences);
+ }
itemService.update(c, item);
}
}
@@ -1117,8 +1123,8 @@ protected void add(Context c, String[] fromCSV, String md, BulkEditChange change
.getAuthoritySeparator() + dcv.getConfidence();
}
- // Add it
- if ((value != null) && (!"".equals(value))) {
+ // Add it, if value is not blank
+ if (value != null && StringUtils.isNotBlank(value)) {
changes.registerAdd(dcv);
}
}
@@ -1363,7 +1369,7 @@ private int displayChanges(List changes, boolean changed) {
* is the field is defined as authority controlled
*/
private static boolean isAuthorityControlledField(String md) {
- String mdf = StringUtils.substringAfter(md, ":");
+ String mdf = md.contains(":") ? StringUtils.substringAfter(md, ":") : md;
mdf = StringUtils.substringBefore(mdf, "[");
return authorityControlled.contains(mdf);
}
@@ -1647,7 +1653,7 @@ private void validateExpressedRelations(Context c) throws MetadataImportExceptio
.getLabel();
} else {
// Target item may be archived; check there.
- // Add to errors if Realtionship.type cannot be derived
+ // Add to errors if Relationship.type cannot be derived
Item targetItem = null;
if (itemService.find(c, UUID.fromString(targetUUID)) != null) {
targetItem = itemService.find(c, UUID.fromString(targetUUID));
@@ -1692,7 +1698,7 @@ private void validateExpressedRelations(Context c) throws MetadataImportExceptio
validateTypesByTypeByTypeName(c, targetType, originType, typeName, originRow);
} else {
// Origin item may be archived; check there.
- // Add to errors if Realtionship.type cannot be derived.
+ // Add to errors if Relationship.type cannot be derived.
Item originItem = null;
if (itemService.find(c, UUID.fromString(targetUUID)) != null) {
DSpaceCSVLine dSpaceCSVLine = this.csv.getCSVLines()
diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java
index a884f9b07564..0ae2283d35be 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemexport/ItemExportServiceImpl.java
@@ -31,8 +31,8 @@
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
-import javax.mail.MessagingException;
+import jakarta.mail.MessagingException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.app.itemexport.service.ItemExportService;
@@ -490,7 +490,7 @@ public void exportAsZip(Context context, Iterator- items,
File wkDir = new File(workDir);
if (!wkDir.exists() && !wkDir.mkdirs()) {
- logError("Unable to create working direcory");
+ logError("Unable to create working directory");
}
File dnDir = new File(destDirName);
@@ -498,11 +498,18 @@ public void exportAsZip(Context context, Iterator
- items,
logError("Unable to create destination directory");
}
- // export the items using normal export method
- exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams);
+ try {
+ // export the items using normal export method (this exports items to our workDir)
+ exportItem(context, items, workDir, seqStart, migrate, excludeBitstreams);
- // now zip up the export directory created above
- zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName);
+ // now zip up the workDir directory created above
+ zip(workDir, destDirName + System.getProperty("file.separator") + zipFileName);
+ } finally {
+ // Cleanup workDir created above, if it still exists
+ if (wkDir.exists()) {
+ deleteDirectory(wkDir);
+ }
+ }
}
@Override
@@ -718,7 +725,7 @@ public void run() {
try {
emailErrorMessage(eperson, e1.getMessage());
} catch (Exception e) {
- // wont throw here
+ // won't throw here
}
throw new IllegalStateException(e1);
} finally {
diff --git a/dspace-api/src/main/java/org/dspace/app/itemexport/service/ItemExportService.java b/dspace-api/src/main/java/org/dspace/app/itemexport/service/ItemExportService.java
index 6ec1027709bb..c28ec70d6fe9 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemexport/service/ItemExportService.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemexport/service/ItemExportService.java
@@ -11,8 +11,8 @@
import java.util.Date;
import java.util.Iterator;
import java.util.List;
-import javax.mail.MessagingException;
+import jakarta.mail.MessagingException;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
import org.dspace.core.Context;
@@ -69,7 +69,7 @@ public void exportAsZip(Context context, Iterator
- items,
boolean excludeBitstreams) throws Exception;
/**
- * Convenience methot to create export a single Community, Collection, or
+ * Convenience method to create export a single Community, Collection, or
* Item
*
* @param dso - the dspace object to export
@@ -93,7 +93,7 @@ public void createDownloadableExport(List dsObjects,
Context context, boolean migrate) throws Exception;
/**
- * Convenience methot to create export a single Community, Collection, or
+ * Convenience method to create export a single Community, Collection, or
* Item
*
* @param dso - the dspace object to export
@@ -156,7 +156,7 @@ public String getExportDownloadDirectory(EPerson ePerson)
public String getExportWorkDirectory() throws Exception;
/**
- * Used to read the export archived. Inteded for download.
+ * Used to read the export archived. Intended for download.
*
* @param fileName the name of the file to download
* @param eperson the eperson requesting the download
@@ -233,7 +233,7 @@ public List getExportsAvailable(EPerson eperson)
/**
* Since the archive is created in a new thread we are unable to communicate
- * with calling method about success or failure. We accomplis this
+ * with calling method about success or failure. We accomplish this
* communication with email instead. Send a success email once the export
* archive is complete and ready for download
*
@@ -248,7 +248,7 @@ public void emailSuccessMessage(Context context, EPerson eperson,
/**
* Since the archive is created in a new thread we are unable to communicate
- * with calling method about success or failure. We accomplis this
+ * with calling method about success or failure. We accomplish this
* communication with email instead. Send an error email if the export
* archive fails
*
diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java
index 4148232cf3ba..01859c4f6b8a 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImportServiceImpl.java
@@ -46,7 +46,6 @@
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-import javax.mail.MessagingException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -56,6 +55,7 @@
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
+import jakarta.mail.MessagingException;
import org.apache.commons.collections4.ComparatorUtils;
import org.apache.commons.io.FileDeleteStrategy;
import org.apache.commons.io.FileUtils;
@@ -774,6 +774,10 @@ protected Item addItem(Context c, List mycollections, String path,
// put item in system
if (!isTest) {
try {
+ // Add provenance info
+ String provenance = installItemService.getSubmittedByProvenanceMessage(c, wi.getItem());
+ itemService.addMetadata(c, wi.getItem(), MetadataSchemaEnum.DC.getName(),
+ "description", "provenance", "en", provenance);
installItemService.installItem(c, wi, myhandle);
} catch (Exception e) {
workspaceItemService.deleteAll(c, wi);
@@ -1739,7 +1743,8 @@ protected void processOptions(Context c, Item myItem, List options)
} else {
logInfo("\tSetting special permissions for "
+ bitstreamName);
- setPermission(c, myGroup, actionID, bs);
+ String rpType = useWorkflow ? ResourcePolicy.TYPE_SUBMISSION : ResourcePolicy.TYPE_INHERITED;
+ setPermission(c, myGroup, rpType, actionID, bs);
}
}
@@ -1797,24 +1802,25 @@ protected void processOptions(Context c, Item myItem, List options)
*
* @param c DSpace Context
* @param g Dspace Group
+ * @param rpType resource policy type string
* @param actionID action identifier
* @param bs Bitstream
* @throws SQLException if database error
* @throws AuthorizeException if authorization error
* @see org.dspace.core.Constants
*/
- protected void setPermission(Context c, Group g, int actionID, Bitstream bs)
+ protected void setPermission(Context c, Group g, String rpType, int actionID, Bitstream bs)
throws SQLException, AuthorizeException {
if (!isTest) {
// remove the default policy
authorizeService.removeAllPolicies(c, bs);
// add the policy
- ResourcePolicy rp = resourcePolicyService.create(c);
+ ResourcePolicy rp = resourcePolicyService.create(c, null, g);
rp.setdSpaceObject(bs);
rp.setAction(actionID);
- rp.setGroup(g);
+ rp.setRpType(rpType);
resourcePolicyService.update(c, rp);
} else {
@@ -1953,58 +1959,57 @@ public String unzip(File zipfile, String destDir) throws IOException {
try {
while (entries.hasMoreElements()) {
entry = entries.nextElement();
+ String entryName = entry.getName();
+ File outFile = new File(zipDir + entryName);
+ // Verify that this file/directory will be extracted into our zipDir (and not somewhere else!)
+ if (!outFile.toPath().normalize().startsWith(zipDir)) {
+ throw new IOException("Bad zip entry: '" + entryName
+ + "' in file '" + zipfile.getAbsolutePath() + "'!"
+ + " Cannot process this file or directory.");
+ }
if (entry.isDirectory()) {
- if (!new File(zipDir + entry.getName()).mkdirs()) {
+ if (!outFile.mkdirs()) {
logError("Unable to create contents directory: " + zipDir + entry.getName());
}
} else {
- String entryName = entry.getName();
- File outFile = new File(zipDir + entryName);
- // Verify that this file will be extracted into our zipDir (and not somewhere else!)
- if (!outFile.toPath().normalize().startsWith(zipDir)) {
- throw new IOException("Bad zip entry: '" + entryName
- + "' in file '" + zipfile.getAbsolutePath() + "'!"
- + " Cannot process this file.");
- } else {
- logInfo("Extracting file: " + entryName);
+ logInfo("Extracting file: " + entryName);
- int index = entryName.lastIndexOf('/');
- if (index == -1) {
- // Was it created on Windows instead?
- index = entryName.lastIndexOf('\\');
+ int index = entryName.lastIndexOf('/');
+ if (index == -1) {
+ // Was it created on Windows instead?
+ index = entryName.lastIndexOf('\\');
+ }
+ if (index > 0) {
+ File dir = new File(zipDir + entryName.substring(0, index));
+ if (!dir.exists() && !dir.mkdirs()) {
+ logError("Unable to create directory: " + dir.getAbsolutePath());
}
- if (index > 0) {
- File dir = new File(zipDir + entryName.substring(0, index));
- if (!dir.exists() && !dir.mkdirs()) {
- logError("Unable to create directory: " + dir.getAbsolutePath());
- }
- //Entries could have too many directories, and we need to adjust the sourcedir
- // file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|...
- // SimpleArchiveFormat / item2 / contents|dublin_core|...
- // or
- // file2.zip (item1 / contents|dublin_core|...
- // item2 / contents|dublin_core|...
-
- //regex supports either windows or *nix file paths
- String[] entryChunks = entryName.split("/|\\\\");
- if (entryChunks.length > 2) {
- if (StringUtils.equals(sourceDirForZip, sourcedir)) {
- sourceDirForZip = sourcedir + "/" + entryChunks[0];
- }
+ //Entries could have too many directories, and we need to adjust the sourcedir
+ // file1.zip (SimpleArchiveFormat / item1 / contents|dublin_core|...
+ // SimpleArchiveFormat / item2 / contents|dublin_core|...
+ // or
+ // file2.zip (item1 / contents|dublin_core|...
+ // item2 / contents|dublin_core|...
+
+ //regex supports either windows or *nix file paths
+ String[] entryChunks = entryName.split("/|\\\\");
+ if (entryChunks.length > 2) {
+ if (StringUtils.equals(sourceDirForZip, sourcedir)) {
+ sourceDirForZip = sourcedir + "/" + entryChunks[0];
}
}
- byte[] buffer = new byte[1024];
- int len;
- InputStream in = zf.getInputStream(entry);
- BufferedOutputStream out = new BufferedOutputStream(
- new FileOutputStream(outFile));
- while ((len = in.read(buffer)) >= 0) {
- out.write(buffer, 0, len);
- }
- in.close();
- out.close();
}
+ byte[] buffer = new byte[1024];
+ int len;
+ InputStream in = zf.getInputStream(entry);
+ BufferedOutputStream out = new BufferedOutputStream(
+ new FileOutputStream(outFile));
+ while ((len = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, len);
+ }
+ in.close();
+ out.close();
}
}
} finally {
@@ -2205,7 +2210,7 @@ public void run() {
emailErrorMessage(eperson, exceptionString);
throw new Exception(e.getMessage());
} catch (Exception e2) {
- // wont throw here
+ // won't throw here
}
} finally {
// Make sure the database connection gets closed in all conditions.
@@ -2229,7 +2234,7 @@ public void emailSuccessMessage(Context context, EPerson eperson,
String fileName) throws MessagingException {
try {
Locale supportedLocale = I18nUtil.getEPersonLocale(eperson);
- Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "bte_batch_import_success"));
+ Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "batch_import_success"));
email.addRecipient(eperson.getEmail());
email.addArgument(fileName);
@@ -2245,7 +2250,7 @@ public void emailErrorMessage(EPerson eperson, String error)
logError("An error occurred during item import, the user will be notified. " + error);
try {
Locale supportedLocale = I18nUtil.getEPersonLocale(eperson);
- Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "bte_batch_import_error"));
+ Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "batch_import_error"));
email.addRecipient(eperson.getEmail());
email.addArgument(error);
email.addArgument(configurationService.getProperty("dspace.ui.url") + "/feedback");
diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java b/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java
index e99ece31b9bb..738991a839cd 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemimport/service/ItemImportService.java
@@ -10,8 +10,8 @@
import java.io.File;
import java.io.IOException;
import java.util.List;
-import javax.mail.MessagingException;
+import jakarta.mail.MessagingException;
import org.dspace.app.itemimport.BatchUpload;
import org.dspace.content.Collection;
import org.dspace.core.Context;
@@ -121,7 +121,7 @@ public void emailSuccessMessage(Context context, EPerson eperson,
/**
* If a batch import is done in a new thread we are unable to communicate
- * with calling method about success or failure. We accomplis this
+ * with calling method about success or failure. We accomplish this
* communication with email instead. Send an error email if the batch
* import fails
*
diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java
index cb5dcfb75dc0..65157f5207e7 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/DeleteBitstreamsAction.java
@@ -70,16 +70,19 @@ public void execute(Context context, ItemArchive itarch, boolean isTest,
}
}
- if (alterProvenance) {
+ if (alterProvenance && !bundles.isEmpty()) {
DtoMetadata dtom = DtoMetadata.create("dc.description.provenance", "en", "");
String append = "Bitstream " + bs.getName() + " deleted on " + DCDate
.getCurrent() + "; ";
- Item item = bundles.iterator().next().getItems().iterator().next();
- ItemUpdate.pr("Append provenance with: " + append);
+ List
- items = bundles.iterator().next().getItems();
+ if (!items.isEmpty()) {
+ Item item = items.iterator().next();
+ ItemUpdate.pr("Append provenance with: " + append);
- if (!isTest) {
- MetadataUtilities.appendMetadata(context, item, dtom, false, append);
+ if (!isTest) {
+ MetadataUtilities.appendMetadata(context, item, dtom, false, append);
+ }
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java
index 26de45caf77e..a3dead0574f9 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/ItemArchive.java
@@ -217,7 +217,7 @@ private Item itemFromHandleInput(Context context)
throws SQLException, Exception {
DtoMetadata dtom = getMetadataField("dc.identifier.uri");
if (dtom == null) {
- throw new Exception("No dc.identier.uri field found for handle");
+ throw new Exception("No dc.identifier.uri field found for handle");
}
this.addUndoMetadataField(dtom); //seed the undo list with the uri
diff --git a/dspace-api/src/main/java/org/dspace/app/itemupdate/ThumbnailBitstreamFilter.java b/dspace-api/src/main/java/org/dspace/app/itemupdate/ThumbnailBitstreamFilter.java
index 2a8f9ac20028..b6000778877c 100644
--- a/dspace-api/src/main/java/org/dspace/app/itemupdate/ThumbnailBitstreamFilter.java
+++ b/dspace-api/src/main/java/org/dspace/app/itemupdate/ThumbnailBitstreamFilter.java
@@ -10,7 +10,7 @@
import java.util.Properties;
/**
- * Bitstream filter targetting the THUMBNAIL bundle
+ * Bitstream filter targeting the THUMBNAIL bundle
*/
public class ThumbnailBitstreamFilter extends BitstreamFilterByBundleName {
diff --git a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java
index fcb2098bd066..89a416bfa883 100644
--- a/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java
+++ b/dspace-api/src/main/java/org/dspace/app/launcher/ScriptLauncher.java
@@ -21,6 +21,7 @@
import org.apache.logging.log4j.Logger;
import org.dspace.core.Context;
import org.dspace.scripts.DSpaceRunnable;
+import org.dspace.scripts.DSpaceRunnable.StepResult;
import org.dspace.scripts.configuration.ScriptConfiguration;
import org.dspace.scripts.factory.ScriptServiceFactory;
import org.dspace.scripts.handler.DSpaceRunnableHandler;
@@ -145,8 +146,13 @@ public static int handleScript(String[] args, Document commandConfigs,
private static int executeScript(String[] args, DSpaceRunnableHandler dSpaceRunnableHandler,
DSpaceRunnable script) {
try {
- script.initialize(args, dSpaceRunnableHandler, null);
- script.run();
+ StepResult result = script.initialize(args, dSpaceRunnableHandler, null);
+ // check the StepResult, only run the script if the result is Continue;
+ // otherwise - for example the script is started with the help as argument, nothing is to do
+ if (StepResult.Continue.equals(result)) {
+ // runs the script, the normal initialization is successful
+ script.run();
+ }
return 0;
} catch (ParseException e) {
script.printHelp();
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java b/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java
new file mode 100644
index 000000000000..44dd5389d3de
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/ItemFilter.java
@@ -0,0 +1,30 @@
+/**
+ * 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.ldn;
+
+/**
+ * model class for the item filters configured into item-filters.xml
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class ItemFilter {
+
+ private String id;
+
+ public ItemFilter(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java
new file mode 100644
index 000000000000..210aaa6c9c97
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageConsumer.java
@@ -0,0 +1,243 @@
+/**
+ * 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.ldn;
+
+import static java.lang.String.format;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.dspace.app.ldn.factory.NotifyServiceFactory;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
+import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
+import org.dspace.content.Bitstream;
+import org.dspace.content.BitstreamFormat;
+import org.dspace.content.Bundle;
+import org.dspace.content.Item;
+import org.dspace.content.MetadataValue;
+import org.dspace.content.factory.ContentServiceFactory;
+import org.dspace.content.logic.LogicalStatement;
+import org.dspace.content.service.BitstreamService;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.core.I18nUtil;
+import org.dspace.core.LDN;
+import org.dspace.event.Consumer;
+import org.dspace.event.Event;
+import org.dspace.services.ConfigurationService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+import org.dspace.utils.DSpace;
+import org.dspace.web.ContextUtil;
+
+/**
+ * class for creating a new LDN Messages of installed item
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class LDNMessageConsumer implements Consumer {
+
+ private NotifyPatternToTriggerService notifyPatternToTriggerService;
+ private NotifyServiceInboundPatternService inboundPatternService;
+ private LDNMessageService ldnMessageService;
+ private ConfigurationService configurationService;
+ private ItemService itemService;
+ private BitstreamService bitstreamService;
+
+ @Override
+ public void initialize() throws Exception {
+ notifyPatternToTriggerService = NotifyServiceFactory.getInstance().getNotifyPatternToTriggerService();
+ ldnMessageService = NotifyServiceFactory.getInstance().getLDNMessageService();
+ configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
+ itemService = ContentServiceFactory.getInstance().getItemService();
+ bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
+ inboundPatternService = NotifyServiceFactory.getInstance().getNotifyServiceInboundPatternService();
+ }
+
+ @Override
+ public void consume(Context context, Event event) throws Exception {
+
+ if (event.getSubjectType() != Constants.ITEM ||
+ event.getEventType() != Event.INSTALL) {
+ return;
+ }
+
+ Item item = (Item) event.getSubject(context);
+ createManualLDNMessages(context, item);
+ createAutomaticLDNMessages(context, item);
+ }
+
+ private void createManualLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException {
+ List patternsToTrigger =
+ notifyPatternToTriggerService.findByItem(context, item);
+
+ for (NotifyPatternToTrigger patternToTrigger : patternsToTrigger) {
+ createLDNMessage(context,patternToTrigger.getItem(),
+ patternToTrigger.getNotifyService(), patternToTrigger.getPattern());
+ }
+ }
+
+ private void createAutomaticLDNMessages(Context context, Item item) throws SQLException, JsonProcessingException {
+
+ List inboundPatterns = inboundPatternService.findAutomaticPatterns(context);
+
+ for (NotifyServiceInboundPattern inboundPattern : inboundPatterns) {
+ if (StringUtils.isEmpty(inboundPattern.getConstraint()) ||
+ evaluateFilter(context, item, inboundPattern.getConstraint())) {
+ createLDNMessage(context, item, inboundPattern.getNotifyService(), inboundPattern.getPattern());
+ }
+ }
+ }
+
+ private boolean evaluateFilter(Context context, Item item, String constraint) {
+ LogicalStatement filter =
+ new DSpace().getServiceManager().getServiceByName(constraint, LogicalStatement.class);
+
+ return filter != null && filter.getResult(context, item);
+ }
+
+ private void createLDNMessage(Context context, Item item, NotifyServiceEntity service, String pattern)
+ throws SQLException, JsonMappingException, JsonProcessingException {
+
+ LDN ldn = getLDNMessage(pattern);
+ LDNMessageEntity ldnMessage =
+ ldnMessageService.create(context, format("urn:uuid:%s", UUID.randomUUID()));
+
+ ldnMessage.setObject(item);
+ ldnMessage.setTarget(service);
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
+ ldnMessage.setQueueTimeout(new Date());
+
+ appendGeneratedMessage(ldn, ldnMessage, pattern);
+
+ ObjectMapper mapper = new ObjectMapper();
+ Notification notification = mapper.readValue(ldnMessage.getMessage(), Notification.class);
+ ldnMessage.setType(StringUtils.joinWith(",", notification.getType()));
+
+ ArrayList notificationTypeArrayList = new ArrayList(notification.getType());
+ // sorting the list
+ Collections.sort(notificationTypeArrayList);
+ ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
+ ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1));
+
+ ldnMessageService.update(context, ldnMessage);
+ }
+
+ private LDN getLDNMessage(String pattern) {
+ try {
+ return LDN.getLDNMessage(I18nUtil.getLDNFilename(Locale.getDefault(), pattern));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void appendGeneratedMessage(LDN ldn, LDNMessageEntity ldnMessage, String pattern) {
+ Item item = (Item) ldnMessage.getObject();
+ ldn.addArgument(getUiUrl());
+ ldn.addArgument(configurationService.getProperty("ldn.notify.inbox"));
+ ldn.addArgument(configurationService.getProperty("dspace.name"));
+ ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getUrl(), ""));
+ ldn.addArgument(Objects.requireNonNullElse(ldnMessage.getTarget().getLdnUrl(), ""));
+ ldn.addArgument(getUiUrl() + "/handle/" + ldnMessage.getObject().getHandle());
+ ldn.addArgument(getIdentifierUri(item));
+ ldn.addArgument(generateBitstreamDownloadUrl(item));
+ ldn.addArgument(getBitstreamMimeType(findPrimaryBitstream(item)));
+ ldn.addArgument(ldnMessage.getID());
+ ldn.addArgument(getRelationUri(item));
+ ldn.addArgument("http://purl.org/vocab/frbr/core#supplement");
+ ldn.addArgument(format("urn:uuid:%s", UUID.randomUUID()));
+
+ ldnMessage.setMessage(ldn.generateLDNMessage());
+ }
+
+ private String getUiUrl() {
+ return configurationService.getProperty("dspace.ui.url");
+ }
+
+ private String getIdentifierUri(Item item) {
+ return itemService.getMetadataByMetadataString(item, "dc.identifier.uri")
+ .stream()
+ .findFirst()
+ .map(MetadataValue::getValue)
+ .orElse("");
+ }
+
+ private String getRelationUri(Item item) {
+ String relationMetadata = configurationService.getProperty("ldn.notify.relation.metadata", "dc.relation");
+ return itemService.getMetadataByMetadataString(item, relationMetadata)
+ .stream()
+ .findFirst()
+ .map(MetadataValue::getValue)
+ .orElse("");
+ }
+
+ private String generateBitstreamDownloadUrl(Item item) {
+ String uiUrl = getUiUrl();
+ return findPrimaryBitstream(item)
+ .map(bs -> uiUrl + "/bitstreams/" + bs.getID() + "/download")
+ .orElse("");
+ }
+
+ private Optional findPrimaryBitstream(Item item) {
+ List bundles = item.getBundles(Constants.CONTENT_BUNDLE_NAME);
+ return bundles.stream()
+ .findFirst()
+ .map(Bundle::getPrimaryBitstream)
+ .or(() -> bundles.stream()
+ .findFirst()
+ .flatMap(bundle -> CollectionUtils.isNotEmpty(bundle.getBitstreams())
+ ? Optional.of(bundle.getBitstreams().get(0))
+ : Optional.empty()));
+ }
+
+ private String getBitstreamMimeType(Optional bitstream) {
+ return bitstream.map(bs -> {
+ try {
+ Context context = ContextUtil.obtainCurrentRequestContext();
+ BitstreamFormat bitstreamFormat = bs.getFormat(context);
+ if (bitstreamFormat.getShortDescription().equals("Unknown")) {
+ return getUserFormatMimeType(bs);
+ }
+ return bitstreamFormat.getMIMEType();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }).orElse("");
+ }
+
+ private String getUserFormatMimeType(Bitstream bitstream) {
+ return bitstreamService.getMetadataFirstValue(bitstream,
+ "dc", "format", "mimetype", Item.ANY);
+ }
+
+ @Override
+ public void end(Context ctx) throws Exception {
+
+ }
+
+ @Override
+ public void finish(Context ctx) throws Exception {
+
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java
new file mode 100644
index 000000000000..27257455e0ce
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageEntity.java
@@ -0,0 +1,319 @@
+/**
+ * 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.ldn;
+
+import java.lang.reflect.Field;
+import java.util.Date;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
+import org.dspace.content.DSpaceObject;
+import org.dspace.core.ReloadableEntity;
+
+/**
+ * Class representing ldnMessages stored in the DSpace system and, when locally resolvable,
+ * some information are stored as dedicated attributes.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+@Entity
+@Table(name = "ldn_message")
+public class LDNMessageEntity implements ReloadableEntity {
+
+ /**
+ * LDN messages interact with a fictitious queue. Scheduled tasks manage the queue.
+ */
+
+ /*
+ * Notification Type constants
+ */
+ public static final String TYPE_INCOMING = "Incoming";
+ public static final String TYPE_OUTGOING = "Outgoing";
+
+ /**
+ * Message must not be processed.
+ */
+ public static final Integer QUEUE_STATUS_UNTRUSTED_IP = 0;
+
+ /**
+ * Message queued, it has to be elaborated.
+ */
+ public static final Integer QUEUE_STATUS_QUEUED = 1;
+
+ /**
+ * Message has been taken from the queue and it's elaboration is in progress.
+ */
+ public static final Integer QUEUE_STATUS_PROCESSING = 2;
+
+ /**
+ * Message has been correctly elaborated.
+ */
+ public static final Integer QUEUE_STATUS_PROCESSED = 3;
+
+ /**
+ * Message has not been correctly elaborated - despite more than "ldn.processor.max.attempts" retryies
+ */
+ public static final Integer QUEUE_STATUS_FAILED = 4;
+
+ /**
+ * Message must not be processed
+ */
+ public static final Integer QUEUE_STATUS_UNTRUSTED = 5;
+
+ /**
+ * Message is not processed since action is not mapped
+ */
+ public static final Integer QUEUE_STATUS_UNMAPPED_ACTION = 6;
+
+ /**
+ * Message queued for retry, it has to be elaborated.
+ */
+ public static final Integer QUEUE_STATUS_QUEUED_FOR_RETRY = 7;
+
+ @Id
+ private String id;
+
+ @ManyToOne
+ @JoinColumn(name = "object", referencedColumnName = "uuid")
+ private DSpaceObject object;
+
+ @Column(name = "message", columnDefinition = "text")
+ private String message;
+
+ @Column(name = "type")
+ private String type;
+
+ @Column(name = "queue_status")
+ private Integer queueStatus;
+
+ @Column(name = "queue_attempts")
+ private Integer queueAttempts = 0;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "queue_last_start_time")
+ private Date queueLastStartTime = null;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "queue_timeout")
+ private Date queueTimeout = null;
+
+ @ManyToOne
+ @JoinColumn(name = "origin", referencedColumnName = "id")
+ private NotifyServiceEntity origin;
+
+ @ManyToOne
+ @JoinColumn(name = "target", referencedColumnName = "id")
+ private NotifyServiceEntity target;
+
+ @ManyToOne
+ @JoinColumn(name = "inReplyTo", referencedColumnName = "id")
+ private LDNMessageEntity inReplyTo;
+
+ @ManyToOne
+ @JoinColumn(name = "context", referencedColumnName = "uuid")
+ private DSpaceObject context;
+
+ @Column(name = "activity_stream_type")
+ private String activityStreamType;
+
+ @Column(name = "coar_notify_type")
+ private String coarNotifyType;
+
+ @Column(name = "source_ip")
+ private String sourceIp;
+
+ protected LDNMessageEntity() {
+
+ }
+
+ public LDNMessageEntity(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getID() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ *
+ * @return the DSpace item related to this message
+ */
+ public DSpaceObject getObject() {
+ return object;
+ }
+
+ public void setObject(DSpaceObject object) {
+ this.object = object;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getActivityStreamType() {
+ return activityStreamType;
+ }
+
+ public void setActivityStreamType(String activityStreamType) {
+ this.activityStreamType = activityStreamType;
+ }
+
+ public String getCoarNotifyType() {
+ return coarNotifyType;
+ }
+
+ public void setCoarNotifyType(String coarNotifyType) {
+ this.coarNotifyType = coarNotifyType;
+ }
+
+ /**
+ *
+ * @return The originator of the activity, typically the service responsible for sending the notification
+ */
+ public NotifyServiceEntity getOrigin() {
+ return origin;
+ }
+
+ public void setOrigin(NotifyServiceEntity origin) {
+ this.origin = origin;
+ }
+
+ /**
+ *
+ * @return The intended destination of the activity, typically the service which consumes the notification
+ */
+ public NotifyServiceEntity getTarget() {
+ return target;
+ }
+
+ public void setTarget(NotifyServiceEntity target) {
+ this.target = target;
+ }
+
+ /**
+ *
+ * @return This property is used when the notification is a direct response to a previous notification;
+ * contains an {@link org.dspace.app.ldn.LDNMessageEntity#inReplyTo id}
+ */
+ public LDNMessageEntity getInReplyTo() {
+ return inReplyTo;
+ }
+
+ public void setInReplyTo(LDNMessageEntity inReplyTo) {
+ this.inReplyTo = inReplyTo;
+ }
+
+ /**
+ *
+ * @return This identifies another resource which is relevant to understanding the notification
+ */
+ public DSpaceObject getContext() {
+ return context;
+ }
+
+ public void setContext(DSpaceObject context) {
+ this.context = context;
+ }
+
+ public Integer getQueueStatus() {
+ return queueStatus;
+ }
+
+ public void setQueueStatus(Integer queueStatus) {
+ this.queueStatus = queueStatus;
+ }
+
+ public Integer getQueueAttempts() {
+ return queueAttempts;
+ }
+
+ public void setQueueAttempts(Integer queueAttempts) {
+ this.queueAttempts = queueAttempts;
+ }
+
+ public Date getQueueLastStartTime() {
+ return queueLastStartTime;
+ }
+
+ public void setQueueLastStartTime(Date queueLastStartTime) {
+ this.queueLastStartTime = queueLastStartTime;
+ }
+
+ public Date getQueueTimeout() {
+ return queueTimeout;
+ }
+
+ public void setQueueTimeout(Date queueTimeout) {
+ this.queueTimeout = queueTimeout;
+ }
+
+ public String getSourceIp() {
+ return sourceIp;
+ }
+
+ public void setSourceIp(String sourceIp) {
+ this.sourceIp = sourceIp;
+ }
+
+ @Override
+ public String toString() {
+ return "LDNMessage id:" + this.getID() + " typed:" + this.getType();
+ }
+
+ public static String getNotificationType(LDNMessageEntity ldnMessage) {
+ if (ldnMessage.getInReplyTo() != null || ldnMessage.getOrigin() != null) {
+ return TYPE_INCOMING;
+ }
+ return TYPE_OUTGOING;
+ }
+
+ public static String getServiceNameForNotifyServ(NotifyServiceEntity serviceEntity) {
+ if (serviceEntity != null) {
+ return serviceEntity.getName();
+ }
+ return "self";
+ }
+
+ public static String getQueueStatus(LDNMessageEntity ldnMessage) {
+ Class cl = LDNMessageEntity.class;
+ try {
+ for (Field f : cl.getDeclaredFields()) {
+ String fieldName = f.getName();
+ if (fieldName.startsWith("QUEUE_") && (f.get(null) == ldnMessage.getQueueStatus())) {
+ return fieldName;
+ }
+ }
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+}
diff --git a/dspace-services/src/main/java/org/dspace/servicemanager/servlet/package-info.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java
similarity index 52%
rename from dspace-services/src/main/java/org/dspace/servicemanager/servlet/package-info.java
rename to dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java
index 652c887e0429..ad3dd36e69c5 100644
--- a/dspace-services/src/main/java/org/dspace/servicemanager/servlet/package-info.java
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMessageQueueStatus.java
@@ -5,10 +5,12 @@
*
* http://www.dspace.org/license/
*/
+package org.dspace.app.ldn;
-/**
- * Support for using DSpace Services in a servlet context. This is how the
- * kernel and services get started by the servlet container.
- */
+public enum LDNMessageQueueStatus {
-package org.dspace.servicemanager.servlet;
+ /**
+ * Resulting processing status of an LDN Message (aka queue management)
+ */
+ QUEUED, PROCESSING, PROCESSED, FAILED;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java
new file mode 100644
index 000000000000..67a87c144c83
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNMetadataFields.java
@@ -0,0 +1,38 @@
+/**
+ * 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.ldn;
+
+/**
+ * Constants for LDN metadata fields
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public final class LDNMetadataFields {
+
+ // schema and element are the same for each metadata of LDN coar-notify
+ public static final String SCHEMA = "coar";
+ public static final String ELEMENT = "notify";
+
+ // qualifiers
+ public static final String INITIALIZE = "initialize";
+ public static final String REQUEST_REVIEW = "requestreview";
+ public static final String REQUEST_ENDORSEMENT = "requestendorsement";
+ public static final String EXAMINATION = "examination";
+ public static final String REFUSED = "refused";
+ public static final String REVIEW = "review";
+ public static final String ENDORSMENT = "endorsement";
+ public static final String RELEASE = "release";
+
+ /**
+ *
+ */
+ private LDNMetadataFields() {
+
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java
new file mode 100644
index 000000000000..57a7cdfb07bf
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueExtractor.java
@@ -0,0 +1,54 @@
+/**
+ * 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.ldn;
+
+import java.sql.SQLException;
+
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.factory.LDNMessageServiceFactory;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.core.Context;
+
+/**
+ * LDN Message manager: scheduled task invoking extractAndProcessMessageFromQueue() of {@link LDNMessageService}
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it)
+ */
+public class LDNQueueExtractor {
+
+ private static final LDNMessageService ldnMessageService = LDNMessageServiceFactory.getInstance()
+ .getLDNMessageService();
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNQueueExtractor.class);
+
+ /**
+ * Default constructor
+ */
+ private LDNQueueExtractor() {
+ }
+
+ /**
+ * invokes
+ * @see org.dspace.app.ldn.service.impl.LDNMessageServiceImpl#extractAndProcessMessageFromQueue(Context)
+ * to process the oldest ldn messages from the queue. An LdnMessage is processed when is routed to a
+ * @see org.dspace.app.ldn.processor.LDNProcessor
+ * Also a +1 is added to the ldnMessage entity
+ * @see org.dspace.app.ldn.LDNMessageEntity#getQueueAttempts()
+ * @return the number of processed ldnMessages.
+ * @throws SQLException
+ */
+ public static int extractMessageFromQueue() throws SQLException {
+ Context context = new Context(Context.Mode.READ_WRITE);
+ int processed_messages = ldnMessageService.extractAndProcessMessageFromQueue(context);
+ if (processed_messages > 0) {
+ log.info("Processed Messages x" + processed_messages);
+ }
+ context.complete();
+ return processed_messages;
+ }
+
+};
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java
new file mode 100644
index 000000000000..36a927d672bc
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNQueueTimeoutChecker.java
@@ -0,0 +1,53 @@
+/**
+ * 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.ldn;
+
+import java.sql.SQLException;
+
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.factory.LDNMessageServiceFactory;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.core.Context;
+
+/**
+ * LDN Message manager: scheduled task invoking checkQueueMessageTimeout() of {@link LDNMessageService}
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it)
+ */
+public class LDNQueueTimeoutChecker {
+
+ private static final LDNMessageService ldnMessageService = LDNMessageServiceFactory.getInstance()
+ .getLDNMessageService();
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNQueueTimeoutChecker.class);
+
+ /**
+ * Default constructor
+ */
+ private LDNQueueTimeoutChecker() {
+ }
+
+ /**
+ * invokes
+ * @see org.dspace.app.ldn.service.impl.LDNMessageServiceImpl#checkQueueMessageTimeout(Context)
+ * to refresh the queue status of timed-out and in progressing status ldn messages:
+ * according to their attempts put them back in queue or set their status as failed if maxAttempts
+ * reached.
+ * @return the number of managed ldnMessages.
+ * @throws SQLException
+ */
+ public static int checkQueueMessageTimeout() throws SQLException {
+ Context context = new Context(Context.Mode.READ_WRITE);
+ int fixed_messages = 0;
+ fixed_messages = ldnMessageService.checkQueueMessageTimeout(context);
+ if (fixed_messages > 0) {
+ log.info("Managed Messages x" + fixed_messages);
+ }
+ context.complete();
+ return fixed_messages;
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java b/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java
new file mode 100644
index 000000000000..14957aa503a3
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/LDNRouter.java
@@ -0,0 +1,91 @@
+/**
+ * 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.ldn;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.processor.LDNProcessor;
+
+/**
+ * Linked Data Notification router.
+ */
+public class LDNRouter {
+
+ private Map, LDNProcessor> incomingProcessors = new HashMap<>();
+ private Map, LDNProcessor> outcomingProcessors = new HashMap<>();
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNRouter.class);
+
+ /**
+ * Route notification to processor
+ *
+ * @return LDNProcessor processor to process notification, can be null
+ */
+ public LDNProcessor route(LDNMessageEntity ldnMessage) {
+ if (ldnMessage == null) {
+ log.warn("A null LDNMessage was received and could not be routed.");
+ return null;
+ }
+ if (StringUtils.isEmpty(ldnMessage.getType())) {
+ log.warn("LDNMessage " + ldnMessage + " was received. It has no type, so it couldn't be routed.");
+ return null;
+ }
+ Set ldnMessageTypeSet = new HashSet();
+ ldnMessageTypeSet.add(ldnMessage.getActivityStreamType());
+ ldnMessageTypeSet.add(ldnMessage.getCoarNotifyType());
+
+ LDNProcessor processor = null;
+ if (ldnMessage.getTarget() == null) {
+ processor = incomingProcessors.get(ldnMessageTypeSet);
+ } else if (ldnMessage.getOrigin() == null) {
+ processor = outcomingProcessors.get(ldnMessageTypeSet);
+ }
+
+ return processor;
+ }
+
+ /**
+ * Get all incoming routes.
+ *
+ * @return Map, LDNProcessor>
+ */
+ public Map, LDNProcessor> getIncomingProcessors() {
+ return incomingProcessors;
+ }
+
+ /**
+ * Set all incoming routes.
+ *
+ * @param incomingProcessors
+ */
+ public void setIncomingProcessors(Map, LDNProcessor> incomingProcessors) {
+ this.incomingProcessors = incomingProcessors;
+ }
+
+ /**
+ * Get all outcoming routes.
+ *
+ * @return Map, LDNProcessor>
+ */
+ public Map, LDNProcessor> getOutcomingProcessors() {
+ return outcomingProcessors;
+ }
+
+ /**
+ * Set all outcoming routes.
+ *
+ * @param outcomingProcessors
+ */
+ public void setOutcomingProcessors(Map, LDNProcessor> outcomingProcessors) {
+ this.outcomingProcessors = outcomingProcessors;
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java
new file mode 100644
index 000000000000..da23471a1c66
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyPatternToTrigger.java
@@ -0,0 +1,82 @@
+/**
+ * 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.ldn;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.SequenceGenerator;
+import jakarta.persistence.Table;
+import org.dspace.content.Item;
+import org.dspace.core.ReloadableEntity;
+
+/**
+ * Database object representing notify patterns to be triggered
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+@Entity
+@Table(name = "notifypatterns_to_trigger")
+public class NotifyPatternToTrigger implements ReloadableEntity {
+
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifypatterns_to_trigger_id_seq")
+ @SequenceGenerator(name = "notifypatterns_to_trigger_id_seq",
+ sequenceName = "notifypatterns_to_trigger_id_seq",
+ allocationSize = 1)
+ private Integer id;
+
+ @ManyToOne
+ @JoinColumn(name = "item_id", referencedColumnName = "uuid")
+ private Item item;
+
+ @ManyToOne
+ @JoinColumn(name = "service_id", referencedColumnName = "id")
+ private NotifyServiceEntity notifyService;
+
+ @Column(name = "pattern")
+ private String pattern;
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Item getItem() {
+ return item;
+ }
+
+ public void setItem(Item item) {
+ this.item = item;
+ }
+
+ public NotifyServiceEntity getNotifyService() {
+ return notifyService;
+ }
+
+ public void setNotifyService(NotifyServiceEntity notifyService) {
+ this.notifyService = notifyService;
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+
+ public void setPattern(String pattern) {
+ this.pattern = pattern;
+ }
+
+ @Override
+ public Integer getID() {
+ return id;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java
new file mode 100644
index 000000000000..c939256b52ba
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceEntity.java
@@ -0,0 +1,156 @@
+/**
+ * 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.ldn;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.SequenceGenerator;
+import jakarta.persistence.Table;
+import org.dspace.core.ReloadableEntity;
+
+/**
+ * Database object representing notify services
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+@Entity
+@Table(name = "notifyservice")
+public class NotifyServiceEntity implements ReloadableEntity {
+
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifyservice_id_seq")
+ @SequenceGenerator(name = "notifyservice_id_seq", sequenceName = "notifyservice_id_seq",
+ allocationSize = 1)
+ private Integer id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "description", columnDefinition = "text")
+ private String description;
+
+ @Column(name = "url")
+ private String url;
+
+ @Column(name = "ldn_url")
+ private String ldnUrl;
+
+ @OneToMany(mappedBy = "notifyService")
+ private List inboundPatterns;
+
+ @Column(name = "enabled")
+ private boolean enabled = false;
+
+ @Column(name = "score")
+ private BigDecimal score;
+
+ @Column(name = "lower_ip")
+ private String lowerIp;
+
+ @Column(name = "upper_ip")
+ private String upperIp;
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * @return URL of an informative website
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ /**
+ * @return URL of the LDN InBox
+ */
+ public String getLdnUrl() {
+ return ldnUrl;
+ }
+
+ public void setLdnUrl(String ldnUrl) {
+ this.ldnUrl = ldnUrl;
+ }
+
+ /**
+ * @return The list of the inbound patterns configuration supported by the service
+ */
+ public List getInboundPatterns() {
+ return inboundPatterns;
+ }
+
+ public void setInboundPatterns(List inboundPatterns) {
+ this.inboundPatterns = inboundPatterns;
+ }
+
+ @Override
+ public Integer getID() {
+ return id;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public BigDecimal getScore() {
+ return score;
+ }
+
+ public void setScore(BigDecimal score) {
+ this.score = score;
+ }
+
+ public String getLowerIp() {
+ return lowerIp;
+ }
+
+ public void setLowerIp(String lowerIp) {
+ this.lowerIp = lowerIp;
+ }
+
+ public String getUpperIp() {
+ return upperIp;
+ }
+
+ public void setUpperIp(String upperIp) {
+ this.upperIp = upperIp;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java
new file mode 100644
index 000000000000..329d6cb11cec
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/NotifyServiceInboundPattern.java
@@ -0,0 +1,103 @@
+/**
+ * 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.ldn;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.SequenceGenerator;
+import jakarta.persistence.Table;
+import org.dspace.core.ReloadableEntity;
+
+/**
+ * Database object representing notify service inbound patterns. Every {@link org.dspace.app.ldn.NotifyServiceEntity}
+ * may have inbounds and outbounds. Inbounds are to be sent to the external service.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+@Entity
+@Table(name = "notifyservice_inbound_pattern")
+public class NotifyServiceInboundPattern implements ReloadableEntity {
+
+ @Id
+ @Column(name = "id")
+ @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notifyservice_inbound_pattern_id_seq")
+ @SequenceGenerator(name = "notifyservice_inbound_pattern_id_seq",
+ sequenceName = "notifyservice_inbound_pattern_id_seq",
+ allocationSize = 1)
+ private Integer id;
+
+ @ManyToOne
+ @JoinColumn(name = "service_id", referencedColumnName = "id")
+ private NotifyServiceEntity notifyService;
+
+ @Column(name = "pattern")
+ private String pattern;
+
+ @Column(name = "constraint_name")
+ private String constraint;
+
+ @Column(name = "automatic")
+ private boolean automatic;
+
+ @Override
+ public Integer getID() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public NotifyServiceEntity getNotifyService() {
+ return notifyService;
+ }
+
+ public void setNotifyService(NotifyServiceEntity notifyService) {
+ this.notifyService = notifyService;
+ }
+
+ /**
+ * @see coar documentation
+ * @return pattern of the inbound notification
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ public void setPattern(String pattern) {
+ this.pattern = pattern;
+ }
+
+ /**
+ * @return the condition checked for automatic evaluation
+ */
+ public String getConstraint() {
+ return constraint;
+ }
+
+ public void setConstraint(String constraint) {
+ this.constraint = constraint;
+ }
+
+ /**
+ * when true - the notification is automatically when constraints are respected.
+ * @return the automatic flag
+ */
+ public boolean isAutomatic() {
+ return automatic;
+ }
+
+ public void setAutomatic(boolean automatic) {
+ this.automatic = automatic;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java
new file mode 100644
index 000000000000..b0c895de9958
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNAction.java
@@ -0,0 +1,31 @@
+/**
+ * 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.ldn.action;
+
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * An action that is run after a notification has been processed.
+ */
+public interface LDNAction {
+
+ /**
+ * Execute action for provided notification and item corresponding to the
+ * notification context.
+ *
+ *@param context the context
+ * @param notification the processed notification to perform action against
+ * @param item the item corresponding to the notification context
+ * @return ActionStatus the resulting status of the action
+ * @throws Exception general exception that can be thrown while executing action
+ */
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception;
+
+}
\ No newline at end of file
diff --git a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterList.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java
similarity index 60%
rename from dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterList.java
rename to dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java
index f6590e36f8e2..86f56ed9baab 100644
--- a/dspace-rest/src/main/java/org/dspace/rest/filter/ItemFilterList.java
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNActionStatus.java
@@ -5,8 +5,11 @@
*
* http://www.dspace.org/license/
*/
-package org.dspace.rest.filter;
+package org.dspace.app.ldn.action;
-public interface ItemFilterList {
- public ItemFilterTest[] getFilters();
-}
+/**
+ * Resulting status of an execution of an action.
+ */
+public enum LDNActionStatus {
+ CONTINUE, ABORT;
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java
new file mode 100644
index 000000000000..5ce3804bcee8
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNCorrectionAction.java
@@ -0,0 +1,108 @@
+/**
+ * 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.ldn.action;
+
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.util.Date;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.content.Item;
+import org.dspace.content.QAEvent;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Context;
+import org.dspace.handle.service.HandleService;
+import org.dspace.qaevent.service.QAEventService;
+import org.dspace.qaevent.service.dto.NotifyMessageDTO;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * Implementation for LDN Correction Action. It creates a QA Event according to the LDN Message received *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.it)
+ *
+ */
+public class LDNCorrectionAction implements LDNAction {
+
+ private static final Logger log = LogManager.getLogger(LDNEmailAction.class);
+
+ private String qaEventTopic;
+
+ @Autowired
+ private ConfigurationService configurationService;
+ @Autowired
+ protected ItemService itemService;
+ @Autowired
+ private QAEventService qaEventService;
+ @Autowired
+ private LDNMessageService ldnMessageService;
+ @Autowired
+ private HandleService handleService;
+
+ @Override
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
+ LDNActionStatus result = LDNActionStatus.ABORT;
+ String itemName = itemService.getName(item);
+ QAEvent qaEvent = null;
+ if (notification.getObject() != null) {
+ String citeAs = notification.getObject().getIetfCiteAs();
+ if (citeAs == null || citeAs.isEmpty()) {
+ citeAs = notification.getObject().getId();
+ }
+ NotifyMessageDTO message = new NotifyMessageDTO();
+ message.setHref(citeAs);
+ message.setRelationship(notification.getObject().getAsRelationship());
+ if (notification.getOrigin() != null) {
+ message.setServiceId(notification.getOrigin().getId());
+ message.setServiceName(notification.getOrigin().getInbox());
+ }
+ BigDecimal score = getScore(context, notification);
+ double doubleScoreValue = score != null ? score.doubleValue() : 0d;
+ ObjectMapper mapper = new ObjectMapper();
+ qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE,
+ handleService.findHandle(context, item), item.getID().toString(), itemName,
+ this.getQaEventTopic(), doubleScoreValue,
+ mapper.writeValueAsString(message),
+ new Date());
+ qaEventService.store(context, qaEvent);
+ result = LDNActionStatus.CONTINUE;
+ }
+
+ return result;
+ }
+
+ private BigDecimal getScore(Context context, Notification notification) throws SQLException {
+
+ if (notification.getOrigin() == null) {
+ return BigDecimal.ZERO;
+ }
+
+ NotifyServiceEntity service = ldnMessageService.findNotifyService(context, notification.getOrigin());
+
+ if (service == null) {
+ return BigDecimal.ZERO;
+ }
+
+ return service.getScore();
+ }
+
+ public String getQaEventTopic() {
+ return qaEventTopic;
+ }
+
+ public void setQaEventTopic(String qaEventTopic) {
+ this.qaEventTopic = qaEventTopic;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java
new file mode 100644
index 000000000000..32b115bd07f6
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNEmailAction.java
@@ -0,0 +1,155 @@
+/**
+ * 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.ldn.action;
+
+import static java.lang.String.format;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.core.Email;
+import org.dspace.core.I18nUtil;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Action to send email to recipients provided in actionSendFilter. The email
+ * body will be result of templating actionSendFilter.
+ */
+public class LDNEmailAction implements LDNAction {
+
+ private static final Logger log = LogManager.getLogger(LDNEmailAction.class);
+
+ private final static String DATE_PATTERN = "dd-MM-yyyy HH:mm:ss";
+
+ @Autowired
+ private ConfigurationService configurationService;
+
+ /*
+ * Supported for actionSendFilter are:
+ * -
+ * - GROUP:
+ * - SUBMITTER
+ */
+ private String actionSendFilter;
+
+ // The file name for the requested email
+ private String actionSendEmailTextFile;
+
+ /**
+ * Execute sending an email.
+ *
+ * Template context parameters:
+ *
+ * {0} Service Name
+ * {1} Item Name
+ * {2} Service URL
+ * {3} Item URL
+ * {4} Submitter's Name
+ * {5} Date of the received LDN notification
+ * {6} LDN notification
+ * {7} Item
+ *
+ * @param notification
+ * @param item
+ * @return ActionStatus
+ * @throws Exception
+ */
+ @Override
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
+ try {
+ Locale supportedLocale = I18nUtil.getEPersonLocale(context.getCurrentUser());
+ Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, actionSendEmailTextFile));
+
+ // Setting recipients email
+ for (String recipient : retrieveRecipientsEmail(item)) {
+ email.addRecipient(recipient);
+ }
+
+ String date = new SimpleDateFormat(DATE_PATTERN).format(Calendar.getInstance().getTime());
+
+ email.addArgument(notification.getActor().getName());
+ email.addArgument(item.getName());
+ email.addArgument(notification.getActor().getId());
+ email.addArgument(notification.getContext() != null ?
+ notification.getContext().getId() : notification.getObject().getId());
+ email.addArgument(item.getSubmitter().getFullName());
+ email.addArgument(date);
+ email.addArgument(notification);
+ email.addArgument(item);
+
+ email.send();
+ } catch (Exception e) {
+ log.error("An Error Occurred while sending a notification email", e);
+ }
+
+ return LDNActionStatus.CONTINUE;
+ }
+
+ /**
+ * @return String
+ */
+ public String getActionSendFilter() {
+ return actionSendFilter;
+ }
+
+ /**
+ * @param actionSendFilter
+ */
+ public void setActionSendFilter(String actionSendFilter) {
+ this.actionSendFilter = actionSendFilter;
+ }
+
+ /**
+ * @return String
+ */
+ public String getActionSendEmailTextFile() {
+ return actionSendEmailTextFile;
+ }
+
+ /**
+ * @param actionSendEmailTextFile
+ */
+ public void setActionSendEmailTextFile(String actionSendEmailTextFile) {
+ this.actionSendEmailTextFile = actionSendEmailTextFile;
+ }
+
+ /**
+ * Parses actionSendFilter for reserved tokens and returns list of email
+ * recipients.
+ *
+ * @param item the item which to get submitter email
+ * @return List list of email recipients
+ */
+ private List retrieveRecipientsEmail(Item item) {
+ List recipients = new LinkedList();
+
+ if (actionSendFilter.startsWith("SUBMITTER")) {
+ recipients.add(item.getSubmitter().getEmail());
+ } else if (actionSendFilter.startsWith("GROUP:")) {
+ String groupName = actionSendFilter.replace("GROUP:", "");
+ String property = format("email.%s.list", groupName);
+ String[] groupEmails = configurationService.getArrayProperty(property);
+ recipients = Arrays.asList(groupEmails);
+ } else {
+ recipients.add(actionSendFilter);
+ }
+
+ return recipients;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java
new file mode 100644
index 000000000000..f11a42ab2f90
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/LDNRelationCorrectionAction.java
@@ -0,0 +1,110 @@
+/**
+ * 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.ldn.action;
+
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.content.Item;
+import org.dspace.content.QAEvent;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Context;
+import org.dspace.handle.service.HandleService;
+import org.dspace.qaevent.service.QAEventService;
+import org.dspace.qaevent.service.dto.NotifyMessageDTO;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * Implementation for LDN Correction Action. It creates a QA Event according to the LDN Message received *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.it)
+ *
+ */
+public class LDNRelationCorrectionAction implements LDNAction {
+
+ private static final Logger log = LogManager.getLogger(LDNEmailAction.class);
+
+ private String qaEventTopic;
+
+ @Autowired
+ private ConfigurationService configurationService;
+ @Autowired
+ protected ItemService itemService;
+ @Autowired
+ private QAEventService qaEventService;
+ @Autowired
+ private LDNMessageService ldnMessageService;
+ @Autowired
+ private HandleService handleService;
+
+ @Override
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
+ LDNActionStatus result = LDNActionStatus.ABORT;
+ String itemName = itemService.getName(item);
+ QAEvent qaEvent = null;
+ if (notification.getObject() != null) {
+ NotifyMessageDTO message = new NotifyMessageDTO();
+ if (notification.getType().containsAll(Set.of("Announce",
+ "coar-notify:RelationshipAction"))) {
+ message.setHref(notification.getObject().getAsSubject());
+ } else {
+ message.setHref(notification.getObject().getAsObject());
+ }
+ message.setRelationship(notification.getObject().getAsRelationship());
+ if (notification.getOrigin() != null) {
+ message.setServiceId(notification.getOrigin().getId());
+ message.setServiceName(notification.getOrigin().getInbox());
+ }
+ BigDecimal score = getScore(context, notification);
+ double doubleScoreValue = score != null ? score.doubleValue() : 0d;
+ ObjectMapper mapper = new ObjectMapper();
+ qaEvent = new QAEvent(QAEvent.COAR_NOTIFY_SOURCE,
+ handleService.findHandle(context, item), item.getID().toString(), itemName,
+ this.getQaEventTopic(), doubleScoreValue,
+ mapper.writeValueAsString(message),
+ new Date());
+ qaEventService.store(context, qaEvent);
+ result = LDNActionStatus.CONTINUE;
+ }
+
+ return result;
+ }
+
+ private BigDecimal getScore(Context context, Notification notification) throws SQLException {
+
+ if (notification.getOrigin() == null) {
+ return BigDecimal.ZERO;
+ }
+
+ NotifyServiceEntity service = ldnMessageService.findNotifyService(context, notification.getOrigin());
+
+ if (service == null) {
+ return BigDecimal.ZERO;
+ }
+
+ return service.getScore();
+ }
+
+ public String getQaEventTopic() {
+ return qaEventTopic;
+ }
+
+ public void setQaEventTopic(String qaEventTopic) {
+ this.qaEventTopic = qaEventTopic;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java b/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java
new file mode 100644
index 000000000000..c0ecf04304b8
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/action/SendLDNMessageAction.java
@@ -0,0 +1,118 @@
+/**
+ * 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.ldn.action;
+
+import java.net.URI;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.http.Header;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * Action to send LDN Message
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class SendLDNMessageAction implements LDNAction {
+
+ private static final Logger log = LogManager.getLogger(SendLDNMessageAction.class);
+
+ private CloseableHttpClient client = null;
+
+ public SendLDNMessageAction() {
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ client = builder
+ .disableAutomaticRetries()
+ .setMaxConnTotal(5)
+ .build();
+ }
+
+ public SendLDNMessageAction(CloseableHttpClient client) {
+ this();
+ if (client != null) {
+ this.client = client;
+ }
+ }
+
+ @Override
+ public LDNActionStatus execute(Context context, Notification notification, Item item) throws Exception {
+ //TODO authorization with Bearer token should be supported.
+
+ String url = notification.getTarget().getInbox();
+
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.addHeader("Content-Type", "application/ld+json");
+ ObjectMapper mapper = new ObjectMapper();
+ httpPost.setEntity(new StringEntity(mapper.writeValueAsString(notification), "UTF-8"));
+
+ LDNActionStatus result = LDNActionStatus.ABORT;
+ // NOTE: Github believes there is a "Potential server-side request forgery due to a user-provided value"
+ // This is a false positive because the LDN Service URL is configured by the user from DSpace.
+ // See the frontend configuration at [dspace.ui.url]/admin/ldn/services
+ try (
+ CloseableHttpResponse response = client.execute(httpPost);
+ ) {
+ if (isSuccessful(response.getStatusLine().getStatusCode())) {
+ result = LDNActionStatus.CONTINUE;
+ } else if (isRedirect(response.getStatusLine().getStatusCode())) {
+ result = handleRedirect(response, httpPost);
+ }
+ } catch (Exception e) {
+ log.error(e);
+ }
+ return result;
+ }
+
+ private boolean isSuccessful(int statusCode) {
+ return statusCode == HttpStatus.SC_ACCEPTED ||
+ statusCode == HttpStatus.SC_CREATED;
+ }
+
+ private boolean isRedirect(int statusCode) {
+ //org.apache.http.HttpStatus has no enum value for 308!
+ return statusCode == (HttpStatus.SC_TEMPORARY_REDIRECT + 1) ||
+ statusCode == HttpStatus.SC_TEMPORARY_REDIRECT;
+ }
+
+ private LDNActionStatus handleRedirect(CloseableHttpResponse oldresponse,
+ HttpPost request) throws HttpException {
+ Header[] urls = oldresponse.getHeaders(HttpHeaders.LOCATION);
+ String url = urls.length > 0 && urls[0] != null ? urls[0].getValue() : null;
+ if (url == null) {
+ throw new HttpException("Error following redirect, unable to reach"
+ + " the correct url.");
+ }
+ LDNActionStatus result = LDNActionStatus.ABORT;
+ try {
+ request.setURI(new URI(url));
+ try (
+ CloseableHttpResponse response = client.execute(request);
+ ) {
+ if (isSuccessful(response.getStatusLine().getStatusCode())) {
+ return LDNActionStatus.CONTINUE;
+ }
+ }
+ } catch (Exception e) {
+ log.error("Error following redirect:", e);
+ }
+
+ return LDNActionStatus.ABORT;
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java
new file mode 100644
index 000000000000..fcbb485acacc
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/LDNMessageDao.java
@@ -0,0 +1,76 @@
+/**
+ * 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.ldn.dao;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.LDNMessageEntity;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.core.GenericDAO;
+
+/**
+ * Database Access Object interface class for the LDNMessage object.
+ *
+ * The implementation of this class is responsible for all database calls for
+ * the LDNMessage object and is autowired by spring
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface LDNMessageDao extends GenericDAO {
+
+ /**
+ * load the oldest ldn messages considering their {@link org.dspace.app.ldn.LDNMessageEntity#queueLastStartTime}
+ * @param context
+ * @param max_attempts consider ldn_message entity with queue_attempts <= max_attempts
+ * @return ldn message entities to be routed
+ * @throws SQLException
+ */
+ public List findOldestMessageToProcess(Context context, int max_attempts) throws SQLException;
+
+ /**
+ * find ldn message entties in processing status and already timed out.
+ * @param context
+ * @param max_attempts consider ldn_message entity with queue_attempts <= max_attempts
+ * @return ldn message entities
+ * @throws SQLException
+ */
+ public List findProcessingTimedoutMessages(Context context, int max_attempts) throws SQLException;
+
+ /**
+ * find all ldn messages related to an item
+ * @param context
+ * @param item item related to the returned ldn messages
+ * @param activities involves only this specific group of activities
+ * @return all ldn messages related to the given item
+ * @throws SQLException
+ */
+ public List findAllMessagesByItem(
+ Context context, Item item, String... activities) throws SQLException;
+
+ /**
+ * find all ldn messages related to an item and to a specific ldn message
+ * @param context
+ * @param msg the referring ldn message
+ * @param item the referring repository item
+ * @param relatedTypes filter for @see org.dspace.app.ldn.LDNMessageEntity#activityStreamType
+ * @return all related ldn messages
+ * @throws SQLException
+ */
+ public List findAllRelatedMessagesByItem(
+ Context context, LDNMessageEntity msg, Item item, String... relatedTypes) throws SQLException;
+
+ /**
+ *
+ * @param context
+ * @return the list of messages in need to be reprocessed - with queue_status as QUEUE_STATUS_QUEUED_FOR_RETRY
+ * @throws SQLException
+ */
+ public List findMessagesToBeReprocessed(Context context) throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java
new file mode 100644
index 000000000000..9ecd1b728a4e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyPatternToTriggerDao.java
@@ -0,0 +1,49 @@
+/**
+ * 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.ldn.dao;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyPatternToTrigger;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.dspace.core.GenericDAO;
+
+/**
+ * This is the Data Access Object for the {@link NotifyPatternToTrigger} object
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyPatternToTriggerDao extends GenericDAO {
+
+ /**
+ * find the NotifyPatternToTrigger matched with the provided item
+ *
+ * @param context the context
+ * @param item the item
+ * @return the NotifyPatternToTrigger matched the provided item
+ * @throws SQLException if database error
+ */
+ public List findByItem(Context context, Item item) throws SQLException;
+
+ /**
+ * find the NotifyPatternToTrigger matched with the provided
+ * item and pattern
+ *
+ * @param context the context
+ * @param item the item
+ * @param pattern the pattern
+ * @return the NotifyPatternToTrigger matched the provided
+ * item and pattern
+ * @throws SQLException if database error
+ */
+ public List findByItemAndPattern(Context context, Item item, String pattern)
+ throws SQLException;
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java
new file mode 100644
index 000000000000..9751b3038290
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceDao.java
@@ -0,0 +1,45 @@
+/**
+ * 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.ldn.dao;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.core.Context;
+import org.dspace.core.GenericDAO;
+
+/**
+ * This is the Data Access Object for the {@link NotifyServiceEntity} object
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyServiceDao extends GenericDAO {
+ /**
+ * find the NotifyServiceEntity matched with the provided ldnUrl
+ *
+ * @param context the context
+ * @param ldnUrl the ldnUrl
+ * @return the NotifyServiceEntity matched the provided ldnUrl
+ * @throws SQLException if database error
+ */
+ public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException;
+
+ /**
+ * find all NotifyServiceEntity matched the provided inbound pattern
+ * from the related notifyServiceInboundPatterns
+ * also with 'automatic' equals to false
+ *
+ * @param context the context
+ * @param pattern the ldnUrl
+ * @return all NotifyServiceEntity matched the provided pattern
+ * @throws SQLException if database error
+ */
+ public List findManualServicesByInboundPattern(Context context, String pattern)
+ throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java
new file mode 100644
index 000000000000..194d30e79598
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/NotifyServiceInboundPatternDao.java
@@ -0,0 +1,47 @@
+/**
+ * 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.ldn.dao;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.core.Context;
+import org.dspace.core.GenericDAO;
+
+/**
+ * This is the Data Access Object for the {@link NotifyServiceInboundPattern} object
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyServiceInboundPatternDao extends GenericDAO {
+
+ /**
+ * find all notifyServiceInboundPatterns matched with
+ * the provided notifyServiceEntity and pattern
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @param pattern the pattern
+ * @return all notifyServiceInboundPatterns matched with
+ * the provided notifyServiceEntity and pattern
+ * @throws SQLException if database error
+ */
+ public NotifyServiceInboundPattern findByServiceAndPattern(Context context,
+ NotifyServiceEntity notifyServiceEntity,
+ String pattern) throws SQLException;
+ /**
+ * find all automatic notifyServiceInboundPatterns
+ *
+ * @param context the context
+ * @return all automatic notifyServiceInboundPatterns
+ * @throws SQLException if database error
+ */
+ List findAutomaticPatterns(Context context) throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java
new file mode 100644
index 000000000000..d811f6d39f34
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/LDNMessageDaoImpl.java
@@ -0,0 +1,169 @@
+/**
+ * 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.ldn.dao.impl;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Order;
+import jakarta.persistence.criteria.Predicate;
+import jakarta.persistence.criteria.Root;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.LDNMessageEntity;
+import org.dspace.app.ldn.LDNMessageEntity_;
+import org.dspace.app.ldn.dao.LDNMessageDao;
+import org.dspace.content.Item;
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.core.Context;
+
+/**
+ * Hibernate implementation of the Database Access Object interface class for
+ * the LDNMessage object. This class is responsible for all database calls for
+ * the LDNMessage object and is autowired by spring
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class LDNMessageDaoImpl extends AbstractHibernateDAO implements LDNMessageDao {
+
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNMessageDaoImpl.class);
+
+ @Override
+ public List findOldestMessageToProcess(Context context, int max_attempts) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>(3);
+ andPredicates
+ .add(criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_QUEUED));
+ andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueAttempts), max_attempts));
+ andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), new Date()));
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages found to be processed");
+ }
+ return result;
+ }
+
+ @Override
+ public List findMessagesToBeReprocessed(Context context) throws SQLException {
+ // looking for LDN Messages to be reprocessed message
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>(1);
+ andPredicates
+ .add(criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus),
+ LDNMessageEntity.QUEUE_STATUS_QUEUED_FOR_RETRY));
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages found to be processed");
+ }
+ return result;
+ }
+
+ @Override
+ public List findProcessingTimedoutMessages(Context context, int max_attempts)
+ throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>(3);
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSING));
+ andPredicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(LDNMessageEntity_.queueAttempts), max_attempts));
+ andPredicates.add(criteriaBuilder.lessThan(root.get(LDNMessageEntity_.queueTimeout), new Date()));
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages found to be processed");
+ }
+ return result;
+ }
+
+ @Override
+ public List findAllRelatedMessagesByItem(
+ Context context, LDNMessageEntity msg, Item item, String... relatedTypes) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>();
+ Predicate relatedtypePredicate = null;
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED));
+ andPredicates.add(
+ criteriaBuilder.isNull(root.get(LDNMessageEntity_.target)));
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.inReplyTo), msg));
+ if (relatedTypes != null && relatedTypes.length > 0) {
+ relatedtypePredicate = root.get(LDNMessageEntity_.activityStreamType).in(relatedTypes);
+ andPredicates.add(relatedtypePredicate);
+ }
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages ACK found to be processed");
+ }
+ return result;
+ }
+
+ @Override
+ public List findAllMessagesByItem(
+ Context context, Item item, String... activities) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, LDNMessageEntity.class);
+ Root root = criteriaQuery.from(LDNMessageEntity.class);
+ criteriaQuery.select(root);
+ List andPredicates = new ArrayList<>();
+ Predicate activityPredicate = null;
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.queueStatus), LDNMessageEntity.QUEUE_STATUS_PROCESSED));
+ andPredicates.add(
+ criteriaBuilder.equal(root.get(LDNMessageEntity_.object), item));
+ if (activities != null && activities.length > 0) {
+ activityPredicate = root.get(LDNMessageEntity_.activityStreamType).in(activities);
+ andPredicates.add(activityPredicate);
+ }
+ criteriaQuery.where(criteriaBuilder.and(andPredicates.toArray(new Predicate[] {})));
+ List orderList = new LinkedList<>();
+ orderList.add(criteriaBuilder.asc(root.get(LDNMessageEntity_.queueLastStartTime)));
+ orderList.add(criteriaBuilder.desc(root.get(LDNMessageEntity_.queueAttempts)));
+ criteriaQuery.orderBy(orderList);
+ List result = list(context, criteriaQuery, false, LDNMessageEntity.class, -1, -1);
+ if (result == null || result.isEmpty()) {
+ log.debug("No LDN messages found");
+ }
+ return result;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java
new file mode 100644
index 000000000000..53cbeabe005a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyPatternToTriggerDaoImpl.java
@@ -0,0 +1,58 @@
+/**
+ * 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.ldn.dao.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Root;
+import org.dspace.app.ldn.NotifyPatternToTrigger;
+import org.dspace.app.ldn.NotifyPatternToTrigger_;
+import org.dspace.app.ldn.dao.NotifyPatternToTriggerDao;
+import org.dspace.content.Item;
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.core.Context;
+
+/**
+ * Implementation of {@link NotifyPatternToTriggerDao}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyPatternToTriggerDaoImpl extends AbstractHibernateDAO
+ implements NotifyPatternToTriggerDao {
+
+ @Override
+ public List findByItem(Context context, Item item)
+ throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyPatternToTrigger.class);
+ Root notifyServiceEntityRoot = criteriaQuery.from(NotifyPatternToTrigger.class);
+ criteriaQuery.select(notifyServiceEntityRoot);
+ criteriaQuery.where(criteriaBuilder.equal(
+ notifyServiceEntityRoot.get(NotifyPatternToTrigger_.item), item));
+ return list(context, criteriaQuery, false, NotifyPatternToTrigger.class, -1, -1);
+ }
+ @Override
+ public List findByItemAndPattern(Context context, Item item, String pattern)
+ throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyPatternToTrigger.class);
+ Root notifyServiceEntityRoot = criteriaQuery.from(NotifyPatternToTrigger.class);
+ criteriaQuery.select(notifyServiceEntityRoot);
+ criteriaQuery.where(criteriaBuilder.and(
+ criteriaBuilder.equal(
+ notifyServiceEntityRoot.get(NotifyPatternToTrigger_.item), item),
+ criteriaBuilder.equal(
+ notifyServiceEntityRoot.get(NotifyPatternToTrigger_.pattern), pattern)
+ ));
+ return list(context, criteriaQuery, false, NotifyPatternToTrigger.class, -1, -1);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java
new file mode 100644
index 000000000000..bb4cf791da27
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceDaoImpl.java
@@ -0,0 +1,62 @@
+/**
+ * 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.ldn.dao.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Join;
+import jakarta.persistence.criteria.Root;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceEntity_;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.app.ldn.NotifyServiceInboundPattern_;
+import org.dspace.app.ldn.dao.NotifyServiceDao;
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.core.Context;
+
+/**
+ * Implementation of {@link NotifyServiceDao}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceDaoImpl extends AbstractHibernateDAO implements NotifyServiceDao {
+
+ @Override
+ public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceEntity.class);
+ Root notifyServiceEntityRoot = criteriaQuery.from(NotifyServiceEntity.class);
+ criteriaQuery.select(notifyServiceEntityRoot);
+ criteriaQuery.where(criteriaBuilder.equal(
+ notifyServiceEntityRoot.get(NotifyServiceEntity_.ldnUrl), ldnUrl));
+ return uniqueResult(context, criteriaQuery, false, NotifyServiceEntity.class);
+ }
+
+ @Override
+ public List findManualServicesByInboundPattern(Context context, String pattern)
+ throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceEntity.class);
+ Root notifyServiceEntityRoot = criteriaQuery.from(NotifyServiceEntity.class);
+
+ Join notifyServiceInboundPatternJoin =
+ notifyServiceEntityRoot.join(NotifyServiceEntity_.inboundPatterns);
+
+ criteriaQuery.select(notifyServiceEntityRoot);
+ criteriaQuery.where(criteriaBuilder.and(
+ criteriaBuilder.equal(
+ notifyServiceInboundPatternJoin.get(NotifyServiceInboundPattern_.pattern), pattern),
+ criteriaBuilder.equal(
+ notifyServiceInboundPatternJoin.get(NotifyServiceInboundPattern_.automatic), false)));
+
+ return list(context, criteriaQuery, false, NotifyServiceEntity.class, -1, -1);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java
new file mode 100644
index 000000000000..dc3dc1c74491
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/dao/impl/NotifyServiceInboundPatternDaoImpl.java
@@ -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.ldn.dao.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Root;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.app.ldn.NotifyServiceInboundPattern_;
+import org.dspace.app.ldn.dao.NotifyServiceInboundPatternDao;
+import org.dspace.core.AbstractHibernateDAO;
+import org.dspace.core.Context;
+
+/**
+ * Implementation of {@link NotifyServiceInboundPatternDao}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceInboundPatternDaoImpl
+ extends AbstractHibernateDAO implements NotifyServiceInboundPatternDao {
+
+ @Override
+ public NotifyServiceInboundPattern findByServiceAndPattern(Context context, NotifyServiceEntity notifyServiceEntity,
+ String pattern) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceInboundPattern.class);
+ Root inboundPatternRoot = criteriaQuery.from(NotifyServiceInboundPattern.class);
+ criteriaQuery.select(inboundPatternRoot);
+ criteriaQuery.where(criteriaBuilder.and(
+ criteriaBuilder.equal(
+ inboundPatternRoot.get(NotifyServiceInboundPattern_.notifyService), notifyServiceEntity),
+ criteriaBuilder.equal(
+ inboundPatternRoot.get(NotifyServiceInboundPattern_.pattern), pattern)
+ ));
+ return uniqueResult(context, criteriaQuery, false, NotifyServiceInboundPattern.class);
+ }
+
+ @Override
+ public List findAutomaticPatterns(Context context) throws SQLException {
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, NotifyServiceInboundPattern.class);
+ Root inboundPatternRoot = criteriaQuery.from(NotifyServiceInboundPattern.class);
+ criteriaQuery.select(inboundPatternRoot);
+ criteriaQuery.where(
+ criteriaBuilder.equal(
+ inboundPatternRoot.get(NotifyServiceInboundPattern_.automatic), true)
+ );
+ return list(context, criteriaQuery, false, NotifyServiceInboundPattern.class, -1, -1);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java
new file mode 100644
index 000000000000..bbf521123bca
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactory.java
@@ -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.ldn.factory;
+
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+
+/**
+ * Abstract factory to get services for the NotifyService package,
+ * use NotifyServiceFactory.getInstance() to retrieve an implementation
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public abstract class LDNMessageServiceFactory {
+
+ public abstract LDNMessageService getLDNMessageService();
+
+ public static LDNMessageServiceFactory getInstance() {
+ return DSpaceServicesFactory.getInstance()
+ .getServiceManager()
+ .getServiceByName("ldnMessageServiceFactory",
+ LDNMessageServiceFactory.class);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java
new file mode 100644
index 000000000000..a001ece04069
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNMessageServiceFactoryImpl.java
@@ -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.ldn.factory;
+
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Factory implementation to get services for the notifyservices package, use
+ * NotifyServiceFactory.getInstance() to retrieve an implementation
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public class LDNMessageServiceFactoryImpl extends LDNMessageServiceFactory {
+
+ @Autowired(required = true)
+ private LDNMessageService ldnMessageService;
+
+ @Override
+ public LDNMessageService getLDNMessageService() {
+ return ldnMessageService;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java
new file mode 100644
index 000000000000..4b0f107d2498
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactory.java
@@ -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.ldn.factory;
+
+import org.dspace.app.ldn.LDNRouter;
+import org.dspace.services.factory.DSpaceServicesFactory;
+
+/**
+ * Abstract factory to get services for the ldn package, use
+ * LDNRouterFactory.getInstance() to retrieve an implementation
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public abstract class LDNRouterFactory {
+
+ public abstract LDNRouter getLDNRouter();
+
+ public static LDNRouterFactory getInstance() {
+ return DSpaceServicesFactory.getInstance()
+ .getServiceManager()
+ .getServiceByName("ldnRouter",
+ LDNRouterFactory.class);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java
new file mode 100644
index 000000000000..f411b9d935d0
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/LDNRouterFactoryImpl.java
@@ -0,0 +1,28 @@
+/**
+ * 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.ldn.factory;
+
+import org.dspace.app.ldn.LDNRouter;
+import org.springframework.beans.factory.annotation.Autowired;
+/**
+ * Factory implementation to get services for the ldn package,
+ * use ldnRouter spring bean instance to retrieve an implementation
+ *
+ * @author Francesco Bacchelli (mohamed.eskander at 4science.com)
+ */
+public class LDNRouterFactoryImpl extends LDNRouterFactory {
+
+ @Autowired(required = true)
+ private LDNRouter ldnRouter;
+
+ @Override
+ public LDNRouter getLDNRouter() {
+ return ldnRouter;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java
new file mode 100644
index 000000000000..ea488ca25031
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactory.java
@@ -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.ldn.factory;
+
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
+import org.dspace.app.ldn.service.NotifyService;
+import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+
+/**
+ * Abstract factory to get services for the NotifyService package,
+ * use NotifyServiceFactory.getInstance() to retrieve an implementation
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public abstract class NotifyServiceFactory {
+
+ public abstract NotifyService getNotifyService();
+
+ public abstract NotifyServiceInboundPatternService getNotifyServiceInboundPatternService();
+
+ public abstract NotifyPatternToTriggerService getNotifyPatternToTriggerService();
+
+ public abstract LDNMessageService getLDNMessageService();
+
+ public static NotifyServiceFactory getInstance() {
+ return DSpaceServicesFactory.getInstance().getServiceManager().getServiceByName(
+ "notifyServiceFactory", NotifyServiceFactory.class);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java
new file mode 100644
index 000000000000..84e15ee261a2
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/factory/NotifyServiceFactoryImpl.java
@@ -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.ldn.factory;
+
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
+import org.dspace.app.ldn.service.NotifyService;
+import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Factory implementation to get services for the notifyservices package,
+ * use NotifyServiceFactory.getInstance() to retrieve an implementation
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceFactoryImpl extends NotifyServiceFactory {
+
+ @Autowired(required = true)
+ private NotifyService notifyService;
+
+ @Autowired(required = true)
+ private NotifyServiceInboundPatternService notifyServiceInboundPatternService;
+
+ @Autowired(required = true)
+ private NotifyPatternToTriggerService notifyPatternToTriggerService;
+
+ @Autowired(required = true)
+ private LDNMessageService ldnMessageService;
+
+ @Override
+ public NotifyService getNotifyService() {
+ return notifyService;
+ }
+
+ @Override
+ public NotifyServiceInboundPatternService getNotifyServiceInboundPatternService() {
+ return notifyServiceInboundPatternService;
+ }
+
+ @Override
+ public NotifyPatternToTriggerService getNotifyPatternToTriggerService() {
+ return notifyPatternToTriggerService;
+ }
+
+ @Override
+ public LDNMessageService getLDNMessageService() {
+ return ldnMessageService;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java
new file mode 100644
index 000000000000..a81cc3f8008a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Actor.java
@@ -0,0 +1,41 @@
+/**
+ * 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.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Actor extends Base {
+
+ @JsonProperty("name")
+ private String name;
+
+ /**
+ *
+ */
+ public Actor() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java
new file mode 100644
index 000000000000..6ddaae110e1f
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Base.java
@@ -0,0 +1,115 @@
+/**
+ * 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.ldn.model;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+@JsonInclude(Include.NON_EMPTY)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Base {
+
+ @JsonProperty("id")
+ private String id;
+
+ @JsonProperty("type")
+ private Set type;
+
+ /**
+ *
+ */
+ public Base() {
+ type = new HashSet<>();
+ }
+
+ /**
+ * @return String
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return Set
+ */
+ public Set getType() {
+ return type;
+ }
+
+ /**
+ * @param type
+ */
+ public void setType(java.lang.Object type) {
+ if (type instanceof String) {
+ this.type.add((String) type);
+ } else if (type instanceof Collection) {
+ this.type.addAll((Collection) type);
+ }
+ }
+
+ /**
+ * @param type
+ */
+ public void addType(String type) {
+ this.type.add(type);
+ }
+
+ /**
+ * @return int
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ return result;
+ }
+
+ /**
+ * @param obj
+ * @return boolean
+ */
+ @Override
+ public boolean equals(java.lang.Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Base other = (Base) obj;
+ if (id == null) {
+ if (other.id != null) {
+ return false;
+ }
+ } else if (!id.equals(other.id)) {
+ return false;
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java
new file mode 100644
index 000000000000..7abe5c8ef44a
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Citation.java
@@ -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.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Citation extends Base {
+
+ @JsonProperty("ietf:cite-as")
+ private String ietfCiteAs;
+
+ @JsonProperty("ietf:item")
+ private Url url;
+
+ /**
+ *
+ */
+ public Citation() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getIetfCiteAs() {
+ return ietfCiteAs;
+ }
+
+ /**
+ * @param ietfCiteAs
+ */
+ public void setIetfCiteAs(String ietfCiteAs) {
+ this.ietfCiteAs = ietfCiteAs;
+ }
+
+ /**
+ * @return Url
+ */
+ public Url getUrl() {
+ return url;
+ }
+
+ /**
+ * @param url
+ */
+ public void setUrl(Url url) {
+ this.url = url;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java
new file mode 100644
index 000000000000..78fe37341697
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Context.java
@@ -0,0 +1,61 @@
+/**
+ * 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.ldn.model;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Context extends Citation {
+
+ @JsonProperty("IsSupplementedBy")
+ private List isSupplementedBy;
+
+ @JsonProperty("IsSupplementTo")
+ private List isSupplementTo;
+
+ /**
+ *
+ */
+ public Context() {
+ super();
+ }
+
+ /**
+ * @return List
+ */
+ public List getIsSupplementedBy() {
+ return isSupplementedBy;
+ }
+
+ /**
+ * @param isSupplementedBy
+ */
+ public void setIsSupplementedBy(List isSupplementedBy) {
+ this.isSupplementedBy = isSupplementedBy;
+ }
+
+ /**
+ * @return List
+ */
+ public List getIsSupplementTo() {
+ return isSupplementTo;
+ }
+
+ /**
+ * @param isSupplementTo
+ */
+ public void setIsSupplementTo(List isSupplementTo) {
+ this.isSupplementTo = isSupplementTo;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java
new file mode 100644
index 000000000000..52bc9840f42d
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Notification.java
@@ -0,0 +1,159 @@
+/**
+ * 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.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+/**
+ * the json object from witch @see org.dspace.app.ldn.LDNMessageEntity are created.
+ * see official coar doc
+ */
+@JsonPropertyOrder(value = {
+ "@context",
+ "id",
+ "type",
+ "actor",
+ "context",
+ "object",
+ "origin",
+ "target",
+ "inReplyTo"
+})
+public class Notification extends Base {
+
+ @JsonProperty("@context")
+ private String[] c = new String[] {
+ "https://purl.org/coar/notify",
+ "https://www.w3.org/ns/activitystreams"
+ };
+
+ @JsonProperty("actor")
+ private Actor actor;
+
+ @JsonProperty("context")
+ private Context context;
+
+ @JsonProperty("object")
+ private Object object;
+
+ @JsonProperty("origin")
+ private Service origin;
+
+ @JsonProperty("target")
+ private Service target;
+
+ @JsonProperty("inReplyTo")
+ private String inReplyTo;
+
+ /**
+ *
+ */
+ public Notification() {
+ super();
+ }
+
+ /**
+ * @return String[]
+ */
+ public String[] getC() {
+ return c;
+ }
+
+ /**
+ * @param c
+ */
+ public void setC(String[] c) {
+ this.c = c;
+ }
+
+ /**
+ * @return Actor
+ */
+ public Actor getActor() {
+ return actor;
+ }
+
+ /**
+ * @param actor
+ */
+ public void setActor(Actor actor) {
+ this.actor = actor;
+ }
+
+ /**
+ * @return Context
+ */
+ public Context getContext() {
+ return context;
+ }
+
+ /**
+ * @param context
+ */
+ public void setContext(Context context) {
+ this.context = context;
+ }
+
+ /**
+ * @return Object
+ */
+ public Object getObject() {
+ return object;
+ }
+
+ /**
+ * @param object
+ */
+ public void setObject(Object object) {
+ this.object = object;
+ }
+
+ /**
+ * @return Service
+ */
+ public Service getOrigin() {
+ return origin;
+ }
+
+ /**
+ * @param origin
+ */
+ public void setOrigin(Service origin) {
+ this.origin = origin;
+ }
+
+ /**
+ * @return Service
+ */
+ public Service getTarget() {
+ return target;
+ }
+
+ /**
+ * @param target
+ */
+ public void setTarget(Service target) {
+ this.target = target;
+ }
+
+ /**
+ * @return String
+ */
+ public String getInReplyTo() {
+ return inReplyTo;
+ }
+
+ /**
+ * @param inReplyTo
+ */
+ public void setInReplyTo(String inReplyTo) {
+ this.inReplyTo = inReplyTo;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java
new file mode 100644
index 000000000000..8333eae91024
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatus.java
@@ -0,0 +1,63 @@
+/**
+ * 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.ldn.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+@JsonPropertyOrder(value = {
+ "itemuuid",
+ "notifyStatus"
+})
+
+/**
+ * item requests of LDN messages of type
+ *
+ * "Offer", "coar-notify:EndorsementAction"
+ * "Offer", "coar-notify:IngestAction"
+ * "Offer", "coar-notify:ReviewAction"
+ *
+ * and their acknowledgements - if any
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science dot it)
+ */
+public class NotifyRequestStatus extends Base {
+
+ private UUID itemUuid;
+
+ private List notifyStatus;
+
+ public NotifyRequestStatus() {
+ super();
+ this.notifyStatus = new ArrayList();
+ }
+
+ public UUID getItemUuid() {
+ return itemUuid;
+ }
+
+ public void setItemUuid(UUID itemUuid) {
+ this.itemUuid = itemUuid;
+ }
+
+ public void addRequestStatus(RequestStatus rs) {
+ this.notifyStatus.add(rs);
+ }
+
+ public List getNotifyStatus() {
+ return notifyStatus;
+ }
+
+ public void setNotifyStatus(List notifyStatus) {
+ this.notifyStatus = notifyStatus;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java
new file mode 100644
index 000000000000..437c624f84d8
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/NotifyRequestStatusEnum.java
@@ -0,0 +1,18 @@
+/**
+ * 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.ldn.model;
+/**
+ * REQUESTED means acknowledgements not received yet
+ * ACCEPTED means acknowledgements of "Accept" type received
+ * REJECTED means ack of "TentativeReject" type received
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public enum NotifyRequestStatusEnum {
+ REJECTED, ACCEPTED, REQUESTED
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java
new file mode 100644
index 000000000000..8913af47dae1
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Object.java
@@ -0,0 +1,74 @@
+/**
+ * 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.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Object extends Citation {
+
+ @JsonProperty("as:object")
+ private String asObject;
+
+ @JsonProperty("as:relationship")
+ private String asRelationship;
+
+ @JsonProperty("as:subject")
+ private String asSubject;
+
+ @JsonProperty("sorg:name")
+ private String title;
+
+ /**
+ *
+ */
+ public Object() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * @param title
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAsObject() {
+ return asObject;
+ }
+
+ public void setAsObject(String asObject) {
+ this.asObject = asObject;
+ }
+
+ public String getAsRelationship() {
+ return asRelationship;
+ }
+
+ public void setAsRelationship(String asRelationship) {
+ this.asRelationship = asRelationship;
+ }
+
+ public String getAsSubject() {
+ return asSubject;
+ }
+
+ public void setAsSubject(String asSubject) {
+ this.asSubject = asSubject;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java
new file mode 100644
index 000000000000..e33bc3eeb7d5
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/RequestStatus.java
@@ -0,0 +1,47 @@
+/**
+ * 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.ldn.model;
+
+/**
+ * Information about the Offer and Acknowledgements targeting a specified Item
+ *
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ */
+public class RequestStatus {
+
+ private String serviceName;
+ private String serviceUrl;
+ private String offerType;
+ private NotifyRequestStatusEnum status;
+
+ public String getServiceName() {
+ return serviceName;
+ }
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+ public String getServiceUrl() {
+ return serviceUrl;
+ }
+ public void setServiceUrl(String serviceUrl) {
+ this.serviceUrl = serviceUrl;
+ }
+ public NotifyRequestStatusEnum getStatus() {
+ return status;
+ }
+ public void setStatus(NotifyRequestStatusEnum status) {
+ this.status = status;
+ }
+ public String getOfferType() {
+ return offerType;
+ }
+ public void setOfferType(String offerType) {
+ this.offerType = offerType;
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java
new file mode 100644
index 000000000000..cdd3ba5bb536
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Service.java
@@ -0,0 +1,41 @@
+/**
+ * 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.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Notification
+ */
+public class Service extends Base {
+
+ @JsonProperty("inbox")
+ private String inbox;
+
+ /**
+ *
+ */
+ public Service() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getInbox() {
+ return inbox;
+ }
+
+ /**
+ * @param inbox
+ */
+ public void setInbox(String inbox) {
+ this.inbox = inbox;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java b/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java
new file mode 100644
index 000000000000..47093e02e44b
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/model/Url.java
@@ -0,0 +1,41 @@
+/**
+ * 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.ldn.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * used to map @see org.dspace.app.ldn.model.Citation
+ */
+public class Url extends Base {
+
+ @JsonProperty("mediaType")
+ private String mediaType;
+
+ /**
+ *
+ */
+ public Url() {
+ super();
+ }
+
+ /**
+ * @return String
+ */
+ public String getMediaType() {
+ return mediaType;
+ }
+
+ /**
+ * @param mediaType
+ */
+ public void setMediaType(String mediaType) {
+ this.mediaType = mediaType;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java
new file mode 100644
index 000000000000..a5ef0957ff8f
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNContextRepeater.java
@@ -0,0 +1,141 @@
+/**
+ * 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.ldn.processor;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.model.Context;
+import org.dspace.app.ldn.model.Notification;
+
+/**
+ * Context repeater to iterate over array context properties of a received
+ * notification. The returned notification iterator is a notification with
+ * context array elements hoisted onto the root of the notification context.
+ */
+public class LDNContextRepeater {
+
+ private final static Logger log = LogManager.getLogger(LDNContextRepeater.class);
+
+ private final static String CONTEXT = "context";
+
+ private String repeatOver;
+
+ /**
+ * @return String
+ */
+ public String getRepeatOver() {
+ return repeatOver;
+ }
+
+ /**
+ * @param repeatOver
+ */
+ public void setRepeatOver(String repeatOver) {
+ this.repeatOver = repeatOver;
+ }
+
+ /**
+ * @param notification
+ * @return Iterator
+ */
+ public Iterator iterator(Notification notification) {
+ return new NotificationIterator(notification, repeatOver);
+ }
+
+ /**
+ * Private inner class defining the notification iterator.
+ */
+ private class NotificationIterator implements Iterator {
+
+ private final List notifications;
+
+ /**
+ * Convert notification to JsonNode in order to clone for each context array
+ * element. Each element is then hoisted to the root of the cloned notification
+ * context.
+ *
+ * @param notification received notification
+ * @param repeatOver which context property to repeat over
+ */
+ private NotificationIterator(Notification notification, String repeatOver) {
+ this.notifications = new ArrayList<>();
+
+ if (Objects.nonNull(repeatOver)) {
+ ObjectMapper objectMapper = new ObjectMapper();
+
+ JsonNode notificationNode = objectMapper.valueToTree(notification);
+
+ log.debug("Notification {}", notificationNode);
+
+ JsonNode topContextNode = notificationNode.get(CONTEXT);
+ if (topContextNode.isNull()) {
+ log.warn("Notification is missing context");
+ return;
+ }
+
+ JsonNode contextArrayNode = topContextNode.get(repeatOver);
+ if (contextArrayNode == null || contextArrayNode.isNull()) {
+ log.error("Notification context {} is not defined", repeatOver);
+ return;
+ }
+
+ if (contextArrayNode.isArray()) {
+
+ for (JsonNode contextNode : ((ArrayNode) contextArrayNode)) {
+
+ try {
+ Context context = objectMapper.treeToValue(contextNode, Context.class);
+
+ Notification copy = objectMapper.treeToValue(notificationNode, Notification.class);
+
+ copy.setContext(context);
+
+ this.notifications.add(copy);
+ } catch (JsonProcessingException e) {
+ log.error("Failed to copy notification");
+ }
+
+ }
+
+ } else {
+ log.error("Notification context {} is not an array", repeatOver);
+ }
+
+ } else {
+ this.notifications.add(notification);
+ }
+ }
+
+ /**
+ * @return boolean
+ */
+ @Override
+ public boolean hasNext() {
+ return !this.notifications.isEmpty();
+ }
+
+ /**
+ * @return Notification
+ */
+ @Override
+ public Notification next() {
+ return this.notifications.remove(0);
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java
new file mode 100644
index 000000000000..9782e2504588
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNMetadataProcessor.java
@@ -0,0 +1,231 @@
+/**
+ * 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.ldn.processor;
+
+import static java.lang.String.format;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpResponseException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.action.LDNAction;
+import org.dspace.app.ldn.action.LDNActionStatus;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.utility.LDNUtils;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.Item;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.handle.service.HandleService;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Linked Data Notification metadata processor for consuming notifications. The
+ * storage of notification details are within item metadata.
+ */
+public class LDNMetadataProcessor implements LDNProcessor {
+
+ private final static Logger log = LogManager.getLogger(LDNMetadataProcessor.class);
+
+ @Autowired
+ private ItemService itemService;
+
+ @Autowired
+ private LDNMessageService ldnMessageService;
+
+ @Autowired
+ private ConfigurationService configurationService;
+
+ private static final Set OBJECT_SUBJECT_ITEM_TYPES = Set.of(
+ "Announce",
+ "coar-notify:RelationshipAction");
+
+ private static final Set CONTEXT_ID_ITEM_TYPES = Set.of(
+ "Announce",
+ "TentativeReject",
+ "Accept",
+ "coar-notify:ReviewAction",
+ "coar-notify:IngestAction",
+ "coar-notify:EndorsementAction");
+
+ private static final Set OBJECT_ID_ITEM_TYPES = Set.of(
+ "Offer",
+ "coar-notify:ReviewAction",
+ "coar-notify:EndorsementAction",
+ "coar-notify:IngestAction");
+
+ @Autowired
+ private HandleService handleService;
+
+ private LDNContextRepeater repeater = new LDNContextRepeater();
+
+ private List actions = new ArrayList<>();
+
+ /**
+ * Initialize velocity engine for templating.
+ */
+ private LDNMetadataProcessor() {
+
+ }
+
+ /**
+ * Process notification by repeating over context, processing each context
+ * notification, and running actions post processing.
+ *
+ * @param notification received notification
+ * @throws Exception something went wrong processing the notification
+ */
+ @Override
+ public void process(Context context, Notification notification) throws Exception {
+ Item item = lookupItem(context, notification);
+ runActions(context, notification, item);
+ }
+
+ /**
+ * Run all actions defined for the processor.
+ *
+ * @param notification current context notification
+ * @param item associated item
+ *
+ * @return ActionStatus result status of running the action
+ *
+ * @throws Exception failed execute the action
+ */
+ private LDNActionStatus runActions(Context context, Notification notification, Item item) throws Exception {
+ LDNActionStatus operation = LDNActionStatus.CONTINUE;
+ for (LDNAction action : actions) {
+ log.info("Running action {} for notification {} {}",
+ action.getClass().getSimpleName(),
+ notification.getId(),
+ notification.getType());
+
+ operation = action.execute(context, notification, item);
+ if (operation == LDNActionStatus.ABORT) {
+ break;
+ }
+ }
+
+ return operation;
+ }
+
+ /**
+ * @return LDNContextRepeater
+ */
+ public LDNContextRepeater getRepeater() {
+ return repeater;
+ }
+
+ /**
+ * @param repeater
+ */
+ public void setRepeater(LDNContextRepeater repeater) {
+ this.repeater = repeater;
+ }
+
+ /**
+ * @return List
+ */
+ public List getActions() {
+ return actions;
+ }
+
+ /**
+ * @param actions
+ */
+ public void setActions(List actions) {
+ this.actions = actions;
+ }
+
+ /**
+ * Lookup associated item to the notification context. If UUID in URL, lookup by
+ * UUID, else lookup by handle.
+ *
+ * @param context current context
+ * @param notification current context notification
+ *
+ * @return Item associated item
+ *
+ * @throws SQLException failed to lookup item
+ * @throws HttpResponseException redirect failure
+ */
+ private Item lookupItem(Context context, Notification notification) throws SQLException, HttpResponseException {
+ Item item = null;
+ String url = null;
+
+ if (CONTEXT_ID_ITEM_TYPES.containsAll(notification.getType())) {
+ url = notification.getContext().getId();
+ } else if (OBJECT_ID_ITEM_TYPES.containsAll(notification.getType())) {
+ url = notification.getObject().getId();
+ } else if (OBJECT_SUBJECT_ITEM_TYPES.containsAll(notification.getType())) {
+ // need to understand if we're sender or receiver
+ if (ldnMessageService.isTargetCurrent(notification)) {
+ // this means we're sending the notification
+ url = notification.getObject().getAsObject();
+ // use as:object for sender
+ } else {
+ // this means we're receiving the notification
+ url = notification.getObject().getAsSubject();
+ // use as:subject for receiver
+ }
+ }
+
+ log.info("Looking up item {}", url);
+
+ item = resolveItemByUrl(context, url, notification);
+
+ return item;
+ }
+
+ private Item resolveItemByUrl(Context context, String url, Notification notification)
+ throws SQLException, HttpResponseException {
+ Item item = null;
+ if (LDNUtils.hasUUIDInURL(url)) {
+ UUID uuid = LDNUtils.getUUIDFromURL(url);
+
+ item = itemService.find(context, uuid);
+
+ if (Objects.isNull(item)) {
+ throw new HttpResponseException(HttpStatus.SC_NOT_FOUND,
+ format("Item with uuid %s not found", uuid));
+ }
+ return item;
+ }
+ String handle = handleService.resolveUrlToHandle(context, url);
+
+ if (Objects.isNull(handle)) {
+ throw new HttpResponseException(HttpStatus.SC_NOT_FOUND,
+ format("Handle not found for %s", url));
+ }
+
+ DSpaceObject object = handleService.resolveToObject(context, handle);
+
+ if (Objects.isNull(object)) {
+ throw new HttpResponseException(HttpStatus.SC_NOT_FOUND,
+ format("Item with handle %s not found", handle));
+ }
+
+ if (object.getType() == Constants.ITEM) {
+ item = (Item) object;
+ } else {
+ throw new HttpResponseException(HttpStatus.SC_UNPROCESSABLE_ENTITY,
+ format("Handle %s does not resolve to an item", handle));
+ }
+ return item;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java
new file mode 100644
index 000000000000..279ec5cedc4b
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/processor/LDNProcessor.java
@@ -0,0 +1,25 @@
+/**
+ * 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.ldn.processor;
+
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.core.Context;
+
+/**
+ * Processor interface to allow for custom implementations of process.
+ */
+public interface LDNProcessor {
+
+ /**
+ * Process received notification.
+ *
+ * @param notification received notification
+ * @throws Exception something went wrong processing the notification
+ */
+ public void process(Context context, Notification notification) throws Exception;
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java
new file mode 100644
index 000000000000..eb18c6a69a70
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/LDNMessageService.java
@@ -0,0 +1,165 @@
+/**
+ * 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.ldn.service;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.LDNMessageEntity;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.model.NotifyRequestStatus;
+import org.dspace.app.ldn.model.Service;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * Service interface class for the {@link LDNMessageEntity} object.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science dot it)
+ */
+public interface LDNMessageService {
+
+ /**
+ * find the ldn message by id
+ *
+ * @param context the context
+ * @param id the uri
+ * @return the ldn message by id
+ * @throws SQLException If something goes wrong in the database
+ */
+ public LDNMessageEntity find(Context context, String id) throws SQLException;
+
+ /**
+ * find all ldn messages
+ *
+ * @param context the context
+ * @return all ldn messages by id
+ * @throws SQLException If something goes wrong in the database
+ */
+ public List findAll(Context context) throws SQLException;
+
+ /**
+ * Creates a new LDNMessage
+ *
+ * @param context The DSpace context
+ * @param id the uri
+ * @return the created LDN Message
+ * @throws SQLException If something goes wrong in the database
+ */
+ public LDNMessageEntity create(Context context, String id) throws SQLException;
+
+ /**
+ * Creates a new LDNMessage
+ *
+ * @param context The DSpace context
+ * @param notification the requested notification
+ * @param sourceIp the source ip
+ * @return the created LDN Message
+ * @throws SQLException If something goes wrong in the database
+ */
+ public LDNMessageEntity create(Context context, Notification notification, String sourceIp) throws SQLException;
+
+ /**
+ * Update the provided LDNMessage
+ *
+ * @param context The DSpace context
+ * @param ldnMessage the LDNMessage
+ * @throws SQLException If something goes wrong in the database
+ */
+ public void update(Context context, LDNMessageEntity ldnMessage) throws SQLException;
+
+ /**
+ * Find the oldest queued LDNMessages that still can be elaborated
+ *
+ * @return list of LDN messages
+ * @param context The DSpace context
+ * @throws SQLException If something goes wrong in the database
+ */
+ public List findOldestMessagesToProcess(Context context) throws SQLException;
+
+ /**
+ * Find all messages in the queue with the Processing status but timed-out
+ *
+ * @return all the LDN Messages to be fixed on their queue_ attributes
+ * @param context The DSpace context
+ * @throws SQLException If something goes wrong in the database
+ */
+ public List findProcessingTimedoutMessages(Context context) throws SQLException;
+
+ /**
+ * Find all messages in the queue with the Processing status but timed-out and modify their queue_status
+ * considering the queue_attempts
+ *
+ * @return number of messages fixed
+ * @param context The DSpace context
+ * @throws SQLException
+ */
+ public int checkQueueMessageTimeout(Context context) throws SQLException;
+
+ /**
+ * Elaborates the oldest enqueued message
+ *
+ * @return number of messages fixed
+ * @param context The DSpace context
+ */
+ public int extractAndProcessMessageFromQueue(Context context) throws SQLException;
+
+ /**
+ * find the related notify service entity
+ *
+ * @param context the context
+ * @param service the service
+ * @return the NotifyServiceEntity
+ * @throws SQLException if something goes wrong
+ */
+ public NotifyServiceEntity findNotifyService(Context context, Service service) throws SQLException;
+
+ /**
+ * find the ldn messages of Requests by item uuid
+ *
+ * @param context the context
+ * @param item the item
+ * @return the item requests object
+ * @throws SQLException If something goes wrong in the database
+ */
+ public NotifyRequestStatus findRequestsByItem(Context context, Item item) throws SQLException;
+
+ /**
+ * delete the provided ldn message
+ *
+ * @param context the context
+ * @param ldnMessage the ldn message
+ * @throws SQLException if something goes wrong
+ */
+ public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException;
+
+ /**
+ * find the ldn messages to be reprocessed
+ *
+ * @param context the context
+ * @throws SQLException if something goes wrong
+ */
+ public List findMessagesToBeReprocessed(Context context) throws SQLException;
+
+ /**
+ * check if IP number is included in the configured ip-range on the Notify
+ * Service
+ *
+ * @param origin the Notify Service entity
+ * @param sourceIp the ip to evaluate
+ */
+ public boolean isValidIp(NotifyServiceEntity origin, String sourceIp);
+
+ /**
+ * check if the notification is targeting the current system
+ *
+ * @param notification the LDN Message entity
+ */
+ boolean isTargetCurrent(Notification notification);
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java
new file mode 100644
index 000000000000..c2c3de7e06d5
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyPatternToTriggerService.java
@@ -0,0 +1,84 @@
+/**
+ * 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.ldn.service;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyPatternToTrigger;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+
+/**
+ * Service interface class for the {@link NotifyPatternToTrigger} object.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyPatternToTriggerService {
+
+ /**
+ * find all notify patterns to be triggered
+ *
+ * @param context the context
+ * @return all notify patterns to be trigger
+ * @throws SQLException if database error
+ */
+ public List findAll(Context context) throws SQLException;
+
+ /**
+ * find list of Notify Patterns To be Triggered by item
+ *
+ * @param context the context
+ * @param item the item of NotifyPatternToTrigger
+ * @return the matched NotifyPatternToTrigger list by item
+ * @throws SQLException if database error
+ */
+ public List findByItem(Context context, Item item)
+ throws SQLException;
+
+ /**
+ * find list of Notify Patterns To be Triggered by item and pattern
+ *
+ * @param context the context
+ * @param item the item of NotifyPatternToTrigger
+ * @param pattern the pattern of NotifyPatternToTrigger
+ *
+ * @return the matched NotifyPatternToTrigger list by item and pattern
+ * @throws SQLException if database error
+ */
+ public List findByItemAndPattern(Context context, Item item, String pattern)
+ throws SQLException;
+
+ /**
+ * create new notifyPatternToTrigger
+ *
+ * @param context the context
+ * @return the created NotifyPatternToTrigger
+ * @throws SQLException if database error
+ */
+ public NotifyPatternToTrigger create(Context context) throws SQLException;
+
+ /**
+ * update the provided notifyPatternToTrigger
+ *
+ * @param context the context
+ * @param notifyPatternToTrigger the notifyPatternToTrigger
+ * @throws SQLException if database error
+ */
+ public void update(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException;
+
+ /**
+ * delete the provided notifyPatternToTrigger
+ *
+ * @param context the context
+ * @param notifyPatternToTrigger the notifyPatternToTrigger
+ * @throws SQLException if database error
+ */
+ public void delete(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException;
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java
new file mode 100644
index 000000000000..e6ac0d63b438
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyService.java
@@ -0,0 +1,92 @@
+/**
+ * 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.ldn.service;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.core.Context;
+
+/**
+ * Service interface class for the {@link NotifyServiceEntity} object.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyService {
+
+ /**
+ * find all notify service entities
+ *
+ * @param context the context
+ * @return all notify service entities
+ * @throws SQLException if database error
+ */
+ public List findAll(Context context) throws SQLException;
+
+ /**
+ * find one NotifyServiceEntity by id
+ *
+ * @param context the context
+ * @param id the id of NotifyServiceEntity
+ * @return the matched NotifyServiceEntity by id
+ * @throws SQLException if database error
+ */
+ public NotifyServiceEntity find(Context context, Integer id) throws SQLException;
+
+ /**
+ * create new notifyServiceEntity
+ *
+ * @param context the context
+ * @param name name of the service
+ * @return the created NotifyServiceEntity
+ * @throws SQLException if database error
+ */
+ public NotifyServiceEntity create(Context context, String name) throws SQLException;
+
+ /**
+ * update the provided notifyServiceEntity
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @throws SQLException if database error
+ */
+ public void update(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException;
+
+ /**
+ * delete the provided notifyServiceEntity
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @throws SQLException if database error
+ */
+ public void delete(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException;
+
+ /**
+ * find the NotifyServiceEntity matched with the provided ldnUrl
+ *
+ * @param context the context
+ * @param ldnUrl the ldnUrl
+ * @return the NotifyServiceEntity matched the provided ldnUrl
+ * @throws SQLException if database error
+ */
+ public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException;
+
+ /**
+ * find all NotifyServiceEntity matched the provided inbound pattern
+ * from its related notifyServiceInboundPatterns
+ * also with 'automatic' equals to false
+ *
+ * @param context the context
+ * @param pattern the ldnUrl
+ * @return all NotifyServiceEntity matched the provided pattern
+ * @throws SQLException if database error
+ */
+ public List findManualServicesByInboundPattern(Context context, String pattern)
+ throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java
new file mode 100644
index 000000000000..8cd92d45dd30
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/NotifyServiceInboundPatternService.java
@@ -0,0 +1,76 @@
+/**
+ * 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.ldn.service;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.core.Context;
+
+/**
+ * Service interface class for the {@link NotifyServiceInboundPattern} object.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public interface NotifyServiceInboundPatternService {
+
+ /**
+ * find all notifyServiceInboundPatterns matched with
+ * the provided notifyServiceEntity and pattern
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @param pattern the pattern
+ * @return all notifyServiceInboundPatterns matched with
+ * the provided notifyServiceEntity and pattern
+ * @throws SQLException if database error
+ */
+ public NotifyServiceInboundPattern findByServiceAndPattern(Context context,
+ NotifyServiceEntity notifyServiceEntity,
+ String pattern) throws SQLException;
+
+ /**
+ * find all automatic notifyServiceInboundPatterns
+ *
+ * @param context the context
+ * @return all automatic notifyServiceInboundPatterns
+ * @throws SQLException if database error
+ */
+ public List findAutomaticPatterns(Context context) throws SQLException;
+
+ /**
+ * create new notifyServiceInboundPattern
+ *
+ * @param context the context
+ * @param notifyServiceEntity the notifyServiceEntity
+ * @return the created notifyServiceInboundPattern
+ * @throws SQLException if database error
+ */
+ public NotifyServiceInboundPattern create(Context context, NotifyServiceEntity notifyServiceEntity)
+ throws SQLException;
+
+ /**
+ * update the provided notifyServiceInboundPattern
+ *
+ * @param context the context
+ * @param inboundPattern the notifyServiceInboundPattern
+ * @throws SQLException if database error
+ */
+ public void update(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException;
+
+ /**
+ * delete the provided notifyServiceInboundPattern
+ *
+ * @param context the context
+ * @param inboundPattern the notifyServiceInboundPattern
+ * @throws SQLException if database error
+ */
+ public void delete(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java
new file mode 100644
index 000000000000..15f07a556112
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/LDNMessageServiceImpl.java
@@ -0,0 +1,404 @@
+/**
+ * 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.ldn.service.impl;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.JsonSyntaxException;
+import org.apache.commons.lang.time.DateUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.Logger;
+import org.dspace.app.ldn.LDNMessageEntity;
+import org.dspace.app.ldn.LDNRouter;
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.dao.LDNMessageDao;
+import org.dspace.app.ldn.dao.NotifyServiceDao;
+import org.dspace.app.ldn.model.Notification;
+import org.dspace.app.ldn.model.NotifyRequestStatus;
+import org.dspace.app.ldn.model.NotifyRequestStatusEnum;
+import org.dspace.app.ldn.model.RequestStatus;
+import org.dspace.app.ldn.model.Service;
+import org.dspace.app.ldn.processor.LDNProcessor;
+import org.dspace.app.ldn.service.LDNMessageService;
+import org.dspace.app.ldn.utility.LDNUtils;
+import org.dspace.content.DSpaceObject;
+import org.dspace.content.Item;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Constants;
+import org.dspace.core.Context;
+import org.dspace.discovery.indexobject.IndexableLDNNotification;
+import org.dspace.event.Event;
+import org.dspace.handle.service.HandleService;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * Implementation of {@link LDNMessageService}
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science dot it)
+ */
+public class LDNMessageServiceImpl implements LDNMessageService {
+
+ @Autowired(required = true)
+ private LDNMessageDao ldnMessageDao;
+ @Autowired(required = true)
+ private NotifyServiceDao notifyServiceDao;
+ @Autowired(required = true)
+ private ConfigurationService configurationService;
+ @Autowired(required = true)
+ private HandleService handleService;
+ @Autowired(required = true)
+ private ItemService itemService;
+ @Autowired(required = true)
+ private LDNRouter ldnRouter;
+
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(LDNMessageServiceImpl.class);
+ private static final String LDN_ID_PREFIX = "urn:uuid:";
+
+ protected LDNMessageServiceImpl() {
+
+ }
+
+ @Override
+ public LDNMessageEntity find(Context context, String id) throws SQLException {
+
+ if (id == null) {
+ return null;
+ }
+
+ id = id.startsWith(LDN_ID_PREFIX) ? id : LDN_ID_PREFIX + id;
+ return ldnMessageDao.findByID(context, LDNMessageEntity.class, id);
+ }
+
+ @Override
+ public List findAll(Context context) throws SQLException {
+ return ldnMessageDao.findAll(context, LDNMessageEntity.class);
+ }
+
+ @Override
+ public LDNMessageEntity create(Context context, String id) throws SQLException {
+ LDNMessageEntity result = ldnMessageDao.findByID(context, LDNMessageEntity.class, id);
+ if (result != null) {
+ throw new SQLException("Duplicate LDN Message ID [" + id + "] detected. This message is rejected.");
+ }
+ return ldnMessageDao.create(context, new LDNMessageEntity(id));
+ }
+
+ @Override
+ public LDNMessageEntity create(Context context, Notification notification, String sourceIp) throws SQLException {
+ LDNMessageEntity ldnMessage = create(context, notification.getId());
+ DSpaceObject obj = findDspaceObjectByUrl(context, notification.getObject().getId());
+ if (obj == null) {
+ if (isTargetCurrent(notification)) {
+ // this means we're sending the notification
+ obj = findDspaceObjectByUrl(context, notification.getObject().getAsObject());
+ // use as:object for sender
+ } else {
+ // this means we're receiving the notification
+ obj = findDspaceObjectByUrl(context, notification.getObject().getAsSubject());
+ // use as:subject for receiver
+ }
+ }
+ ldnMessage.setObject(obj);
+ if (null != notification.getContext()) {
+ ldnMessage.setContext(findDspaceObjectByUrl(context, notification.getContext().getId()));
+ }
+ ldnMessage.setOrigin(findNotifyService(context, notification.getOrigin()));
+ ldnMessage.setInReplyTo(find(context, notification.getInReplyTo()));
+ ObjectMapper mapper = new ObjectMapper();
+ String message = null;
+ try {
+ message = mapper.writeValueAsString(notification);
+ ldnMessage.setMessage(message);
+ } catch (JsonProcessingException e) {
+ log.error("Notification json can't be correctly processed " +
+ "and stored inside the LDN Message Entity" + ldnMessage);
+ log.error(e);
+ }
+ ldnMessage.setType(StringUtils.joinWith(",", notification.getType()));
+ Set notificationType = notification.getType();
+ if (notificationType == null) {
+ log.error("Notification has no notificationType attribute! " + notification);
+ return null;
+ }
+ ArrayList notificationTypeArrayList = new ArrayList(notificationType);
+ // sorting the list
+ Collections.sort(notificationTypeArrayList);
+ ldnMessage.setActivityStreamType(notificationTypeArrayList.get(0));
+ if (notificationTypeArrayList.size() > 1) {
+ ldnMessage.setCoarNotifyType(notificationTypeArrayList.get(1));
+ }
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
+ ldnMessage.setSourceIp(sourceIp);
+ if (ldnMessage.getOrigin() == null && !"Offer".equalsIgnoreCase(ldnMessage.getActivityStreamType())) {
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED);
+ } else {
+
+ boolean ipCheckRangeEnabled = configurationService.getBooleanProperty("ldn.ip-range.enabled", true);
+ if (ipCheckRangeEnabled && !isValidIp(ldnMessage.getOrigin(), sourceIp)) {
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_UNTRUSTED_IP);
+ }
+ }
+ ldnMessage.setQueueTimeout(new Date());
+
+ update(context, ldnMessage);
+ return ldnMessage;
+ }
+
+ @Override
+ public boolean isValidIp(NotifyServiceEntity origin, String sourceIp) {
+
+ String lowerIp = origin.getLowerIp();
+ String upperIp = origin.getUpperIp();
+
+ try {
+ InetAddress ip = InetAddress.getByName(sourceIp);
+ InetAddress lowerBoundAddress = InetAddress.getByName(lowerIp);
+ InetAddress upperBoundAddress = InetAddress.getByName(upperIp);
+
+ long ipLong = ipToLong(ip);
+ long lowerBoundLong = ipToLong(lowerBoundAddress);
+ long upperBoundLong = ipToLong(upperBoundAddress);
+
+ return ipLong >= lowerBoundLong && ipLong <= upperBoundLong;
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+ private long ipToLong(InetAddress ip) {
+ byte[] octets = ip.getAddress();
+ long result = 0;
+ for (byte octet : octets) {
+ result <<= 8;
+ result |= octet & 0xff;
+ }
+ return result;
+ }
+
+ @Override
+ public void update(Context context, LDNMessageEntity ldnMessage) throws SQLException {
+ // move the queue_status from UNTRUSTED to QUEUED if origin is a known NotifyService
+ if (ldnMessage.getOrigin() != null &&
+ LDNMessageEntity.QUEUE_STATUS_UNTRUSTED.compareTo(ldnMessage.getQueueStatus()) == 0) {
+ ldnMessage.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
+ }
+ ldnMessageDao.save(context, ldnMessage);
+ UUID notificationUUID = UUID.fromString(ldnMessage.getID().replace(LDN_ID_PREFIX, ""));
+ ArrayList identifiersList = new ArrayList();
+ identifiersList.add(ldnMessage.getID());
+ context.addEvent(
+ new Event(Event.MODIFY, Constants.LDN_MESSAGE,
+ notificationUUID,
+ IndexableLDNNotification.TYPE, identifiersList));
+ }
+
+ private DSpaceObject findDspaceObjectByUrl(Context context, String url) throws SQLException {
+ String dspaceUrl = configurationService.getProperty("dspace.ui.url") + "/handle/";
+
+ if (StringUtils.startsWith(url, dspaceUrl)) {
+ return handleService.resolveToObject(context, url.substring(dspaceUrl.length()));
+ }
+
+ String handleResolver = configurationService.getProperty("handle.canonical.prefix", "https://hdl.handle.net/");
+ if (StringUtils.startsWith(url, handleResolver)) {
+ return handleService.resolveToObject(context, url.substring(handleResolver.length()));
+ }
+
+ dspaceUrl = configurationService.getProperty("dspace.ui.url") + "/items/";
+ if (StringUtils.startsWith(url, dspaceUrl)) {
+ return itemService.find(context, UUID.fromString(url.substring(dspaceUrl.length())));
+ }
+
+ return null;
+ }
+
+ public NotifyServiceEntity findNotifyService(Context context, Service service) throws SQLException {
+ return notifyServiceDao.findByLdnUrl(context, service.getInbox());
+ }
+
+ @Override
+ public List findOldestMessagesToProcess(Context context) throws SQLException {
+ List result = null;
+ int max_attempts = configurationService.getIntProperty("ldn.processor.max.attempts");
+ result = ldnMessageDao.findOldestMessageToProcess(context, max_attempts);
+ return result;
+ }
+
+ @Override
+ public List findMessagesToBeReprocessed(Context context) throws SQLException {
+ List result = null;
+ result = ldnMessageDao.findMessagesToBeReprocessed(context);
+ return result;
+ }
+
+ @Override
+ public List findProcessingTimedoutMessages(Context context) throws SQLException {
+ List result = null;
+ int max_attempts = configurationService.getIntProperty("ldn.processor.max.attempts");
+ result = ldnMessageDao.findProcessingTimedoutMessages(context, max_attempts);
+ return result;
+ }
+
+ @Override
+ public int extractAndProcessMessageFromQueue(Context context) throws SQLException {
+ int count = 0;
+ int timeoutInMinutes = configurationService.getIntProperty("ldn.processor.queue.msg.timeout", 60);
+
+ List messages = findOldestMessagesToProcess(context);
+ messages.addAll(findMessagesToBeReprocessed(context));
+
+ Optional msgOpt = getSingleMessageEntity(messages);
+
+ while (msgOpt.isPresent()) {
+ LDNProcessor processor = null;
+ LDNMessageEntity msg = msgOpt.get();
+ processor = ldnRouter.route(msg);
+ try {
+ boolean isServiceDisabled = !isServiceEnabled(msg);
+ if (processor == null || isServiceDisabled) {
+ log.warn("No processor found for LDN message " + msg);
+ Integer status = isServiceDisabled ? LDNMessageEntity.QUEUE_STATUS_UNTRUSTED
+ : LDNMessageEntity.QUEUE_STATUS_UNMAPPED_ACTION;
+ msg.setQueueStatus(status);
+ msg.setQueueAttempts(msg.getQueueAttempts() + 1);
+ update(context, msg);
+ } else {
+ msg.setQueueLastStartTime(new Date());
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSING);
+ msg.setQueueTimeout(DateUtils.addMinutes(new Date(), timeoutInMinutes));
+ update(context, msg);
+ ObjectMapper mapper = new ObjectMapper();
+ Notification notification = mapper.readValue(msg.getMessage(), Notification.class);
+ processor.process(context, notification);
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_PROCESSED);
+ count++;
+ }
+ } catch (JsonSyntaxException jse) {
+ log.error("Unable to read JSON notification from LdnMessage " + msg, jse);
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_FAILED);
+ } catch (Exception e) {
+ log.error(e);
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_FAILED);
+ } finally {
+ msg.setQueueAttempts(msg.getQueueAttempts() + 1);
+ update(context, msg);
+ }
+
+ messages = findOldestMessagesToProcess(context);
+ messages.addAll(findMessagesToBeReprocessed(context));
+ msgOpt = getSingleMessageEntity(messages);
+ }
+ return count;
+ }
+
+ private boolean isServiceEnabled(LDNMessageEntity msg) {
+ String localInboxUrl = configurationService.getProperty("ldn.notify.inbox");
+ if (msg.getTarget() == null || StringUtils.equals(msg.getTarget().getLdnUrl(), localInboxUrl)) {
+ return msg.getOrigin().isEnabled();
+ }
+ return msg.getTarget().isEnabled();
+ }
+
+ @Override
+ public int checkQueueMessageTimeout(Context context) throws SQLException {
+ int count = 0;
+ int maxAttempts = configurationService.getIntProperty("ldn.processor.max.attempts", 5);
+ /*
+ * put failed on processing messages with timed-out timeout and
+ * attempts >= configured_max_attempts put queue on processing messages with
+ * timed-out timeout and attempts < configured_max_attempts
+ */
+ Optional msgOpt = getSingleMessageEntity(findProcessingTimedoutMessages(context));
+
+ while (msgOpt.isPresent()) {
+ LDNMessageEntity msg = msgOpt.get();
+ try {
+ if (msg.getQueueAttempts() >= maxAttempts) {
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_FAILED);
+ } else {
+ msg.setQueueStatus(LDNMessageEntity.QUEUE_STATUS_QUEUED);
+ }
+ update(context, msg);
+ count++;
+ } catch (SQLException e) {
+ log.error("Can't update LDN message " + msg, e);
+ }
+ msgOpt = getSingleMessageEntity(findProcessingTimedoutMessages(context));
+ }
+ return count;
+ }
+
+ public Optional getSingleMessageEntity(Collection messages) {
+ return messages.stream().findFirst();
+ }
+
+ @Override
+ public NotifyRequestStatus findRequestsByItem(Context context, Item item) throws SQLException {
+ NotifyRequestStatus result = new NotifyRequestStatus();
+ result.setItemUuid(item.getID());
+ List msgs = ldnMessageDao.findAllMessagesByItem(
+ context, item, "Offer");
+ if (msgs != null && !msgs.isEmpty()) {
+ for (LDNMessageEntity msg : msgs) {
+ RequestStatus offer = new RequestStatus();
+ NotifyServiceEntity nse = msg.getOrigin();
+ if (nse == null) {
+ nse = msg.getTarget();
+ }
+ offer.setServiceName(nse == null ? "Unknown Service" : nse.getName());
+ offer.setServiceUrl(nse == null ? "" : nse.getUrl());
+ offer.setOfferType(LDNUtils.getNotifyType(msg.getCoarNotifyType()));
+ List acks = ldnMessageDao.findAllRelatedMessagesByItem(
+ context, msg, item, "Accept", "TentativeReject", "TentativeAccept", "Announce");
+ if (acks == null || acks.isEmpty()) {
+ offer.setStatus(NotifyRequestStatusEnum.REQUESTED);
+ } else if (acks.stream()
+ .filter(c -> (c.getActivityStreamType().equalsIgnoreCase("TentativeAccept") ||
+ c.getActivityStreamType().equalsIgnoreCase("Accept")))
+ .findAny().isPresent()) {
+ offer.setStatus(NotifyRequestStatusEnum.ACCEPTED);
+ } else if (acks.stream()
+ .filter(c -> c.getActivityStreamType().equalsIgnoreCase("TentativeReject"))
+ .findAny().isPresent()) {
+ offer.setStatus(NotifyRequestStatusEnum.REJECTED);
+ }
+ if (acks.stream().filter(
+ c -> c.getActivityStreamType().equalsIgnoreCase("Announce"))
+ .findAny().isEmpty()) {
+ result.addRequestStatus(offer);
+ }
+ }
+ }
+ return result;
+ }
+
+ public void delete(Context context, LDNMessageEntity ldnMessage) throws SQLException {
+ ldnMessageDao.delete(context, ldnMessage);
+ }
+
+ @Override
+ public boolean isTargetCurrent(Notification notification) {
+ String localInboxUrl = configurationService.getProperty("ldn.notify.inbox");
+ return StringUtils.equals(notification.getTarget().getInbox(), localInboxUrl);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java
new file mode 100644
index 000000000000..89ec4abe58d9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyPatternToTriggerImpl.java
@@ -0,0 +1,62 @@
+/**
+ * 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.ldn.service.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyPatternToTrigger;
+import org.dspace.app.ldn.dao.NotifyPatternToTriggerDao;
+import org.dspace.app.ldn.service.NotifyPatternToTriggerService;
+import org.dspace.content.Item;
+import org.dspace.core.Context;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation of {@link NotifyPatternToTriggerService}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyPatternToTriggerImpl implements NotifyPatternToTriggerService {
+
+ @Autowired(required = true)
+ private NotifyPatternToTriggerDao notifyPatternToTriggerDao;
+
+ @Override
+ public List findAll(Context context) throws SQLException {
+ return notifyPatternToTriggerDao.findAll(context, NotifyPatternToTrigger.class);
+ }
+
+ @Override
+ public List findByItem(Context context, Item item) throws SQLException {
+ return notifyPatternToTriggerDao.findByItem(context, item);
+ }
+
+ @Override
+ public List findByItemAndPattern(Context context, Item item, String pattern)
+ throws SQLException {
+ return notifyPatternToTriggerDao.findByItemAndPattern(context, item, pattern);
+ }
+
+ @Override
+ public NotifyPatternToTrigger create(Context context) throws SQLException {
+ NotifyPatternToTrigger notifyPatternToTrigger = new NotifyPatternToTrigger();
+ return notifyPatternToTriggerDao.create(context, notifyPatternToTrigger);
+ }
+
+ @Override
+ public void update(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException {
+ notifyPatternToTriggerDao.save(context, notifyPatternToTrigger);
+ }
+
+ @Override
+ public void delete(Context context, NotifyPatternToTrigger notifyPatternToTrigger) throws SQLException {
+ notifyPatternToTriggerDao.delete(context, notifyPatternToTrigger);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java
new file mode 100644
index 000000000000..87be008371aa
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceImpl.java
@@ -0,0 +1,67 @@
+/**
+ * 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.ldn.service.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.dao.NotifyServiceDao;
+import org.dspace.app.ldn.service.NotifyService;
+import org.dspace.core.Context;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation of {@link NotifyService}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceImpl implements NotifyService {
+
+ @Autowired(required = true)
+ private NotifyServiceDao notifyServiceDao;
+
+ @Override
+ public List findAll(Context context) throws SQLException {
+ return notifyServiceDao.findAll(context, NotifyServiceEntity.class);
+ }
+
+ @Override
+ public NotifyServiceEntity find(Context context, Integer id) throws SQLException {
+ return notifyServiceDao.findByID(context, NotifyServiceEntity.class, id);
+ }
+
+ @Override
+ public NotifyServiceEntity create(Context context, String name) throws SQLException {
+ NotifyServiceEntity notifyServiceEntity = new NotifyServiceEntity();
+ notifyServiceEntity.setName(name);
+ return notifyServiceDao.create(context, notifyServiceEntity);
+ }
+
+ @Override
+ public void update(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException {
+ notifyServiceDao.save(context, notifyServiceEntity);
+ }
+
+ @Override
+ public void delete(Context context, NotifyServiceEntity notifyServiceEntity) throws SQLException {
+ notifyServiceDao.delete(context, notifyServiceEntity);
+ }
+
+ @Override
+ public NotifyServiceEntity findByLdnUrl(Context context, String ldnUrl) throws SQLException {
+ return notifyServiceDao.findByLdnUrl(context, ldnUrl);
+ }
+
+ @Override
+ public List findManualServicesByInboundPattern(Context context, String pattern)
+ throws SQLException {
+ return notifyServiceDao.findManualServicesByInboundPattern(context, pattern);
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java
new file mode 100644
index 000000000000..c699d9fd0376
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/service/impl/NotifyServiceInboundPatternServiceImpl.java
@@ -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.ldn.service.impl;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.dspace.app.ldn.NotifyServiceEntity;
+import org.dspace.app.ldn.NotifyServiceInboundPattern;
+import org.dspace.app.ldn.dao.NotifyServiceInboundPatternDao;
+import org.dspace.app.ldn.service.NotifyServiceInboundPatternService;
+import org.dspace.core.Context;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation Service class for the {@link NotifyServiceInboundPatternService}.
+ *
+ * @author Mohamed Eskander (mohamed.eskander at 4science.com)
+ */
+public class NotifyServiceInboundPatternServiceImpl implements NotifyServiceInboundPatternService {
+
+ @Autowired
+ private NotifyServiceInboundPatternDao inboundPatternDao;
+
+ @Override
+ public NotifyServiceInboundPattern findByServiceAndPattern(Context context,
+ NotifyServiceEntity notifyServiceEntity,
+ String pattern) throws SQLException {
+ return inboundPatternDao.findByServiceAndPattern(context, notifyServiceEntity, pattern);
+ }
+
+ @Override
+ public List findAutomaticPatterns(Context context) throws SQLException {
+ return inboundPatternDao.findAutomaticPatterns(context);
+ }
+
+ @Override
+ public NotifyServiceInboundPattern create(Context context, NotifyServiceEntity notifyServiceEntity)
+ throws SQLException {
+ NotifyServiceInboundPattern inboundPattern = new NotifyServiceInboundPattern();
+ inboundPattern.setNotifyService(notifyServiceEntity);
+ return inboundPatternDao.create(context, inboundPattern);
+ }
+
+ @Override
+ public void update(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException {
+ inboundPatternDao.save(context, inboundPattern);
+ }
+
+ @Override
+ public void delete(Context context, NotifyServiceInboundPattern inboundPattern) throws SQLException {
+ inboundPatternDao.delete(context, inboundPattern);
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java b/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java
new file mode 100644
index 000000000000..949da655bc70
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/ldn/utility/LDNUtils.java
@@ -0,0 +1,96 @@
+/**
+ * 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.ldn.utility;
+
+import static org.apache.commons.lang3.StringUtils.EMPTY;
+
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Some linked data notification utilities.
+ */
+public class LDNUtils {
+
+ private final static Pattern UUID_REGEX_PATTERN = Pattern.compile(
+ "\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{12}");
+
+ private final static String SIMPLE_PROTOCOL_REGEX = "^(http[s]?://www\\.|http[s]?://|www\\.)";
+
+ /**
+ *
+ */
+ private LDNUtils() {
+
+ }
+
+ /**
+ * Whether the URL contains an UUID. Used to determine item id from item URL.
+ *
+ * @param url item URL
+ * @return boolean true if URL has UUID, false otherwise
+ */
+ public static boolean hasUUIDInURL(String url) {
+ Matcher matcher = UUID_REGEX_PATTERN.matcher(url);
+
+ return matcher.find();
+ }
+
+ /**
+ * Extract UUID from URL.
+ *
+ * @param url item URL
+ * @return UUID item id
+ */
+ public static UUID getUUIDFromURL(String url) {
+ Matcher matcher = UUID_REGEX_PATTERN.matcher(url);
+ StringBuilder handle = new StringBuilder();
+ if (matcher.find()) {
+ handle.append(matcher.group(0));
+ }
+ return UUID.fromString(handle.toString());
+ }
+
+ /**
+ * Remove http or https protocol from URL.
+ *
+ * @param url URL
+ * @return String URL without protocol
+ */
+ public static String removedProtocol(String url) {
+ return url.replaceFirst(SIMPLE_PROTOCOL_REGEX, EMPTY);
+ }
+
+ /**
+ * Custom context resolver processing. Currently converting DOI URL to DOI id.
+ *
+ * @param value context ietf:cite-as
+ * @return String ietf:cite-as identifier
+ */
+ public static String processContextResolverId(String value) {
+ String resolverId = value;
+ resolverId = resolverId.replace("https://doi.org/", "doi:");
+
+ return resolverId;
+ }
+
+ /**
+ * Clear the coarNotifyType from the source code.
+ *
+ * @param coarNotifyType coar Notify Type to sanitize
+ * @return String just the notify type
+ */
+ public static String getNotifyType(String coarNotifyType) {
+ String justNotifyType = coarNotifyType;
+ justNotifyType = justNotifyType.substring(justNotifyType.lastIndexOf(":") + 1);
+ justNotifyType = justNotifyType.replace("Action", "");
+ return justNotifyType;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java
index 408982d157e5..8ed67f4df4a0 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/ImageMagickThumbnailFilter.java
@@ -148,7 +148,7 @@ public File getImageFile(File f, boolean verbose)
// the thumbnail because the CropBox is generally used to define the
// area displayed when a user opens the PDF on a screen, whereas the
// MediaBox is used for print. Not all PDFs set these correctly, so
- // we can use ImageMagick's default behavior unless we see an explit
+ // we can use ImageMagick's default behavior unless we see an explicit
// CropBox. Note: we don't need to do anything special to detect if
// the CropBox is missing or empty because pdfbox will set it to the
// same size as the MediaBox if it doesn't exist. Also note that we
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java
index 5fbbebbb28cc..7f022f38b318 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScript.java
@@ -7,6 +7,7 @@
*/
package org.dspace.app.mediafilter;
+import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -37,8 +38,9 @@
* MFM: -v verbose outputs all extracted text to STDOUT; -f force forces all
* bitstreams to be processed, even if they have been before; -n noindex does not
* recreate index after processing bitstreams; -i [identifier] limits processing
- * scope to a community, collection or item; and -m [max] limits processing to a
- * maximum number of items.
+ * scope to a community, collection or item; -m [max] limits processing to a
+ * maximum number of items; -fd [fromdate] takes only items starting from this date,
+ * filtering by last_modified in the item table.
*/
public class MediaFilterScript extends DSpaceRunnable {
@@ -60,6 +62,7 @@ public class MediaFilterScript extends DSpaceRunnable> filterFormats = new HashMap<>();
+ private LocalDate fromDate = null;
public MediaFilterScriptConfiguration getScriptConfiguration() {
return new DSpace().getServiceManager()
@@ -112,6 +115,10 @@ public void setup() throws ParseException {
skipIds = commandLine.getOptionValues('s');
}
+ if (commandLine.hasOption('d')) {
+ fromDate = LocalDate.parse(commandLine.getOptionValue('d'));
+ }
+
}
@@ -215,6 +222,10 @@ public void internalRun() throws Exception {
mediaFilterService.setSkipList(Arrays.asList(skipIds));
}
+ if (fromDate != null) {
+ mediaFilterService.setFromDate(fromDate);
+ }
+
Context c = null;
try {
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java
index 867e684db86b..c9f61292d617 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterScriptConfiguration.java
@@ -33,7 +33,8 @@ public Options getOptions() {
options.addOption("v", "verbose", false, "print all extracted text and other details to STDOUT");
options.addOption("q", "quiet", false, "do not print anything except in the event of errors.");
options.addOption("f", "force", false, "force all bitstreams to be processed");
- options.addOption("i", "identifier", true, "ONLY process bitstreams belonging to identifier");
+ options.addOption("i", "identifier", true,
+ "ONLY process bitstreams belonging to the provided handle identifier");
options.addOption("m", "maximum", true, "process no more than maximum items");
options.addOption("h", "help", false, "help");
@@ -51,6 +52,8 @@ public Options getOptions() {
.build();
options.addOption(pluginOption);
+ options.addOption("d", "fromdate", true, "Process only item from specified last modified date");
+
Option skipOption = Option.builder("s")
.longOpt("skip")
.hasArg()
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java
index e2c6c9c5db06..512b8f803b9b 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterServiceImpl.java
@@ -9,7 +9,11 @@
import java.io.InputStream;
import java.sql.SQLException;
+import java.time.LocalDate;
+import java.time.ZoneId;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -40,6 +44,7 @@
import org.dspace.eperson.service.GroupService;
import org.dspace.scripts.handler.DSpaceRunnableHandler;
import org.dspace.services.ConfigurationService;
+import org.dspace.util.ThrowableUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
@@ -91,6 +96,7 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
protected boolean isVerbose = false;
protected boolean isQuiet = false;
protected boolean isForce = false; // default to not forced
+ protected LocalDate fromDate = null;
protected MediaFilterServiceImpl() {
@@ -118,6 +124,15 @@ public void applyFiltersAllItems(Context context) throws Exception {
for (Community topLevelCommunity : topLevelCommunities) {
applyFiltersCommunity(context, topLevelCommunity);
}
+ } else if (fromDate != null) {
+ Iterator
- itemIterator =
+ itemService.findByLastModifiedSince(
+ context,
+ Date.from(fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant())
+ );
+ while (itemIterator.hasNext() && processed < max2Process) {
+ applyFiltersItem(context, itemIterator.next());
+ }
} else {
//otherwise, just find every item and process
Iterator
- itemIterator = itemService.findAll(context);
@@ -130,12 +145,18 @@ public void applyFiltersAllItems(Context context) throws Exception {
@Override
public void applyFiltersCommunity(Context context, Community community)
throws Exception { //only apply filters if community not in skip-list
+ // ensure that the community is attached to the current hibernate session
+ // as we are committing after each item (handles, sub-communties and
+ // collections are lazy attributes)
+ community = context.reloadEntity(community);
if (!inSkipList(community.getHandle())) {
List subcommunities = community.getSubcommunities();
for (Community subcommunity : subcommunities) {
applyFiltersCommunity(context, subcommunity);
}
-
+ // ensure that the community is attached to the current hibernate session
+ // as we are committing after each item
+ community = context.reloadEntity(community);
List collections = community.getCollections();
for (Collection collection : collections) {
applyFiltersCollection(context, collection);
@@ -146,6 +167,9 @@ public void applyFiltersCommunity(Context context, Community community)
@Override
public void applyFiltersCollection(Context context, Collection collection)
throws Exception {
+ // ensure that the collection is attached to the current hibernate session
+ // as we are committing after each item (handles are lazy attributes)
+ collection = context.reloadEntity(collection);
//only apply filters if collection not in skip-list
if (!inSkipList(collection.getHandle())) {
Iterator
- itemIterator = itemService.findAllByCollection(context, collection);
@@ -169,6 +193,8 @@ public void applyFiltersItem(Context c, Item item) throws Exception {
}
// clear item objects from context cache and internal cache
c.uncacheEntity(currentItem);
+ // commit after each item to release DB resources
+ c.commit();
currentItem = null;
}
}
@@ -225,23 +251,9 @@ public boolean filterBitstream(Context context, Item myItem,
filtered = true;
}
} catch (Exception e) {
- String handle = myItem.getHandle();
- List bundles = myBitstream.getBundles();
- long size = myBitstream.getSizeBytes();
- String checksum = myBitstream.getChecksum() + " (" + myBitstream.getChecksumAlgorithm() + ")";
- int assetstore = myBitstream.getStoreNumber();
-
// Printout helpful information to find the errored bitstream.
- StringBuilder sb = new StringBuilder("ERROR filtering, skipping bitstream:\n");
- sb.append("\tItem Handle: ").append(handle);
- for (Bundle bundle : bundles) {
- sb.append("\tBundle Name: ").append(bundle.getName());
- }
- sb.append("\tFile Size: ").append(size);
- sb.append("\tChecksum: ").append(checksum);
- sb.append("\tAsset Store: ").append(assetstore);
- logError(sb.toString());
- logError(e.getMessage(), e);
+ logError(formatBitstreamDetails(myItem.getHandle(), myBitstream));
+ logError(ThrowableUtils.formatCauseChain(e));
}
} else if (filterClass instanceof SelfRegisterInputFormats) {
// Filter implements self registration, so check to see if it should be applied
@@ -319,10 +331,10 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo
// check if destination bitstream exists
Bundle existingBundle = null;
- List existingBitstreams = new ArrayList();
+ List existingBitstreams = new ArrayList<>();
List bundles = itemService.getBundles(item, formatFilter.getBundleName());
- if (bundles.size() > 0) {
+ if (!bundles.isEmpty()) {
// only finds the last matching bundle and all matching bitstreams in the proper bundle(s)
for (Bundle bundle : bundles) {
List bitstreams = bundle.getBitstreams();
@@ -337,7 +349,7 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo
}
// if exists and overwrite = false, exit
- if (!overWrite && (existingBitstreams.size() > 0)) {
+ if (!overWrite && (!existingBitstreams.isEmpty())) {
if (!isQuiet) {
logInfo("SKIPPED: bitstream " + source.getID()
+ " (item: " + item.getHandle() + ") because '" + newName + "' already exists");
@@ -370,7 +382,7 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo
}
Bundle targetBundle; // bundle we're modifying
- if (bundles.size() < 1) {
+ if (bundles.isEmpty()) {
// create new bundle if needed
targetBundle = bundleService.create(context, item, formatFilter.getBundleName());
} else {
@@ -399,6 +411,7 @@ public boolean processBitstream(Context context, Item item, Bitstream source, Fo
} catch (OutOfMemoryError oome) {
logError("!!! OutOfMemoryError !!!");
+ logError(formatBitstreamDetails(item.getHandle(), source));
}
// we are overwriting, so remove old bitstream
@@ -496,6 +509,37 @@ public boolean inSkipList(String identifier) {
}
}
+ /**
+ * Describe a Bitstream in detail. Format a single line of text with
+ * information such as Bitstore index, backing file ID, size, checksum,
+ * enclosing Item and Bundles.
+ *
+ * @param itemHandle Handle of the Item by which we found the Bitstream.
+ * @param bitstream the Bitstream to be described.
+ * @return Bitstream details.
+ */
+ private String formatBitstreamDetails(String itemHandle,
+ Bitstream bitstream) {
+ List bundles;
+ try {
+ bundles = bitstream.getBundles();
+ } catch (SQLException ex) {
+ logError("Unexpected error fetching Bundles", ex);
+ bundles = Collections.EMPTY_LIST;
+ }
+ StringBuilder sb = new StringBuilder("ERROR filtering, skipping bitstream:\n");
+ sb.append("\tItem Handle: ").append(itemHandle);
+ for (Bundle bundle : bundles) {
+ sb.append("\tBundle Name: ").append(bundle.getName());
+ }
+ sb.append("\tFile Size: ").append(bitstream.getSizeBytes());
+ sb.append("\tChecksum: ").append(bitstream.getChecksum())
+ .append(" (").append(bitstream.getChecksumAlgorithm()).append(')');
+ sb.append("\tAsset Store: ").append(bitstream.getStoreNumber());
+ sb.append("\tInternal ID: ").append(bitstream.getInternalId());
+ return sb.toString();
+ }
+
private void logInfo(String message) {
if (handler != null) {
handler.logInfo(message);
@@ -557,4 +601,9 @@ public void setFilterFormats(Map> filterFormats) {
public void setLogHandler(DSpaceRunnableHandler handler) {
this.handler = handler;
}
+
+ @Override
+ public void setFromDate(LocalDate fromDate) {
+ this.fromDate = fromDate;
+ }
}
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java
index e83bf706ed02..17e7b85e9bfc 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/TikaTextExtractionFilter.java
@@ -18,6 +18,7 @@
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.poi.util.IOUtils;
import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
@@ -72,21 +73,23 @@ public InputStream getDestinationStream(Item currentItem, InputStream source, bo
// Not using temporary file. We'll use Tika's default in-memory parsing.
// Get maximum characters to extract. Default is 100,000 chars, which is also Tika's default setting.
String extractedText;
- int maxChars = configurationService.getIntProperty("textextractor.max-chars", 100000);
+ int maxChars = configurationService.getIntProperty("textextractor.max-chars", 100_000);
try {
// Use Tika to extract text from input. Tika will automatically detect the file type.
Tika tika = new Tika();
tika.setMaxStringLength(maxChars); // Tell Tika the maximum number of characters to extract
+ IOUtils.setByteArrayMaxOverride(
+ configurationService.getIntProperty("textextractor.max-array", 100_000_000));
extractedText = tika.parseToString(source);
} catch (IOException e) {
System.err.format("Unable to extract text from bitstream in Item %s%n", currentItem.getID().toString());
- e.printStackTrace();
+ e.printStackTrace(System.err);
log.error("Unable to extract text from bitstream in Item {}", currentItem.getID().toString(), e);
throw e;
} catch (OutOfMemoryError oe) {
System.err.format("OutOfMemoryError occurred when extracting text from bitstream in Item %s. " +
"You may wish to enable 'textextractor.use-temp-file'.%n", currentItem.getID().toString());
- oe.printStackTrace();
+ oe.printStackTrace(System.err);
log.error("OutOfMemoryError occurred when extracting text from bitstream in Item {}. " +
"You may wish to enable 'textextractor.use-temp-file'.", currentItem.getID().toString(), oe);
throw oe;
diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java
index bc92ff521098..30e6dba42f08 100644
--- a/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java
+++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java
@@ -8,6 +8,7 @@
package org.dspace.app.mediafilter.service;
import java.sql.SQLException;
+import java.time.LocalDate;
import java.util.List;
import java.util.Map;
@@ -149,4 +150,6 @@ public void updatePoliciesOfDerivativeBitstreams(Context context, Item item, Bit
* @param handler
*/
public void setLogHandler(DSpaceRunnableHandler handler);
+
+ public void setFromDate(LocalDate fromDate);
}
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java
index cdefd1298c6e..bf2bfb4d60b2 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItem.java
@@ -8,19 +8,19 @@
package org.dspace.app.requestitem;
import java.util.Date;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.SequenceGenerator;
-import javax.persistence.Table;
-import javax.persistence.Temporal;
-import javax.persistence.TemporalType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.SequenceGenerator;
+import jakarta.persistence.Table;
+import jakarta.persistence.Temporal;
+import jakarta.persistence.TemporalType;
import org.dspace.content.Bitstream;
import org.dspace.content.Item;
import org.dspace.core.Context;
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java
index 6499c45a7830..c489fb4b3ff0 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/RequestItemEmailNotifier.java
@@ -11,11 +11,11 @@
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
-import javax.annotation.ManagedBean;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javax.mail.MessagingException;
+import jakarta.annotation.ManagedBean;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import jakarta.mail.MessagingException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.requestitem.service.RequestItemService;
diff --git a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java
index 008174ded88c..c76bd50d1910 100644
--- a/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java
+++ b/dspace-api/src/main/java/org/dspace/app/requestitem/dao/impl/RequestItemDAOImpl.java
@@ -9,11 +9,11 @@
import java.sql.SQLException;
import java.util.Iterator;
-import javax.persistence.Query;
-import javax.persistence.criteria.CriteriaBuilder;
-import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.Root;
+import jakarta.persistence.Query;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Root;
import org.dspace.app.requestitem.RequestItem;
import org.dspace.app.requestitem.RequestItem_;
import org.dspace.app.requestitem.dao.RequestItemDAO;
@@ -44,8 +44,12 @@ public RequestItem findByToken(Context context, String token) throws SQLExceptio
}
@Override
public Iterator findByItem(Context context, Item item) throws SQLException {
- Query query = createQuery(context, "FROM RequestItem WHERE item_id= :uuid");
- query.setParameter("uuid", item.getID());
+ CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context);
+ CriteriaQuery criteriaQuery = getCriteriaQuery(criteriaBuilder, RequestItem.class);
+ Root requestItemRoot = criteriaQuery.from(RequestItem.class);
+ criteriaQuery.select(requestItemRoot);
+ criteriaQuery.where(criteriaBuilder.equal(requestItemRoot.get(RequestItem_.item), item));
+ Query query = createQuery(context, criteriaQuery);
return iterate(query);
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java
index ead725e842c4..943df5f2a0e8 100644
--- a/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java
+++ b/dspace-api/src/main/java/org/dspace/app/sherpa/SHERPAService.java
@@ -12,8 +12,8 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
-import javax.annotation.PostConstruct;
+import jakarta.annotation.PostConstruct;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
@@ -78,7 +78,7 @@ public SHERPAService() {
@SuppressWarnings("unused")
@PostConstruct
private void init() {
- // Get endoint and API key from configuration
+ // Get endpoint and API key from configuration
endpoint = configurationService.getProperty("sherpa.romeo.url",
"https://v2.sherpa.ac.uk/cgi/retrieve");
apiKey = configurationService.getProperty("sherpa.romeo.apikey");
@@ -93,7 +93,7 @@ private void init() {
* @param query ISSN string to pass in an "issn equals" API query
* @return SHERPAResponse containing an error or journal policies
*/
- @Cacheable(key = "#query", cacheNames = "sherpa.searchByJournalISSN")
+ @Cacheable(key = "#query", condition = "#query != null", cacheNames = "sherpa.searchByJournalISSN")
public SHERPAResponse searchByJournalISSN(String query) {
return performRequest("publication", "issn", "equals", query, 0, 1);
}
@@ -156,7 +156,7 @@ public SHERPAPublisherResponse performPublisherRequest(String type, String field
// If the response body is valid, pass to SHERPAResponse for parsing as JSON
if (null != responseBody) {
- log.debug("Non-null SHERPA resonse received for query of " + value);
+ log.debug("Non-null SHERPA response received for query of " + value);
InputStream content = null;
try {
content = responseBody.getContent();
@@ -259,7 +259,7 @@ public SHERPAResponse performRequest(String type, String field, String predicate
// If the response body is valid, pass to SHERPAResponse for parsing as JSON
if (null != responseBody) {
- log.debug("Non-null SHERPA resonse received for query of " + value);
+ log.debug("Non-null SHERPA response received for query of " + value);
InputStream content = null;
try {
content = responseBody.getContent();
diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/cache/SherpaCacheLogger.java b/dspace-api/src/main/java/org/dspace/app/sherpa/cache/SherpaCacheLogger.java
index e84fb7775ae2..5bdf1efa2632 100644
--- a/dspace-api/src/main/java/org/dspace/app/sherpa/cache/SherpaCacheLogger.java
+++ b/dspace-api/src/main/java/org/dspace/app/sherpa/cache/SherpaCacheLogger.java
@@ -13,7 +13,7 @@
import org.ehcache.event.CacheEventListener;
/**
- * This is a EHCache listner responsible for logging sherpa cache events. It is
+ * This is a EHCache listener responsible for logging sherpa cache events. It is
* bound to the sherpa cache via the dspace/config/ehcache.xml file. We need a
* dedicated Logger for each cache as the CacheEvent doesn't include details
* about where the event occur
diff --git a/dspace-api/src/main/java/org/dspace/app/sherpa/submit/SHERPASubmitService.java b/dspace-api/src/main/java/org/dspace/app/sherpa/submit/SHERPASubmitService.java
index b795c8a2b2d2..cb913a9f261e 100644
--- a/dspace-api/src/main/java/org/dspace/app/sherpa/submit/SHERPASubmitService.java
+++ b/dspace-api/src/main/java/org/dspace/app/sherpa/submit/SHERPASubmitService.java
@@ -47,7 +47,7 @@ public void setConfiguration(SHERPASubmitConfigurationService configuration) {
}
/**
- * Setter for SHERPA service, reponsible for actual HTTP API calls
+ * Setter for SHERPA service, responsible for actual HTTP API calls
* @see "dspace-dspace-addon-sherpa-configuration-services.xml"
* @param sherpaService
*/
diff --git a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java
index d65447d311ee..2464221d2df2 100644
--- a/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java
+++ b/dspace-api/src/main/java/org/dspace/app/sitemap/GenerateSitemaps.java
@@ -7,18 +7,10 @@
*/
package org.dspace.app.sitemap;
-import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.Date;
-import java.util.Iterator;
import java.util.List;
import org.apache.commons.cli.CommandLine;
@@ -29,12 +21,8 @@
import org.apache.commons.cli.ParseException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
-import org.dspace.content.Collection;
-import org.dspace.content.Community;
-import org.dspace.content.Item;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
@@ -43,6 +31,7 @@
import org.dspace.core.LogHelper;
import org.dspace.discovery.DiscoverQuery;
import org.dspace.discovery.DiscoverResult;
+import org.dspace.discovery.IndexableObject;
import org.dspace.discovery.SearchService;
import org.dspace.discovery.SearchServiceException;
import org.dspace.discovery.SearchUtils;
@@ -68,6 +57,7 @@ public class GenerateSitemaps {
private static final ConfigurationService configurationService =
DSpaceServicesFactory.getInstance().getConfigurationService();
private static final SearchService searchService = SearchUtils.getSearchService();
+ private static final int PAGE_SIZE = 100;
/**
* Default constructor
@@ -87,11 +77,6 @@ public static void main(String[] args) throws Exception {
"do not generate sitemaps.org protocol sitemap");
options.addOption("b", "no_htmlmap", false,
"do not generate a basic HTML sitemap");
- options.addOption("a", "ping_all", false,
- "ping configured search engines");
- options
- .addOption("p", "ping", true,
- "ping specified search engine URL");
options
.addOption("d", "delete", false,
"delete sitemaps dir and its contents");
@@ -116,14 +101,13 @@ public static void main(String[] args) throws Exception {
}
/*
- * Sanity check -- if no sitemap generation or pinging to do, or deletion, print usage
+ * Sanity check -- if no sitemap generation or deletion, print usage
*/
if (line.getArgs().length != 0 || line.hasOption('d') || line.hasOption('b')
&& line.hasOption('s') && !line.hasOption('g')
- && !line.hasOption('m') && !line.hasOption('y')
- && !line.hasOption('p')) {
+ && !line.hasOption('m') && !line.hasOption('y')) {
System.err
- .println("Nothing to do (no sitemap to generate, no search engines to ping)");
+ .println("Nothing to do (no sitemap to generate)");
hf.printHelp(usage, options);
System.exit(1);
}
@@ -137,20 +121,6 @@ public static void main(String[] args) throws Exception {
deleteSitemaps();
}
- if (line.hasOption('a')) {
- pingConfiguredSearchEngines();
- }
-
- if (line.hasOption('p')) {
- try {
- pingSearchEngine(line.getOptionValue('p'));
- } catch (MalformedURLException me) {
- System.err
- .println("Bad search engine URL (include all except sitemap URL)");
- System.exit(1);
- }
- }
-
System.exit(0);
}
@@ -171,7 +141,7 @@ public static void generateSitemapsScheduled() throws IOException, SQLException
public static void deleteSitemaps() throws IOException {
File outputDir = new File(configurationService.getProperty("sitemap.dir"));
if (!outputDir.exists() && !outputDir.isDirectory()) {
- log.error("Unable to delete sitemaps directory, doesn't exist or isn't a directort");
+ log.error("Unable to delete sitemaps directory, doesn't exist or isn't a directory");
} else {
FileUtils.deleteDirectory(outputDir);
}
@@ -189,7 +159,10 @@ public static void deleteSitemaps() throws IOException {
*/
public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg) throws SQLException, IOException {
String uiURLStem = configurationService.getProperty("dspace.ui.url");
- String sitemapStem = uiURLStem + "/sitemap";
+ if (!uiURLStem.endsWith("/")) {
+ uiURLStem = uiURLStem + '/';
+ }
+ String sitemapStem = uiURLStem + "sitemap";
File outputDir = new File(configurationService.getProperty("sitemap.dir"));
if (!outputDir.exists() && !outputDir.mkdir()) {
@@ -208,171 +181,113 @@ public static void generateSitemaps(boolean makeHTMLMap, boolean makeSitemapOrg)
}
Context c = new Context(Context.Mode.READ_ONLY);
+ int offset = 0;
+ long commsCount = 0;
+ long collsCount = 0;
+ long itemsCount = 0;
- List comms = communityService.findAll(c);
-
- for (Community comm : comms) {
- String url = uiURLStem + "/communities/" + comm.getID();
-
- if (makeHTMLMap) {
- html.addURL(url, null);
- }
- if (makeSitemapOrg) {
- sitemapsOrg.addURL(url, null);
- }
-
- c.uncacheEntity(comm);
- }
-
- List colls = collectionService.findAll(c);
-
- for (Collection coll : colls) {
- String url = uiURLStem + "/collections/" + coll.getID();
-
- if (makeHTMLMap) {
- html.addURL(url, null);
- }
- if (makeSitemapOrg) {
- sitemapsOrg.addURL(url, null);
- }
-
- c.uncacheEntity(coll);
- }
-
- Iterator
- allItems = itemService.findAll(c);
- int itemCount = 0;
-
- while (allItems.hasNext()) {
- Item i = allItems.next();
-
- DiscoverQuery entityQuery = new DiscoverQuery();
- entityQuery.setQuery("search.uniqueid:\"Item-" + i.getID() + "\" and entityType:*");
- entityQuery.addSearchField("entityType");
-
- try {
- DiscoverResult discoverResult = searchService.search(c, entityQuery);
-
- String url;
- if (CollectionUtils.isNotEmpty(discoverResult.getIndexableObjects())
- && CollectionUtils.isNotEmpty(discoverResult.getSearchDocument(
- discoverResult.getIndexableObjects().get(0)).get(0).getSearchFieldValues("entityType"))
- && StringUtils.isNotBlank(discoverResult.getSearchDocument(
- discoverResult.getIndexableObjects().get(0)).get(0).getSearchFieldValues("entityType").get(0))
- ) {
- url = uiURLStem + "/entities/" + StringUtils.lowerCase(discoverResult.getSearchDocument(
- discoverResult.getIndexableObjects().get(0))
- .get(0).getSearchFieldValues("entityType").get(0)) + "/" + i.getID();
- } else {
- url = uiURLStem + "/items/" + i.getID();
+ try {
+ DiscoverQuery discoveryQuery = new DiscoverQuery();
+ discoveryQuery.setMaxResults(PAGE_SIZE);
+ discoveryQuery.setQuery("search.resourcetype:Community");
+ do {
+ discoveryQuery.setStart(offset);
+ DiscoverResult discoverResult = searchService.search(c, discoveryQuery);
+ List docs = discoverResult.getIndexableObjects();
+ commsCount = discoverResult.getTotalSearchResults();
+
+ for (IndexableObject doc : docs) {
+ String url = uiURLStem + "communities/" + doc.getID();
+ c.uncacheEntity(doc.getIndexedObject());
+
+ if (makeHTMLMap) {
+ html.addURL(url, null);
+ }
+ if (makeSitemapOrg) {
+ sitemapsOrg.addURL(url, null);
+ }
}
- Date lastMod = i.getLastModified();
-
- if (makeHTMLMap) {
- html.addURL(url, lastMod);
+ offset += PAGE_SIZE;
+ } while (offset < commsCount);
+
+ offset = 0;
+ discoveryQuery = new DiscoverQuery();
+ discoveryQuery.setMaxResults(PAGE_SIZE);
+ discoveryQuery.setQuery("search.resourcetype:Collection");
+ do {
+ discoveryQuery.setStart(offset);
+ DiscoverResult discoverResult = searchService.search(c, discoveryQuery);
+ List docs = discoverResult.getIndexableObjects();
+ collsCount = discoverResult.getTotalSearchResults();
+
+ for (IndexableObject doc : docs) {
+ String url = uiURLStem + "collections/" + doc.getID();
+ c.uncacheEntity(doc.getIndexedObject());
+
+ if (makeHTMLMap) {
+ html.addURL(url, null);
+ }
+ if (makeSitemapOrg) {
+ sitemapsOrg.addURL(url, null);
+ }
}
- if (makeSitemapOrg) {
- sitemapsOrg.addURL(url, lastMod);
+ offset += PAGE_SIZE;
+ } while (offset < collsCount);
+
+ offset = 0;
+ discoveryQuery = new DiscoverQuery();
+ discoveryQuery.setMaxResults(PAGE_SIZE);
+ discoveryQuery.setQuery("search.resourcetype:Item");
+ discoveryQuery.addSearchField("search.entitytype");
+ do {
+
+ discoveryQuery.setStart(offset);
+ DiscoverResult discoverResult = searchService.search(c, discoveryQuery);
+ List docs = discoverResult.getIndexableObjects();
+ itemsCount = discoverResult.getTotalSearchResults();
+
+ for (IndexableObject doc : docs) {
+ String url;
+ List entityTypeFieldValues = discoverResult.getSearchDocument(doc).get(0)
+ .getSearchFieldValues("search.entitytype");
+ if (CollectionUtils.isNotEmpty(entityTypeFieldValues)) {
+ url = uiURLStem + "entities/" + StringUtils.lowerCase(entityTypeFieldValues.get(0)) + "/"
+ + doc.getID();
+ } else {
+ url = uiURLStem + "items/" + doc.getID();
+ }
+ Date lastMod = doc.getLastModified();
+ c.uncacheEntity(doc.getIndexedObject());
+
+ if (makeHTMLMap) {
+ html.addURL(url, null);
+ }
+ if (makeSitemapOrg) {
+ sitemapsOrg.addURL(url, null);
+ }
}
- } catch (SearchServiceException e) {
- log.error("Failed getting entitytype through solr for item " + i.getID() + ": " + e.getMessage());
- }
-
- c.uncacheEntity(i);
-
- itemCount++;
- }
-
- if (makeHTMLMap) {
- int files = html.finish();
- log.info(LogHelper.getHeader(c, "write_sitemap",
- "type=html,num_files=" + files + ",communities="
- + comms.size() + ",collections=" + colls.size()
- + ",items=" + itemCount));
- }
-
- if (makeSitemapOrg) {
- int files = sitemapsOrg.finish();
- log.info(LogHelper.getHeader(c, "write_sitemap",
- "type=html,num_files=" + files + ",communities="
- + comms.size() + ",collections=" + colls.size()
- + ",items=" + itemCount));
- }
-
- c.abort();
- }
-
- /**
- * Ping all search engines configured in {@code dspace.cfg}.
- *
- * @throws UnsupportedEncodingException theoretically should never happen
- */
- public static void pingConfiguredSearchEngines()
- throws UnsupportedEncodingException {
- String[] engineURLs = configurationService
- .getArrayProperty("sitemap.engineurls");
-
- if (ArrayUtils.isEmpty(engineURLs)) {
- log.warn("No search engine URLs configured to ping");
- return;
- }
-
- for (int i = 0; i < engineURLs.length; i++) {
- try {
- pingSearchEngine(engineURLs[i]);
- } catch (MalformedURLException me) {
- log.warn("Bad search engine URL in configuration: "
- + engineURLs[i]);
- }
- }
- }
-
- /**
- * Ping the given search engine.
- *
- * @param engineURL Search engine URL minus protocol etc, e.g.
- * {@code www.google.com}
- * @throws MalformedURLException if the passed in URL is malformed
- * @throws UnsupportedEncodingException theoretically should never happen
- */
- public static void pingSearchEngine(String engineURL)
- throws MalformedURLException, UnsupportedEncodingException {
- // Set up HTTP proxy
- if ((StringUtils.isNotBlank(configurationService.getProperty("http.proxy.host")))
- && (StringUtils.isNotBlank(configurationService.getProperty("http.proxy.port")))) {
- System.setProperty("proxySet", "true");
- System.setProperty("proxyHost", configurationService
- .getProperty("http.proxy.host"));
- System.getProperty("proxyPort", configurationService
- .getProperty("http.proxy.port"));
- }
+ offset += PAGE_SIZE;
+ } while (offset < itemsCount);
- String sitemapURL = configurationService.getProperty("dspace.ui.url")
- + "/sitemap";
-
- URL url = new URL(engineURL + URLEncoder.encode(sitemapURL, "UTF-8"));
-
- try {
- HttpURLConnection connection = (HttpURLConnection) url
- .openConnection();
-
- BufferedReader in = new BufferedReader(new InputStreamReader(
- connection.getInputStream()));
-
- String inputLine;
- StringBuffer resp = new StringBuffer();
- while ((inputLine = in.readLine()) != null) {
- resp.append(inputLine).append("\n");
+ if (makeHTMLMap) {
+ int files = html.finish();
+ log.info(LogHelper.getHeader(c, "write_sitemap",
+ "type=html,num_files=" + files + ",communities="
+ + commsCount + ",collections=" + collsCount
+ + ",items=" + itemsCount));
}
- in.close();
- if (connection.getResponseCode() == 200) {
- log.info("Pinged " + url.toString() + " successfully");
- } else {
- log.warn("Error response pinging " + url.toString() + ":\n"
- + resp);
+ if (makeSitemapOrg) {
+ int files = sitemapsOrg.finish();
+ log.info(LogHelper.getHeader(c, "write_sitemap",
+ "type=html,num_files=" + files + ",communities="
+ + commsCount + ",collections=" + collsCount
+ + ",items=" + itemsCount));
}
- } catch (IOException e) {
- log.warn("Error pinging " + url.toString(), e);
+ } catch (SearchServiceException e) {
+ throw new RuntimeException(e);
+ } finally {
+ c.abort();
}
}
}
diff --git a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java
index f901c9ca569e..aac42ce1acf9 100644
--- a/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java
+++ b/dspace-api/src/main/java/org/dspace/app/solrdatabaseresync/SolrDatabaseResyncCli.java
@@ -105,35 +105,17 @@ private void performStatusUpdate(Context context) throws SearchServiceException,
solrQuery.addFilterQuery(dateRangeFilter);
solrQuery.addField(SearchUtils.RESOURCE_ID_FIELD);
solrQuery.addField(SearchUtils.RESOURCE_UNIQUE_ID);
+ solrQuery.setRows(0);
QueryResponse response = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD);
-
- if (response != null) {
- logInfoAndOut(response.getResults().size() + " items found to process");
-
- for (SolrDocument doc : response.getResults()) {
- String uuid = (String) doc.getFirstValue(SearchUtils.RESOURCE_ID_FIELD);
- String uniqueId = (String) doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID);
- logDebugAndOut("Processing item with UUID: " + uuid);
-
- Optional indexableObject = Optional.empty();
- try {
- indexableObject = indexObjectServiceFactory
- .getIndexableObjectFactory(uniqueId).findIndexableObject(context, uuid);
- } catch (SQLException e) {
- log.warn("An exception occurred when attempting to retrieve item with UUID \"" + uuid +
- "\" from the database, removing related solr document", e);
- }
-
- try {
- if (indexableObject.isPresent()) {
- logDebugAndOut("Item exists in DB, updating solr document");
- updateItem(context, indexableObject.get());
- } else {
- logDebugAndOut("Item doesn't exist in DB, removing solr document");
- removeItem(context, uniqueId);
- }
- } catch (SQLException | IOException e) {
- log.error(e.getMessage(), e);
+ if (response != null && response.getResults() != null) {
+ long nrOfPreDBResults = response.getResults().getNumFound();
+ if (nrOfPreDBResults > 0) {
+ logInfoAndOut(nrOfPreDBResults + " items found to process");
+ int batchSize = configurationService.getIntProperty("script.solr-database-resync.batch-size", 100);
+ for (int start = 0; start < nrOfPreDBResults; start += batchSize) {
+ solrQuery.setStart(start);
+ solrQuery.setRows(batchSize);
+ performStatusUpdateOnNextBatch(context, solrQuery);
}
}
}
@@ -141,6 +123,38 @@ private void performStatusUpdate(Context context) throws SearchServiceException,
indexingService.commit();
}
+ private void performStatusUpdateOnNextBatch(Context context, SolrQuery solrQuery)
+ throws SolrServerException, IOException {
+ QueryResponse response = solrSearchCore.getSolr().query(solrQuery, solrSearchCore.REQUEST_METHOD);
+
+ for (SolrDocument doc : response.getResults()) {
+ String uuid = (String) doc.getFirstValue(SearchUtils.RESOURCE_ID_FIELD);
+ String uniqueId = (String) doc.getFirstValue(SearchUtils.RESOURCE_UNIQUE_ID);
+ logDebugAndOut("Processing item with UUID: " + uuid);
+
+ Optional indexableObject = Optional.empty();
+ try {
+ indexableObject = indexObjectServiceFactory
+ .getIndexableObjectFactory(uniqueId).findIndexableObject(context, uuid);
+ } catch (SQLException e) {
+ log.warn("An exception occurred when attempting to retrieve item with UUID \"" + uuid +
+ "\" from the database, removing related solr document", e);
+ }
+
+ try {
+ if (indexableObject.isPresent()) {
+ logDebugAndOut("Item exists in DB, updating solr document");
+ updateItem(context, indexableObject.get());
+ } else {
+ logDebugAndOut("Item doesn't exist in DB, removing solr document");
+ removeItem(context, uniqueId);
+ }
+ } catch (SQLException | IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+ }
+
private void updateItem(Context context, IndexableObject indexableObject) throws SolrServerException, IOException {
Map fieldModifier = new HashMap<>(1);
fieldModifier.put("remove", STATUS_FIELD_PREDB);
diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/HTMLReport.java b/dspace-api/src/main/java/org/dspace/app/statistics/HTMLReport.java
index 3d76ecaecfdc..9e27ffdd9334 100644
--- a/dspace-api/src/main/java/org/dspace/app/statistics/HTMLReport.java
+++ b/dspace-api/src/main/java/org/dspace/app/statistics/HTMLReport.java
@@ -451,7 +451,7 @@ public String footer() {
}
/**
- * Clean Stirngs for display in HTML
+ * Clean Strings for display in HTML
*
* @param s The String to clean
* @return The cleaned String
diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java b/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java
index 2e4ed69b268e..2408a693b6fd 100644
--- a/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java
+++ b/dspace-api/src/main/java/org/dspace/app/statistics/LogAnalyser.java
@@ -281,10 +281,14 @@ public class LogAnalyser {
*/
private static String fileTemplate = "dspace\\.log.*";
+ private static final ConfigurationService configurationService =
+ DSpaceServicesFactory.getInstance().getConfigurationService();
+
/**
* the configuration file from which to configure the analyser
*/
- private static String configFile;
+ private static String configFile = configurationService.getProperty("dspace.dir")
+ + File.separator + "config" + File.separator + "dstat.cfg";
/**
* the output file to which to write aggregation data
@@ -481,7 +485,7 @@ public static String processLogs(Context context, String myLogDir,
// of the log file are sequential, but can we assume the files are
// provided in a data sequence?
for (i = 0; i < logFiles.length; i++) {
- // check to see if this file is a log file agains the global regex
+ // check to see if this file is a log file against the global regex
Matcher matchRegex = logRegex.matcher(logFiles[i].getName());
if (matchRegex.matches()) {
// if it is a log file, open it up and lets have a look at the
@@ -616,8 +620,6 @@ public static String processLogs(Context context, String myLogDir,
}
// now do the host name and url lookup
- ConfigurationService configurationService
- = DSpaceServicesFactory.getInstance().getConfigurationService();
hostName = Utils.getHostName(configurationService.getProperty("dspace.ui.url"));
name = configurationService.getProperty("dspace.name").trim();
url = configurationService.getProperty("dspace.ui.url").trim();
@@ -658,8 +660,6 @@ public static void setParameters(String myLogDir, String myFileTemplate,
String myConfigFile, String myOutFile,
Date myStartDate, Date myEndDate,
boolean myLookUp) {
- ConfigurationService configurationService
- = DSpaceServicesFactory.getInstance().getConfigurationService();
if (myLogDir != null) {
logDir = myLogDir;
@@ -673,9 +673,6 @@ public static void setParameters(String myLogDir, String myFileTemplate,
if (myConfigFile != null) {
configFile = myConfigFile;
- } else {
- configFile = configurationService.getProperty("dspace.dir")
- + File.separator + "config" + File.separator + "dstat.cfg";
}
if (myStartDate != null) {
diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java b/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java
index c5fe0072f514..5b526773d48a 100644
--- a/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java
+++ b/dspace-api/src/main/java/org/dspace/app/statistics/ReportGenerator.java
@@ -352,7 +352,7 @@ public static void processReport(Context context, Report report,
report.setEndDate(endDate);
report.setMainTitle(name, serverName);
- // define our standard variables for re-use
+ // define our standard variables for reuse
// FIXME: we probably don't need these once we've finished re-factoring
Iterator keys = null;
int i = 0;
@@ -518,7 +518,7 @@ public static void processReport(Context context, Report report,
/**
* a standard stats block preparation method for use when an aggregator
- * has to be put out in its entirity. This method will not be able to
+ * has to be put out in its entirety. This method will not be able to
* deal with complex cases, although it will perform sorting by value and
* translations as per the map file if requested
*
@@ -783,7 +783,7 @@ public static String getItemInfo(Context context, String handle)
return null;
}
- // build the referece
+ // build the reference
// FIXME: here we have blurred the line between content and presentation
// and it should probably be un-blurred
List title = itemService.getMetadata(item, MetadataSchemaEnum.DC.getName(),
diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/StatisticsLoader.java b/dspace-api/src/main/java/org/dspace/app/statistics/StatisticsLoader.java
index cc8a7024f1b2..23dbe19b61a6 100644
--- a/dspace-api/src/main/java/org/dspace/app/statistics/StatisticsLoader.java
+++ b/dspace-api/src/main/java/org/dspace/app/statistics/StatisticsLoader.java
@@ -291,7 +291,7 @@ private static synchronized void loadFileList(File[] fileList) {
* by the formatter provided, then we return null.
*
* @param thisFile file
- * @param thisPattern patter
+ * @param thisPattern pattern
* @param sdf date format
* @return StatsFile
*/
diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/package.html b/dspace-api/src/main/java/org/dspace/app/statistics/package.html
index a6d8d8699cf7..931a7039080d 100644
--- a/dspace-api/src/main/java/org/dspace/app/statistics/package.html
+++ b/dspace-api/src/main/java/org/dspace/app/statistics/package.html
@@ -46,8 +46,6 @@
writes event records to the Java logger.
{@link org.dspace.statistics.SolrLoggerUsageEventListener SolrLoggerUsageEventListener}
writes event records to Solr.
- {@link org.dspace.google.GoogleRecorderEventListener GoogleRecorderEventListener}<.dt>
- writes event records to Google Analytics.