diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 9893d233e1..8f061cab88 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 34cc2c9e4f..9eaa4d9f3f 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 0000000000..b6412b25b6
--- /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/pull_request_template.md b/.github/pull_request_template.md
index 5b3f4336e6..4da35876d2 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 8b3cb1d017..39a6f41429 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
diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml
index 1e3d835e27..3a563c6fa3 100644
--- a/.github/workflows/codescan.yml
+++ b/.github/workflows/codescan.yml
@@ -41,7 +41,7 @@ jobs:
- name: Install JDK
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/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml
index 687474caed..7a8de661fa 100644
--- a/.github/workflows/reusable-docker-build.yml
+++ b/.github/workflows/reusable-docker-build.yml
@@ -68,9 +68,9 @@ env:
# See "Redeploy" steps below for more details.
REDEPLOY_SANDBOX_URL: ${{ secrets.REDEPLOY_SANDBOX_URL }}
REDEPLOY_DEMO_URL: ${{ secrets.REDEPLOY_DEMO_URL }}
- # Current DSpace maintenance branch (and architecture) which is deployed to demo.dspace.org / sandbox.dspace.org
- # (NOTE: No deployment branch specified for sandbox.dspace.org as it uses the default_branch)
- DEPLOY_DEMO_BRANCH: 'dspace-7_x'
+ # 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'
jobs:
@@ -174,7 +174,7 @@ jobs:
!matrix.isPR &&
env.REDEPLOY_SANDBOX_URL != '' &&
matrix.arch == env.DEPLOY_ARCH &&
- github.ref_name == github.event.repository.default_branch
+ github.ref_name == env.DEPLOY_SANDBOX_BRANCH
run: |
curl -X POST $REDEPLOY_SANDBOX_URL
diff --git a/.gitignore b/.gitignore
index 2fcb46b993..529351edc5 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 132de8a6de..0000000000
--- 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 45a6af9ce5..657e11eee7 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 ee48dec508..7581798037 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,15 @@
# 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
+ARG DSPACE_VERSION=latest
# Step 1 - Run Maven Build
-FROM dspace/dspace-dependencies:dspace-7_x as build
+FROM dspace/dspace-dependencies:${DSPACE_VERSION} AS build
ARG TARGET_DIR=dspace-installer
WORKDIR /app
# The dspace-installer directory will be written to /install
@@ -18,7 +19,7 @@ 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
# 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.
@@ -26,9 +27,11 @@ ENV MAVEN_FLAGS="-P-test-environment -Denforcer.skip=true -Dcheckstyle.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 eclipse-temurin:${JDK_VERSION} as ant_build
+FROM 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
@@ -48,23 +51,16 @@ RUN mkdir $ANT_HOME && \
# 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 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
-# Expose Tomcat port and AJP port
-EXPOSE 8080 8009
+WORKDIR $DSPACE_INSTALL
+# Expose Tomcat port
+EXPOSE 8080
# 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 53040a2fad..5254d1eb4d 100644
--- a/Dockerfile.cli
+++ b/Dockerfile.cli
@@ -1,14 +1,15 @@
# 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
+ARG DSPACE_VERSION=latest
# Step 1 - Run Maven Build
-FROM dspace/dspace-dependencies:dspace-7_x as build
+FROM dspace/dspace-dependencies:${DSPACE_VERSION} AS build
ARG TARGET_DIR=dspace-installer
WORKDIR /app
# The dspace-installer directory will be written to /install
@@ -24,7 +25,7 @@ RUN mvn --no-transfer-progress package && \
mvn clean
# Step 2 - Run Ant Deploy
-FROM eclipse-temurin:${JDK_VERSION} as ant_build
+FROM 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
diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies
index 1400b356d4..f3bf1f8332 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-eclipse-temurin-${JDK_VERSION} as build
+FROM maven:3-eclipse-temurin-${JDK_VERSION} AS build
ARG TARGET_DIR=dspace-installer
WORKDIR /app
# Create the 'dspace' user account & home directory
diff --git a/Dockerfile.test b/Dockerfile.test
index f6f8c1a290..f3acef00e8 100644
--- a/Dockerfile.test
+++ b/Dockerfile.test
@@ -1,16 +1,17 @@
# 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
+ARG DSPACE_VERSION=latest
# Step 1 - Run Maven Build
-FROM dspace/dspace-dependencies:dspace-7_x as build
+FROM dspace/dspace-dependencies:${DSPACE_VERSION} AS build
ARG TARGET_DIR=dspace-installer
WORKDIR /app
# The dspace-installer directory will be written to /install
@@ -20,14 +21,16 @@ 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 eclipse-temurin:${JDK_VERSION} as ant_build
+FROM 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
@@ -47,36 +50,18 @@ RUN mkdir $ANT_HOME && \
# 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 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
+# 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 e494c80c5d..d7e928147c 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 af9158eff3..1d93abe499 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 e0fa808d83..a33fc48319 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 d6a194617e..91f89916d2 100644
--- a/docker-compose-cli.yml
+++ b/docker-compose-cli.yml
@@ -6,7 +6,7 @@ networks:
external: true
services:
dspace-cli:
- image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
+ image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}"
container_name: dspace-cli
build:
context: .
diff --git a/docker-compose.yml b/docker-compose.yml
index 23fce37db2..6a930a8d31 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -28,7 +28,7 @@ services:
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
proxies__P__trusted__P__ipranges: '172.23.0'
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
- image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}"
+ image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
build:
context: .
dockerfile: Dockerfile.test
@@ -39,8 +39,6 @@ services:
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_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,7 +84,7 @@ services:
# DSpace Solr container
dspacesolr:
container_name: dspacesolr
- image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}"
+ image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
build:
context: ./dspace/src/main/docker/dspace-solr/
# Provide path to Solr configs necessary to build Docker image
@@ -122,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 4c881cbd24..a80f27bc22 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -12,7 +12,7 @@
org.dspace
dspace-parent
- 7.6.2-SNAPSHOT
+ 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.4.0
+ 3.6.0
validate
@@ -116,7 +116,7 @@
org.codehaus.mojo
buildnumber-maven-plugin
- 3.2.0
+ 3.2.1
UNKNOWN_REVISION
@@ -177,7 +177,7 @@
org.codehaus.mojo
jaxb2-maven-plugin
- 2.5.0
+ 3.2.0
workflow-curation
@@ -265,7 +265,7 @@
- ${agnostic.build.dir}/testing/dspace/
+ ${agnostic.build.dir}/testing/dspace
true
${agnostic.build.dir}/testing/dspace/solr/
@@ -324,7 +324,7 @@
- ${agnostic.build.dir}/testing/dspace/
+ ${agnostic.build.dir}/testing/dspace
true
${agnostic.build.dir}/testing/dspace/solr/
@@ -342,18 +342,15 @@
log4j-api
- org.hibernate
+ org.hibernate.orm
hibernate-core
-
-
-
- org.javassist
- javassist
-
-
- org.hibernate
+ org.hibernate.orm
+ hibernate-jpamodelgen
+
+
+ org.hibernate.orm
hibernate-jcache
@@ -375,23 +372,18 @@
- 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
@@ -402,30 +394,26 @@
net.handle
handle
+
net.cnri
cnri-servlet-container
+ runtime
-
+
- org.ow2.asm
- asm-commons
-
-
-
- org.bouncycastle
- bcpkix-jdk15on
-
-
- org.bouncycastle
- bcprov-jdk15on
+ org.mortbay.jasper
+ apache-jsp
-
+
+
org.eclipse.jetty
jetty-server
+ runtime
org.dspace
@@ -435,12 +423,6 @@
org.apache.jena
apache-jena-libs
pom
-
-
- log4j
- log4j
-
-
commons-cli
@@ -458,10 +440,6 @@
org.apache.commons
commons-dbcp2
-
- commons-fileupload
- commons-fileupload
-
commons-io
@@ -480,17 +458,26 @@
commons-validator
- com.sun.mail
- javax.mail
-
-
- javax.servlet
- javax.servlet-api
+ jakarta.mail
+ jakarta.mail-api
provided
- javax.annotation
- javax.annotation-api
+ org.eclipse.angus
+ jakarta.mail
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ provided
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+ jakarta.el
+ jakarta.el-api
jaxen
@@ -586,6 +573,13 @@
solr-core
test
${solr.client.version}
+
+
+
+ org.antlr
+ antlr4-runtime
+
+
org.apache.lucene
@@ -629,7 +623,7 @@
dnsjava
dnsjava
- 2.1.9
+ 3.6.0
@@ -665,7 +659,12 @@
org.flywaydb
flyway-core
- 8.5.13
+ ${flyway.version}
+
+
+ org.flywaydb
+ flyway-database-postgresql
+ ${flyway.version}
@@ -701,27 +700,22 @@
- joda-time
- joda-time
-
-
- javax.inject
- javax.inject
- 1
- jar
+ jakarta.inject
+ jakarta.inject-api
+ 2.0.1
- javax.xml.bind
- jaxb-api
+ jakarta.xml.bind
+ jakarta.xml.bind-api
org.glassfish.jaxb
jaxb-runtime
-
+
org.glassfish.jersey.core
jersey-client
@@ -742,31 +736,18 @@
1.12.261
+
+
- 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
-
@@ -823,11 +804,17 @@
+
+
+ eu.openaire
+ broker-client
+ 1.1.2
+
org.mock-server
mockserver-junit-rule
- 5.11.2
+ 5.15.0
test
@@ -835,6 +822,20 @@
org.yaml
snakeyaml
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+
+ javax.servlet
+ javax.servlet-api
+
@@ -854,7 +855,6 @@
-
@@ -865,32 +865,32 @@
io.netty
netty-buffer
- 4.1.106.Final
+ 4.1.114.Final
io.netty
netty-transport
- 4.1.106.Final
+ 4.1.114.Final
io.netty
netty-transport-native-unix-common
- 4.1.106.Final
+ 4.1.114.Final
io.netty
netty-common
- 4.1.106.Final
+ 4.1.114.Final
io.netty
netty-handler
- 4.1.106.Final
+ 4.1.114.Final
io.netty
netty-codec
- 4.1.106.Final
+ 4.1.114.Final
org.apache.velocity
@@ -909,14 +909,9 @@
2.2.14
- jakarta.xml.bind
- jakarta.xml.bind-api
- 2.3.3
-
-
- javax.validation
- validation-api
- 2.0.1.Final
+ jakarta.validation
+ jakarta.validation-api
+ 3.1.0
io.swagger
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 e1f11285d8..01b3707479 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.content.Item;
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 class AccessStatusServiceImpl implements AccessStatusService {
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());
}
}
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 2ed47bde4c..e86c5a69f4 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 @@ import org.dspace.core.Context;
* 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.
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 2677cb2050..80bda610c7 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.CommandLineParser;
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.factory.ContentServiceFactory;
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 @@ import org.xml.sax.SAXException;
/**
* @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 @@ public class 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 class MetadataImporter {
* @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 @@ public class MetadataImporter {
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 @@ public class MetadataImporter {
// 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 @@ public class MetadataImporter {
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 13a1b3b5bb..8bbcfe0ff7 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 @@ public class StructBuilder {
// 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 f56cbdcce9..432c633ea5 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 13a0e0af23..79dc1bcf27 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 @@ package org.dspace.alerts.dao.impl;
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/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java
index cbc052b557..ecd6a24287 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 class DSpaceCSV implements Serializable {
// 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 class DSpaceCSV implements Serializable {
}
}
+ // 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 class DSpaceCSV implements Serializable {
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 ad46cb95c3..988768864e 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.Map;
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 class MetadataImport extends DSpaceRunnable 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 0000000000..27257455e0
--- /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 652c887e04..ad3dd36e69 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 0000000000..67a87c144c
--- /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 0000000000..57a7cdfb07
--- /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 0000000000..36a927d672
--- /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 0000000000..14957aa503
--- /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 0000000000..da23471a1c
--- /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 0000000000..c939256b52
--- /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 0000000000..329d6cb11c
--- /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 0000000000..b0c895de99
--- /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 f6590e36f8..86f56ed9ba 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 0000000000..5ce3804bce
--- /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 0000000000..32b115bd07
--- /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 0000000000..f11a42ab2f
--- /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 0000000000..c0ecf04304
--- /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 0000000000..fcbb485aca
--- /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 0000000000..9ecd1b728a
--- /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 0000000000..9751b30382
--- /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 0000000000..194d30e795
--- /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 0000000000..d811f6d39f
--- /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 0000000000..53cbeabe00
--- /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 0000000000..bb4cf791da
--- /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 0000000000..dc3dc1c744
--- /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 0000000000..bbf521123b
--- /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 0000000000..a001ece040
--- /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 0000000000..4b0f107d24
--- /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 0000000000..f411b9d935
--- /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 0000000000..ea488ca250
--- /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 0000000000..84e15ee261
--- /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 0000000000..a81cc3f800
--- /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 0000000000..6ddaae110e
--- /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 0000000000..7abe5c8ef4
--- /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 0000000000..78fe373416
--- /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 0000000000..52bc9840f4
--- /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 0000000000..8333eae910
--- /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 0000000000..437c624f84
--- /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 0000000000..8913af47da
--- /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 0000000000..e33bc3eeb7
--- /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 0000000000..cdd3ba5bb5
--- /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 0000000000..47093e02e4
--- /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 0000000000..a5ef0957ff
--- /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 0000000000..9782e25045
--- /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 0000000000..279ec5cedc
--- /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 0000000000..eb18c6a69a
--- /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 0000000000..c2c3de7e06
--- /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 0000000000..e6ac0d63b4
--- /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 0000000000..8cd92d45dd
--- /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 0000000000..15f07a5561
--- /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 0000000000..89ec4abe58
--- /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 0000000000..87be008371
--- /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 0000000000..c699d9fd03
--- /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 0000000000..949da655bc
--- /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 408982d157..8ed67f4df4 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 abstract class ImageMagickThumbnailFilter extends MediaFilter {
// 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 5fbbebbb28..7f022f38b3 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 @@ import org.dspace.utils.DSpace;
* 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 class MediaFilterScript extends DSpaceRunnable extends
.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 a6ba9fde88..512b8f803b 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,8 +9,11 @@ package org.dspace.app.mediafilter;
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;
@@ -93,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() {
@@ -120,6 +124,15 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
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);
@@ -588,4 +601,9 @@ public class MediaFilterServiceImpl implements MediaFilterService, InitializingB
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/service/MediaFilterService.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/service/MediaFilterService.java
index bc92ff5210..30e6dba42f 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 interface MediaFilterService {
* @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 cdefd1298c..bf2bfb4d60 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 6499c45a78..c489fb4b3f 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 @@ package org.dspace.app.requestitem;
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 008174ded8..c76bd50d19 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 @@ package org.dspace.app.requestitem.dao.impl;
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 class RequestItemDAOImpl extends AbstractHibernateDAO implem
}
@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 ead725e842..943df5f2a0 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.io.InputStream;
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 class 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 @@ public class SHERPAService {
* @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 class SHERPAService {
// 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 class SHERPAService {
// 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 e84fb7775a..5bdf1efa26 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.CacheEvent;
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 b795c8a2b2..cb913a9f26 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 class SHERPASubmitService {
}
/**
- * 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 90962d12aa..2464221d2d 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
@@ -141,7 +141,7 @@ public class GenerateSitemaps {
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);
}
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 3d76ecaecf..9e27ffdd93 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 class HTMLReport implements Report {
}
/**
- * 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 2e4ed69b26..982c339963 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
@@ -481,7 +481,7 @@ public class LogAnalyser {
// 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
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 c5fe0072f5..5b526773d4 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 class ReportGenerator {
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 class ReportGenerator {
/**
* 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 class ReportGenerator {
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 cc8a7024f1..23dbe19b61 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 @@ public class StatisticsLoader {
* 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/suggestion/SolrSuggestionProvider.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionProvider.java
new file mode 100644
index 0000000000..f5acd2ccbc
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionProvider.java
@@ -0,0 +1,140 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Context;
+import org.dspace.external.model.ExternalDataObject;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Suggestion provider that read the suggestion from the local suggestion solr
+ * core
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science dot it)
+ *
+ */
+public abstract class SolrSuggestionProvider implements SuggestionProvider {
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrSuggestionProvider.class);
+
+ @Autowired
+ protected ItemService itemService;
+
+ @Autowired
+ protected SolrSuggestionStorageService solrSuggestionStorageService;
+
+ private String sourceName;
+
+ public String getSourceName() {
+ return sourceName;
+ }
+
+ public void setSourceName(String sourceName) {
+ this.sourceName = sourceName;
+ }
+
+ public void setItemService(ItemService itemService) {
+ this.itemService = itemService;
+ }
+
+ @Override
+ public long countAllTargets(Context context) {
+ try {
+ return this.solrSuggestionStorageService.countAllTargets(context, sourceName);
+ } catch (SolrServerException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public long countUnprocessedSuggestionByTarget(Context context, UUID target) {
+ try {
+ return this.solrSuggestionStorageService.countUnprocessedSuggestionByTarget(context, sourceName, target);
+ } catch (SolrServerException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public List findAllUnprocessedSuggestions(Context context, UUID target, int pageSize, long offset,
+ boolean ascending) {
+
+ try {
+ return this.solrSuggestionStorageService.findAllUnprocessedSuggestions(context, sourceName,
+ target, pageSize, offset, ascending);
+ } catch (SolrServerException | IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ @Override
+ public List findAllTargets(Context context, int pageSize, long offset) {
+ try {
+ return this.solrSuggestionStorageService.findAllTargets(context, sourceName, pageSize, offset);
+ } catch (SolrServerException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Suggestion findUnprocessedSuggestion(Context context, UUID target, String id) {
+ try {
+ return this.solrSuggestionStorageService.findUnprocessedSuggestion(context, sourceName, target, id);
+ } catch (SolrServerException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public SuggestionTarget findTarget(Context context, UUID target) {
+ try {
+ return this.solrSuggestionStorageService.findTarget(context, sourceName, target);
+ } catch (SolrServerException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void rejectSuggestion(Context context, UUID target, String idPart) {
+ Suggestion suggestion = findUnprocessedSuggestion(context, target, idPart);
+ try {
+ solrSuggestionStorageService.flagSuggestionAsProcessed(suggestion);
+ } catch (SolrServerException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void flagRelatedSuggestionsAsProcessed(Context context, ExternalDataObject externalDataObject) {
+ if (!isExternalDataObjectPotentiallySuggested(context, externalDataObject)) {
+ return;
+ }
+ try {
+ solrSuggestionStorageService.flagAllSuggestionAsProcessed(sourceName, externalDataObject.getId());
+ } catch (SolrServerException | IOException e) {
+ log.error(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * check if the externalDataObject may have suggestion
+ * @param context
+ * @param externalDataObject
+ * @return true if the externalDataObject could be suggested by this provider
+ * (i.e. it comes from a DataProvider used by this suggestor)
+ */
+ protected abstract boolean isExternalDataObjectPotentiallySuggested(Context context,
+ ExternalDataObject externalDataObject);
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageService.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageService.java
new file mode 100644
index 0000000000..b7de6146f2
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageService.java
@@ -0,0 +1,191 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.solr.client.solrj.SolrServerException;
+import org.dspace.core.Context;
+
+/**
+ * Service to deal with the local suggestion solr core used by the
+ * SolrSuggestionProvider(s)
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science dot it)
+ * @author Luca Giamminonni (luca.giamminonni at 4science dot it)
+ *
+ */
+public interface SolrSuggestionStorageService {
+ public static final String SOURCE = "source";
+ /** This is the URI Part of the suggestion source:target:id */
+ public static final String SUGGESTION_FULLID = "suggestion_fullid";
+ public static final String SUGGESTION_ID = "suggestion_id";
+ public static final String TARGET_ID = "target_id";
+ public static final String TITLE = "title";
+ public static final String DATE = "date";
+ public static final String DISPLAY = "display";
+ public static final String CONTRIBUTORS = "contributors";
+ public static final String ABSTRACT = "abstract";
+ public static final String CATEGORY = "category";
+ public static final String EXTERNAL_URI = "external-uri";
+ public static final String PROCESSED = "processed";
+ public static final String SCORE = "trust";
+ public static final String EVIDENCES = "evidences";
+
+ /**
+ * Add a new suggestion to SOLR
+ *
+ * @param suggestion
+ * @param force true if the suggestion must be reindexed
+ * @param commit
+ * @throws IOException
+ * @throws SolrServerException
+ */
+ public void addSuggestion(Suggestion suggestion, boolean force, boolean commit)
+ throws SolrServerException, IOException;
+
+ /**
+ * Return true if the suggestion is already in SOLR and flagged as processed
+ *
+ * @param suggestion
+ * @return true if the suggestion is already in SOLR and flagged as processed
+ * @throws IOException
+ * @throws SolrServerException
+ */
+ public boolean exist(Suggestion suggestion) throws SolrServerException, IOException;
+
+ /**
+ * Delete a suggestion from SOLR if any
+ *
+ * @param suggestion
+ * @throws IOException
+ * @throws SolrServerException
+ */
+ public void deleteSuggestion(Suggestion suggestion) throws SolrServerException, IOException;
+
+ /**
+ * Flag a suggestion as processed in SOLR if any
+ *
+ * @param suggestion
+ * @throws IOException
+ * @throws SolrServerException
+ */
+ public void flagSuggestionAsProcessed(Suggestion suggestion) throws SolrServerException, IOException;
+
+ /**
+ * Delete all the suggestions from SOLR if any related to a specific target
+ *
+ * @param target
+ * @throws IOException
+ * @throws SolrServerException
+ */
+ public void deleteTarget(SuggestionTarget target) throws SolrServerException, IOException;
+
+ /**
+ * Performs an explicit commit, causing pending documents to be committed for
+ * indexing.
+ *
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ void commit() throws SolrServerException, IOException;
+
+ /**
+ * Flag all the suggestion related to the given source and id as processed.
+ *
+ * @param source the source name
+ * @param idPart the id's last part
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ void flagAllSuggestionAsProcessed(String source, String idPart) throws SolrServerException, IOException;
+
+ /**
+ * Count all the targets related to the given source.
+ *
+ * @param source the source name
+ * @return the target's count
+ * @throws IOException
+ * @throws SolrServerException
+ */
+ long countAllTargets(Context context, String source) throws SolrServerException, IOException;
+
+ /**
+ * Count all the unprocessed suggestions related to the given source and target.
+ *
+ * @param context the DSpace Context
+ * @param source the source name
+ * @param target the target id
+ * @return the suggestion count
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ long countUnprocessedSuggestionByTarget(Context context, String source, UUID target)
+ throws SolrServerException, IOException;
+
+ /**
+ * Find all the unprocessed suggestions related to the given source and target.
+ *
+ * @param context the DSpace Context
+ * @param source the source name
+ * @param target the target id
+ * @param pageSize the page size
+ * @param offset the page offset
+ * @param ascending true to retrieve the suggestions ordered by score
+ * ascending
+ * @return the found suggestions
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ List findAllUnprocessedSuggestions(Context context, String source, UUID target,
+ int pageSize, long offset, boolean ascending) throws SolrServerException, IOException;
+
+ /**
+ *
+ * Find all the suggestion targets related to the given source.
+ *
+ * @param context the DSpace Context
+ * @param source the source name
+ * @param pageSize the page size
+ * @param offset the page offset
+ * @return the found suggestion targets
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ List findAllTargets(Context context, String source, int pageSize, long offset)
+ throws SolrServerException, IOException;
+
+ /**
+ * Find an unprocessed suggestion by the given source, target id and suggestion
+ * id.
+ *
+ * @param context the DSpace Context
+ * @param source the source name
+ * @param target the target id
+ * @param id the suggestion id
+ * @return the suggestion, if any
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ Suggestion findUnprocessedSuggestion(Context context, String source, UUID target, String id)
+ throws SolrServerException, IOException;
+
+ /**
+ * Find a suggestion target by the given source and target.
+ *
+ * @param context the DSpace Context
+ * @param source the source name
+ * @param target the target id
+ * @return the suggestion target, if any
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ SuggestionTarget findTarget(Context context, String source, UUID target) throws SolrServerException, IOException;
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java
new file mode 100644
index 0000000000..3c2ad71846
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SolrSuggestionStorageServiceImpl.java
@@ -0,0 +1,361 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion;
+
+import static org.apache.commons.collections.CollectionUtils.isEmpty;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrQuery.SortClause;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.response.FacetField;
+import org.apache.solr.client.solrj.response.FacetField.Count;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.params.FacetParams;
+import org.dspace.content.Item;
+import org.dspace.content.dto.MetadataValueDTO;
+import org.dspace.content.service.ItemService;
+import org.dspace.core.Context;
+import org.dspace.services.factory.DSpaceServicesFactory;
+import org.dspace.util.UUIDUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+
+
+/**
+ * Service to deal with the local suggestion solr core used by the
+ * SolrSuggestionProvider(s)
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science dot it)
+ *
+ */
+public class SolrSuggestionStorageServiceImpl implements SolrSuggestionStorageService {
+
+ private static final Logger log = LogManager.getLogger(SolrSuggestionStorageServiceImpl.class);
+
+ protected SolrClient solrSuggestionClient;
+
+ @Autowired
+ private ItemService itemService;
+
+ /**
+ * Get solr client which use suggestion core
+ *
+ * @return solr client
+ */
+ protected SolrClient getSolr() {
+ if (solrSuggestionClient == null) {
+ String solrService = DSpaceServicesFactory.getInstance().getConfigurationService()
+ .getProperty("suggestion.solr.server", "http://localhost:8983/solr/suggestion");
+ solrSuggestionClient = new HttpSolrClient.Builder(solrService).build();
+ }
+ return solrSuggestionClient;
+ }
+
+ @Override
+ public void addSuggestion(Suggestion suggestion, boolean force, boolean commit)
+ throws SolrServerException, IOException {
+ if (force || !exist(suggestion)) {
+ ObjectMapper jsonMapper = new JsonMapper();
+ jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ SolrInputDocument document = new SolrInputDocument();
+ document.addField(SOURCE, suggestion.getSource());
+ // suggestion id is written as concatenation of
+ // source + ":" + targetID + ":" + idPart (of externalDataObj)
+ String suggestionFullID = suggestion.getID();
+ document.addField(SUGGESTION_FULLID, suggestionFullID);
+ document.addField(SUGGESTION_ID, suggestionFullID.split(":", 3)[2]);
+ document.addField(TARGET_ID, suggestion.getTarget().getID().toString());
+ document.addField(DISPLAY, suggestion.getDisplay());
+ document.addField(TITLE, getFirstValue(suggestion, "dc", "title", null));
+ document.addField(DATE, getFirstValue(suggestion, "dc", "date", "issued"));
+ document.addField(CONTRIBUTORS, getAllValues(suggestion, "dc", "contributor", "author"));
+ document.addField(ABSTRACT, getFirstValue(suggestion, "dc", "description", "abstract"));
+ document.addField(CATEGORY, getAllValues(suggestion, "dc", "source", null));
+ document.addField(EXTERNAL_URI, suggestion.getExternalSourceUri());
+ document.addField(SCORE, suggestion.getScore());
+ document.addField(PROCESSED, false);
+ document.addField(EVIDENCES, jsonMapper.writeValueAsString(suggestion.getEvidences()));
+ getSolr().add(document);
+ if (commit) {
+ getSolr().commit();
+ }
+ }
+ }
+
+ @Override
+ public void commit() throws SolrServerException, IOException {
+ getSolr().commit();
+ }
+
+ private List getAllValues(Suggestion suggestion, String schema, String element, String qualifier) {
+ return suggestion.getMetadata().stream()
+ .filter(st -> StringUtils.isNotBlank(st.getValue()) && StringUtils.equals(st.getSchema(), schema)
+ && StringUtils.equals(st.getElement(), element)
+ && StringUtils.equals(st.getQualifier(), qualifier))
+ .map(st -> st.getValue()).collect(Collectors.toList());
+ }
+
+ private String getFirstValue(Suggestion suggestion, String schema, String element, String qualifier) {
+ return suggestion.getMetadata().stream()
+ .filter(st -> StringUtils.isNotBlank(st.getValue())
+ && StringUtils.equals(st.getSchema(), schema)
+ && StringUtils.equals(st.getElement(), element)
+ && StringUtils.equals(st.getQualifier(), qualifier))
+ .map(st -> st.getValue()).findFirst().orElse(null);
+ }
+
+ @Override
+ public boolean exist(Suggestion suggestion) throws SolrServerException, IOException {
+ SolrQuery query = new SolrQuery(
+ SUGGESTION_FULLID + ":\"" + suggestion.getID() + "\" AND " + PROCESSED + ":true");
+ return getSolr().query(query).getResults().getNumFound() == 1;
+ }
+
+ @Override
+ public void deleteSuggestion(Suggestion suggestion) throws SolrServerException, IOException {
+ getSolr().deleteById(suggestion.getID());
+ getSolr().commit();
+ }
+
+ @Override
+ public void flagSuggestionAsProcessed(Suggestion suggestion) throws SolrServerException, IOException {
+ SolrInputDocument sdoc = new SolrInputDocument();
+ sdoc.addField(SUGGESTION_FULLID, suggestion.getID());
+ Map fieldModifier = new HashMap<>(1);
+ fieldModifier.put("set", true);
+ sdoc.addField(PROCESSED, fieldModifier); // add the map as the field value
+ getSolr().add(sdoc);
+ getSolr().commit();
+ }
+
+ @Override
+ public void flagAllSuggestionAsProcessed(String source, String idPart) throws SolrServerException, IOException {
+ SolrQuery query = new SolrQuery(SOURCE + ":" + source + " AND " + SUGGESTION_ID + ":\"" + idPart + "\"");
+ query.setRows(Integer.MAX_VALUE);
+ query.setFields(SUGGESTION_FULLID);
+ SolrDocumentList results = getSolr().query(query).getResults();
+ if (results.getNumFound() > 0) {
+ for (SolrDocument rDoc : results) {
+ SolrInputDocument sdoc = new SolrInputDocument();
+ sdoc.addField(SUGGESTION_FULLID, rDoc.getFieldValue(SUGGESTION_FULLID));
+ Map fieldModifier = new HashMap<>(1);
+ fieldModifier.put("set", true);
+ sdoc.addField(PROCESSED, fieldModifier); // add the map as the field value
+ getSolr().add(sdoc);
+ }
+ }
+ getSolr().commit();
+ }
+
+ @Override
+ public void deleteTarget(SuggestionTarget target) throws SolrServerException, IOException {
+ getSolr().deleteByQuery(
+ SOURCE + ":" + target.getSource() + " AND " + TARGET_ID + ":" + target.getTarget().getID().toString());
+ getSolr().commit();
+ }
+
+ @Override
+ public long countAllTargets(Context context, String source) throws SolrServerException, IOException {
+ SolrQuery solrQuery = new SolrQuery();
+ solrQuery.setRows(0);
+ solrQuery.setQuery(SOURCE + ":" + source);
+ solrQuery.addFilterQuery(PROCESSED + ":false");
+ solrQuery.setFacet(true);
+ solrQuery.setFacetMinCount(1);
+ solrQuery.addFacetField(TARGET_ID);
+ solrQuery.setFacetLimit(Integer.MAX_VALUE);
+ QueryResponse response = getSolr().query(solrQuery);
+ return response.getFacetField(TARGET_ID).getValueCount();
+ }
+
+ @Override
+ public long countUnprocessedSuggestionByTarget(Context context, String source, UUID target)
+ throws SolrServerException, IOException {
+ SolrQuery solrQuery = new SolrQuery();
+ solrQuery.setRows(0);
+ solrQuery.setQuery("*:*");
+ solrQuery.addFilterQuery(
+ SOURCE + ":" + source,
+ TARGET_ID + ":" + target.toString(),
+ PROCESSED + ":false");
+
+ QueryResponse response = getSolr().query(solrQuery);
+ return response.getResults().getNumFound();
+ }
+
+ @Override
+ public List findAllUnprocessedSuggestions(Context context, String source, UUID target,
+ int pageSize, long offset, boolean ascending) throws SolrServerException, IOException {
+
+ SolrQuery solrQuery = new SolrQuery();
+ solrQuery.setRows(pageSize);
+ solrQuery.setStart((int) offset);
+ solrQuery.setQuery("*:*");
+ solrQuery.addFilterQuery(
+ SOURCE + ":" + source,
+ TARGET_ID + ":" + target.toString(),
+ PROCESSED + ":false");
+
+ if (ascending) {
+ solrQuery.addSort(SortClause.asc("trust"));
+ } else {
+ solrQuery.addSort(SortClause.desc("trust"));
+ }
+
+ solrQuery.addSort(SortClause.desc("date"));
+ solrQuery.addSort(SortClause.asc("title"));
+
+ QueryResponse response = getSolr().query(solrQuery);
+ List suggestions = new ArrayList();
+ for (SolrDocument solrDoc : response.getResults()) {
+ suggestions.add(convertSolrDoc(context, solrDoc, source));
+ }
+ return suggestions;
+
+ }
+
+ @Override
+ public List findAllTargets(Context context, String source, int pageSize, long offset)
+ throws SolrServerException, IOException {
+
+ SolrQuery solrQuery = new SolrQuery();
+ solrQuery.setRows(0);
+ solrQuery.setQuery(SOURCE + ":" + source);
+ solrQuery.addFilterQuery(PROCESSED + ":false");
+ solrQuery.setFacet(true);
+ solrQuery.setFacetMinCount(1);
+ solrQuery.addFacetField(TARGET_ID);
+ solrQuery.setParam(FacetParams.FACET_OFFSET, String.valueOf(offset));
+ solrQuery.setFacetLimit((int) (pageSize));
+ QueryResponse response = getSolr().query(solrQuery);
+ FacetField facetField = response.getFacetField(TARGET_ID);
+ List suggestionTargets = new ArrayList();
+ int idx = 0;
+ for (Count c : facetField.getValues()) {
+ SuggestionTarget target = new SuggestionTarget();
+ target.setSource(source);
+ target.setTotal((int) c.getCount());
+ target.setTarget(findItem(context, c.getName()));
+ suggestionTargets.add(target);
+ idx++;
+ }
+ return suggestionTargets;
+
+ }
+
+ @Override
+ public Suggestion findUnprocessedSuggestion(Context context, String source, UUID target, String id)
+ throws SolrServerException, IOException {
+
+ SolrQuery solrQuery = new SolrQuery();
+ solrQuery.setRows(1);
+ solrQuery.setQuery("*:*");
+ solrQuery.addFilterQuery(
+ SOURCE + ":" + source,
+ TARGET_ID + ":" + target.toString(),
+ SUGGESTION_ID + ":\"" + id + "\"",
+ PROCESSED + ":false");
+
+ SolrDocumentList results = getSolr().query(solrQuery).getResults();
+ return isEmpty(results) ? null : convertSolrDoc(context, results.get(0), source);
+ }
+
+ @Override
+ public SuggestionTarget findTarget(Context context, String source, UUID target)
+ throws SolrServerException, IOException {
+ SolrQuery solrQuery = new SolrQuery();
+ solrQuery.setRows(0);
+ solrQuery.setQuery(SOURCE + ":" + source);
+ solrQuery.addFilterQuery(
+ TARGET_ID + ":" + target.toString(),
+ PROCESSED + ":false");
+ QueryResponse response = getSolr().query(solrQuery);
+ SuggestionTarget sTarget = new SuggestionTarget();
+ sTarget.setSource(source);
+ sTarget.setTotal((int) response.getResults().getNumFound());
+ Item itemTarget = findItem(context, target);
+ if (itemTarget != null) {
+ sTarget.setTarget(itemTarget);
+ } else {
+ return null;
+ }
+ return sTarget;
+ }
+
+ private Suggestion convertSolrDoc(Context context, SolrDocument solrDoc, String sourceName) {
+ Item target = findItem(context, (String) solrDoc.getFieldValue(TARGET_ID));
+
+ Suggestion suggestion = new Suggestion(sourceName, target, (String) solrDoc.getFieldValue(SUGGESTION_ID));
+ suggestion.setDisplay((String) solrDoc.getFieldValue(DISPLAY));
+ suggestion.getMetadata()
+ .add(new MetadataValueDTO("dc", "title", null, null, (String) solrDoc.getFieldValue(TITLE)));
+ suggestion.getMetadata()
+ .add(new MetadataValueDTO("dc", "date", "issued", null, (String) solrDoc.getFieldValue(DATE)));
+ suggestion.getMetadata().add(
+ new MetadataValueDTO("dc", "description", "abstract", null, (String) solrDoc.getFieldValue(ABSTRACT)));
+
+ suggestion.setExternalSourceUri((String) solrDoc.getFieldValue(EXTERNAL_URI));
+ if (solrDoc.containsKey(CATEGORY)) {
+ for (Object o : solrDoc.getFieldValues(CATEGORY)) {
+ suggestion.getMetadata().add(
+ new MetadataValueDTO("dc", "source", null, null, (String) o));
+ }
+ }
+ if (solrDoc.containsKey(CONTRIBUTORS)) {
+ for (Object o : solrDoc.getFieldValues(CONTRIBUTORS)) {
+ suggestion.getMetadata().add(
+ new MetadataValueDTO("dc", "contributor", "author", null, (String) o));
+ }
+ }
+ String evidencesJson = (String) solrDoc.getFieldValue(EVIDENCES);
+ ObjectMapper jsonMapper = new JsonMapper();
+ jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ List evidences = new LinkedList();
+ try {
+ evidences = jsonMapper.readValue(evidencesJson, new TypeReference
>() {});
+ } catch (JsonProcessingException e) {
+ log.error(e);
+ }
+ suggestion.getEvidences().addAll(evidences);
+ return suggestion;
+ }
+
+ private Item findItem(Context context, UUID itemId) {
+ try {
+ return itemService.find(context, itemId);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Item findItem(Context context, String itemId) {
+ return findItem(context, UUIDUtils.fromString(itemId));
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/Suggestion.java b/dspace-api/src/main/java/org/dspace/app/suggestion/Suggestion.java
new file mode 100644
index 0000000000..7812cbd522
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/Suggestion.java
@@ -0,0 +1,99 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.dspace.content.Item;
+import org.dspace.content.dto.MetadataValueDTO;
+
+/**
+ * This entity contains metadatas that should be added to the targeted Item
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class Suggestion {
+
+ /** id of the suggestion */
+ private String id;
+
+ /** the dc.title of the item */
+ private String display;
+
+ /** the external source name the suggestion comes from */
+ private String source;
+
+ /** external uri of the item */
+ private String externalSourceUri;
+
+ /** item targeted by this suggestion */
+ private Item target;
+
+ private List evidences = new LinkedList();
+
+ private List metadata = new LinkedList();
+
+ /** suggestion creation
+ * @param source name of the external source
+ * @param target the targeted item in repository
+ * @param idPart external item id, used mainly for suggestion @see #id creation
+ * */
+ public Suggestion(String source, Item target, String idPart) {
+ this.source = source;
+ this.target = target;
+ this.id = source + ":" + target.getID().toString() + ":" + idPart;
+ }
+
+ public String getDisplay() {
+ return display;
+ }
+
+ public void setDisplay(String display) {
+ this.display = display;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public String getExternalSourceUri() {
+ return externalSourceUri;
+ }
+
+ public void setExternalSourceUri(String externalSourceUri) {
+ this.externalSourceUri = externalSourceUri;
+ }
+
+ public List getEvidences() {
+ return evidences;
+ }
+
+ public List getMetadata() {
+ return metadata;
+ }
+
+ public Item getTarget() {
+ return target;
+ }
+
+ public String getID() {
+ return id;
+ }
+
+ public Double getScore() {
+ if (evidences != null && evidences.size() > 0) {
+ double score = 0;
+ for (SuggestionEvidence evidence : evidences) {
+ score += evidence.getScore();
+ }
+ return score;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionEvidence.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionEvidence.java
new file mode 100644
index 0000000000..d7f04929a1
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionEvidence.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.suggestion;
+
+/**
+ * This DTO class is returned by an {@link org.dspace.app.suggestion.openaire.EvidenceScorer} to model the concept of
+ * an evidence / fact that has been used to evaluate the precision of a suggestion increasing or decreasing the score
+ * of the suggestion.
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class SuggestionEvidence {
+
+ /** name of the evidence */
+ private String name;
+
+ /** positive or negative value to influence the score of the suggestion */
+ private double score;
+
+ /** additional notes */
+ private String notes;
+
+ public SuggestionEvidence() {
+ }
+
+ public SuggestionEvidence(String name, double score, String notes) {
+ this.name = name;
+ this.score = score;
+ this.notes = notes;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public double getScore() {
+ return score;
+ }
+
+ public void setScore(double score) {
+ this.score = score;
+ }
+
+ public String getNotes() {
+ return notes;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionProvider.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionProvider.java
new file mode 100644
index 0000000000..7cfc3cfb53
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionProvider.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.suggestion;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.dspace.core.Context;
+import org.dspace.external.model.ExternalDataObject;
+
+/**
+ *
+ * Interface for suggestion management like finding and counting.
+ * @see org.dspace.app.suggestion.SuggestionTarget
+ * @author Francesco Bacchelli (francesco.bacchelli at 4science.com)
+ *
+ */
+public interface SuggestionProvider {
+
+ /** find all suggestion targets
+ * @see org.dspace.app.suggestion.SuggestionTarget
+ * */
+ public List findAllTargets(Context context, int pageSize, long offset);
+
+ /** count all suggestion targets */
+ public long countAllTargets(Context context);
+
+ /** find a suggestion target by UUID */
+ public SuggestionTarget findTarget(Context context, UUID target);
+
+ /** find unprocessed suggestions (paged) by target UUID
+ * @see org.dspace.app.suggestion.Suggestion
+ * */
+ public List findAllUnprocessedSuggestions(Context context, UUID target, int pageSize, long offset,
+ boolean ascending);
+
+ /** find unprocessed suggestions by target UUID */
+ public long countUnprocessedSuggestionByTarget(Context context, UUID target);
+
+ /** find an unprocessed suggestion by target UUID and suggestion id */
+ public Suggestion findUnprocessedSuggestion(Context context, UUID target, String id);
+
+ /** reject a specific suggestion by target @param target and by suggestion id @param idPart */
+ public void rejectSuggestion(Context context, UUID target, String idPart);
+
+ /** flag a suggestion as processed */
+ public void flagRelatedSuggestionsAsProcessed(Context context, ExternalDataObject externalDataObject);
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.java
new file mode 100644
index 0000000000..e8a8830261
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionService.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.suggestion;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.dspace.core.Context;
+
+/**
+ * Service that handles {@link Suggestion}.
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public interface SuggestionService {
+
+ /** find a {@link SuggestionTarget } by source name and suggestion id */
+ public SuggestionTarget find(Context context, String source, UUID id);
+
+ /** count all suggestion targets by suggestion source */
+ public long countAll(Context context, String source);
+
+ /** find all suggestion targets by source (paged) */
+ public List findAllTargets(Context context, String source, int pageSize, long offset);
+
+ /** count all (unprocessed) suggestions by the given target uuid */
+ public long countAllByTarget(Context context, UUID target);
+
+ /** find suggestion target by targeted item (paged) */
+ public List findByTarget(Context context, UUID target, int pageSize, long offset);
+
+ /** find suggestion source by source name */
+ public SuggestionSource findSource(Context context, String source);
+
+ /** count all suggestion sources */
+ public long countSources(Context context);
+
+ /** find all suggestion sources (paged) */
+ public List findAllSources(Context context, int pageSize, long offset);
+
+ /** find unprocessed suggestion by id */
+ public Suggestion findUnprocessedSuggestion(Context context, String id);
+
+ /** reject a specific suggestion by its id */
+ public void rejectSuggestion(Context context, String id);
+
+ /** find all suggestions by targeted item and external source */
+ public List findByTargetAndSource(Context context, UUID target, String source, int pageSize,
+ long offset, boolean ascending);
+
+ /** count all suggestions by targeted item id and source name */
+ public long countAllByTargetAndSource(Context context, String source, UUID target);
+
+ /** returns all suggestion providers */
+ public List getSuggestionProviders();
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java
new file mode 100644
index 0000000000..57fe42806b
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionServiceImpl.java
@@ -0,0 +1,194 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import jakarta.annotation.Resource;
+import org.apache.logging.log4j.Logger;
+import org.dspace.core.Context;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SuggestionServiceImpl implements SuggestionService {
+ private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SuggestionServiceImpl.class);
+
+ @Resource(name = "suggestionProviders")
+ private Map providersMap;
+
+ @Override
+ public List getSuggestionProviders() {
+ if (providersMap != null) {
+ return providersMap.values().stream().collect(Collectors.toList());
+ }
+ return null;
+ }
+
+ @Override
+ public SuggestionTarget find(Context context, String source, UUID id) {
+ if (providersMap.containsKey(source)) {
+ return providersMap.get(source).findTarget(context, id);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public long countAll(Context context, String source) {
+ if (providersMap.containsKey(source)) {
+ return providersMap.get(source).countAllTargets(context);
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public List findAllTargets(Context context, String source, int pageSize, long offset) {
+ if (providersMap.containsKey(source)) {
+ return providersMap.get(source).findAllTargets(context, pageSize, offset);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public long countAllByTarget(Context context, UUID target) {
+ int count = 0;
+ for (String provider : providersMap.keySet()) {
+ if (providersMap.get(provider).countUnprocessedSuggestionByTarget(context, target) > 0) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public List findByTarget(Context context, UUID target, int pageSize, long offset) {
+ List fullSourceTargets = new ArrayList();
+ for (String source : providersMap.keySet()) {
+ // all the suggestion target will be related to the same target (i.e. the same researcher - person item)
+ SuggestionTarget sTarget = providersMap.get(source).findTarget(context, target);
+ if (sTarget != null && sTarget.getTotal() > 0) {
+ fullSourceTargets.add(sTarget);
+ }
+ }
+ fullSourceTargets.sort(new Comparator() {
+ @Override
+ public int compare(SuggestionTarget arg0, SuggestionTarget arg1) {
+ return -(arg0.getTotal() - arg1.getTotal());
+ }
+ }
+ );
+ // this list will be as large as the number of sources available in the repository so it is unlikely that
+ // real pagination will occur
+ return fullSourceTargets.stream().skip(offset).limit(pageSize).collect(Collectors.toList());
+ }
+
+ @Override
+ public long countSources(Context context) {
+ return providersMap.size();
+ }
+
+ @Override
+ public SuggestionSource findSource(Context context, String source) {
+ if (providersMap.containsKey(source)) {
+ SuggestionSource ssource = new SuggestionSource(source);
+ ssource.setTotal((int) providersMap.get(source).countAllTargets(context));
+ return ssource;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public List findAllSources(Context context, int pageSize, long offset) {
+ List fullSources = getSources(context).stream().skip(offset).limit(pageSize)
+ .collect(Collectors.toList());
+ return fullSources;
+ }
+
+ private List getSources(Context context) {
+ List results = new ArrayList();
+ for (String source : providersMap.keySet()) {
+ SuggestionSource ssource = new SuggestionSource(source);
+ ssource.setTotal((int) providersMap.get(source).countAllTargets(context));
+ results.add(ssource);
+ }
+ return results;
+ }
+
+ @Override
+ public long countAllByTargetAndSource(Context context, String source, UUID target) {
+ if (providersMap.containsKey(source)) {
+ return providersMap.get(source).countUnprocessedSuggestionByTarget(context, target);
+ }
+ return 0;
+ }
+
+ @Override
+ public List findByTargetAndSource(Context context, UUID target, String source, int pageSize,
+ long offset, boolean ascending) {
+ if (providersMap.containsKey(source)) {
+ return providersMap.get(source).findAllUnprocessedSuggestions(context, target, pageSize, offset, ascending);
+ }
+ return null;
+ }
+
+ @Override
+ public Suggestion findUnprocessedSuggestion(Context context, String id) {
+ String source = null;
+ UUID target = null;
+ String idPart = null;
+ String[] split;
+ try {
+ split = id.split(":", 3);
+ source = split[0];
+ target = UUID.fromString(split[1]);
+ idPart = split[2];
+ } catch (Exception e) {
+ log.warn("findSuggestion got an invalid id " + id + ", return null");
+ return null;
+ }
+ if (split.length != 3) {
+ return null;
+ }
+ if (providersMap.containsKey(source)) {
+ return providersMap.get(source).findUnprocessedSuggestion(context, target, idPart);
+ }
+ return null;
+ }
+
+ @Override
+ public void rejectSuggestion(Context context, String id) {
+ String source = null;
+ UUID target = null;
+ String idPart = null;
+ String[] split;
+ try {
+ split = id.split(":", 3);
+ source = split[0];
+ target = UUID.fromString(split[1]);
+ idPart = split[2];
+ } catch (Exception e) {
+ log.warn("rejectSuggestion got an invalid id " + id + ", doing nothing");
+ return;
+ }
+ if (split.length != 3) {
+ return;
+ }
+ if (providersMap.containsKey(source)) {
+ providersMap.get(source).rejectSuggestion(context, target, idPart);
+ }
+
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.java
new file mode 100644
index 0000000000..6dcc3f7e1e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionSource.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.suggestion;
+
+/**
+ * This DTO class is used to pass around the number of items interested by suggestion provided by a specific source
+ * (i.e. openaire)
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class SuggestionSource {
+
+ /** source name of the suggestion */
+ private String name;
+
+ /** number of targeted items */
+ private int total;
+
+ public SuggestionSource() {
+ }
+
+ /**
+ * Summarize the available suggestions from a source.
+ *
+ * @param name the name must be not null
+ */
+ public SuggestionSource(String name) {
+ super();
+ this.name = name;
+ }
+
+ public String getID() {
+ return name;
+ }
+
+ public int getTotal() {
+ return total;
+ }
+
+ public void setTotal(int total) {
+ this.total = total;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionTarget.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionTarget.java
new file mode 100644
index 0000000000..db82aa8081
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionTarget.java
@@ -0,0 +1,75 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion;
+
+import org.dspace.content.Item;
+
+/**
+ * This DTO class is used to pass around the number of suggestions available from a specific source for a target
+ * repository item
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class SuggestionTarget {
+
+ /** the item targeted */
+ private Item target;
+
+ /** source name of the suggestion */
+ private String source;
+
+ /** total count of suggestions for same target and source */
+ private int total;
+
+ public SuggestionTarget() {
+ }
+
+ /**
+ * Wrap a target repository item (usually a person item) into a suggestion target.
+ *
+ * @param item must be not null
+ */
+ public SuggestionTarget(Item item) {
+ super();
+ this.target = item;
+ }
+
+ /**
+ * The suggestion target uses the concatenation of the source and target uuid separated by colon as id
+ *
+ * @return the source:uuid of the wrapped item
+ */
+ public String getID() {
+ return source + ":" + (target != null ? target.getID() : "");
+ }
+
+ public Item getTarget() {
+ return target;
+ }
+
+ public void setTarget(Item target) {
+ this.target = target;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ public int getTotal() {
+ return total;
+ }
+
+ public void setTotal(int total) {
+ this.total = total;
+ }
+
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionUtils.java b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionUtils.java
new file mode 100644
index 0000000000..30ced75fc9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/SuggestionUtils.java
@@ -0,0 +1,111 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.dspace.external.model.ExternalDataObject;
+
+/**
+ * This utility class provides convenient methods to deal with the
+ * {@link ExternalDataObject} for the purpose of the Suggestion framework
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science.it)
+ */
+public class SuggestionUtils {
+ private SuggestionUtils() {
+ }
+ /**
+ * This method receive an ExternalDataObject and a metadatum key.
+ * It return only the values of the Metadata associated with the key.
+ *
+ * @param record the ExternalDataObject to extract metadata from
+ * @param schema schema of the searching record
+ * @param element element of the searching record
+ * @param qualifier qualifier of the searching record
+ * @return value of the first matching record
+ */
+ public static List getAllEntriesByMetadatum(ExternalDataObject record, String schema, String element,
+ String qualifier) {
+ return record.getMetadata().stream()
+ .filter(x ->
+ StringUtils.equals(x.getSchema(), schema)
+ && StringUtils.equals(x.getElement(), element)
+ && StringUtils.equals(x.getQualifier(), qualifier))
+ .map(x -> x.getValue()).collect(Collectors.toList());
+ }
+
+ /**
+ * This method receive an ExternalDataObject and a metadatum key.
+ * It return only the values of the Metadata associated with the key.
+ *
+ * @param record the ExternalDataObject to extract metadata from
+ * @param metadataFieldKey the metadata field key (i.e. dc.title or dc.contributor.author),
+ * the jolly char is not supported
+ * @return value of the first matching record
+ */
+ public static List getAllEntriesByMetadatum(ExternalDataObject record, String metadataFieldKey) {
+ if (metadataFieldKey == null) {
+ return Collections.EMPTY_LIST;
+ }
+ String[] fields = metadataFieldKey.split("\\.");
+ String schema = fields[0];
+ String element = fields[1];
+ String qualifier = null;
+ if (fields.length == 3) {
+ qualifier = fields[2];
+ }
+ return getAllEntriesByMetadatum(record, schema, element, qualifier);
+ }
+
+ /**
+ * This method receive and ExternalDataObject and a metadatum key.
+ * It return only the value of the first Metadatum from the list associated with the key.
+ *
+ * @param record the ExternalDataObject to extract metadata from
+ * @param schema schema of the searching record
+ * @param element element of the searching record
+ * @param qualifier qualifier of the searching record
+ * @return value of the first matching record
+ */
+ public static String getFirstEntryByMetadatum(ExternalDataObject record, String schema, String element,
+ String qualifier) {
+ return record.getMetadata().stream()
+ .filter(x ->
+ StringUtils.equals(x.getSchema(), schema)
+ && StringUtils.equals(x.getElement(), element)
+ && StringUtils.equals(x.getQualifier(), qualifier))
+ .map(x -> x.getValue()).findFirst().orElse(null);
+ }
+
+ /**
+ * This method receive and ExternalDataObject and a metadatum key.
+ * It return only the value of the first Metadatum from the list associated with the key.
+ *
+ * @param record the ExternalDataObject to extract metadata from
+ * @param metadataFieldKey the metadata field key (i.e. dc.title or dc.contributor.author),
+ * the jolly char is not supported
+ * @return value of the first matching record
+ */
+ public static String getFirstEntryByMetadatum(ExternalDataObject record, String metadataFieldKey) {
+ if (metadataFieldKey == null) {
+ return null;
+ }
+ String[] fields = metadataFieldKey.split("\\.");
+ String schema = fields[0];
+ String element = fields[1];
+ String qualifier = null;
+ if (fields.length == 3) {
+ qualifier = fields[2];
+ }
+ return getFirstEntryByMetadatum(record, schema, element, qualifier);
+ }
+}
\ No newline at end of file
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/AuthorNamesScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/AuthorNamesScorer.java
new file mode 100644
index 0000000000..60b1521f7e
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/AuthorNamesScorer.java
@@ -0,0 +1,151 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion.openaire;
+
+import static org.dspace.app.suggestion.SuggestionUtils.getAllEntriesByMetadatum;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import com.ibm.icu.text.CharsetDetector;
+import com.ibm.icu.text.CharsetMatch;
+import com.ibm.icu.text.Normalizer;
+import org.apache.commons.lang3.StringUtils;
+import org.dspace.app.suggestion.SuggestionEvidence;
+import org.dspace.content.Item;
+import org.dspace.content.MetadataValue;
+import org.dspace.content.service.ItemService;
+import org.dspace.external.model.ExternalDataObject;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation of {@see org.dspace.app.suggestion.oaire.EvidenceScorer} which evaluate ImportRecords
+ * based on Author's name.
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science dot it)
+ * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
+ *
+ */
+public class AuthorNamesScorer implements EvidenceScorer {
+
+ private List contributorMetadata;
+
+ private List names;
+
+ @Autowired
+ private ItemService itemService;
+
+ /**
+ * returns the metadata key of the Item which to base the filter on
+ * @return metadata key
+ */
+ public List getContributorMetadata() {
+ return contributorMetadata;
+ }
+
+ /**
+ * set the metadata key of the Item which to base the filter on
+ */
+ public void setContributorMetadata(List contributorMetadata) {
+ this.contributorMetadata = contributorMetadata;
+ }
+
+ /**
+ * return the metadata key of ImportRecord which to base the filter on
+ * @return
+ */
+ public List getNames() {
+ return names;
+ }
+
+ /**
+ * set the metadata key of ImportRecord which to base the filter on
+ */
+ public void setNames(List names) {
+ this.names = names;
+ }
+
+ /**
+ * Method which is responsible to evaluate ImportRecord based on authors name.
+ * This method extract the researcher name from Item using contributorMetadata fields
+ * and try to match them with values extract from ImportRecord using metadata keys defined
+ * in names.
+ * ImportRecords which don't match will be discarded.
+ *
+ * @param importRecord the import record to check
+ * @param researcher DSpace item
+ * @return the generated evidence or null if the record must be discarded
+ */
+ @Override
+ public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord) {
+ List names = searchMetadataValues(researcher);
+ int maxNameLenght = names.stream().mapToInt(n -> n[0].length()).max().orElse(1);
+ List metadataAuthors = new ArrayList<>();
+ for (String contributorMetadatum : contributorMetadata) {
+ metadataAuthors.addAll(getAllEntriesByMetadatum(importRecord, contributorMetadatum));
+ }
+ List normalizedMetadataAuthors = metadataAuthors.stream().map(x -> normalize(x))
+ .collect(Collectors.toList());
+ int idx = 0;
+ for (String nMetadataAuthor : normalizedMetadataAuthors) {
+ Optional found = names.stream()
+ .filter(a -> StringUtils.equalsIgnoreCase(a[0], nMetadataAuthor)).findFirst();
+ if (found.isPresent()) {
+ return new SuggestionEvidence(this.getClass().getSimpleName(),
+ 100 * ((double) nMetadataAuthor.length() / (double) maxNameLenght),
+ "The author " + metadataAuthors.get(idx) + " at position " + (idx + 1)
+ + " in the authors list matches the name " + found.get()[1]
+ + " in the researcher profile");
+ }
+ idx++;
+ }
+ return null;
+ }
+
+ /**
+ * Return list of Item metadata values starting from metadata keys defined in class level variable names.
+ *
+ * @param researcher DSpace item
+ * @return list of metadata values
+ */
+ private List searchMetadataValues(Item researcher) {
+ List authors = new ArrayList();
+ for (String name : names) {
+ List values = itemService.getMetadataByMetadataString(researcher, name);
+ if (values != null) {
+ for (MetadataValue v : values) {
+ authors.add(new String[] {normalize(v.getValue()), v.getValue()});
+ }
+ }
+ }
+ return authors;
+ }
+
+ /**
+ * cleans up undesired characters
+ * @param value the string to clean up
+ * @return cleaned up string
+ * */
+ private String normalize(String value) {
+ String norm = Normalizer.normalize(value, Normalizer.NFD);
+ CharsetDetector cd = new CharsetDetector();
+ cd.setText(value.getBytes());
+ CharsetMatch detect = cd.detect();
+ if (detect != null && detect.getLanguage() != null) {
+ norm = norm.replaceAll("[^\\p{L}]", " ").toLowerCase(new Locale(detect.getLanguage()));
+ } else {
+ norm = norm.replaceAll("[^\\p{L}]", " ").toLowerCase();
+ }
+ return Arrays.asList(norm.split("\\s+")).stream().sorted().collect(Collectors.joining());
+ }
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/DateScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/DateScorer.java
new file mode 100644
index 0000000000..94f81715fa
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/DateScorer.java
@@ -0,0 +1,214 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion.openaire;
+
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import org.dspace.app.suggestion.SuggestionEvidence;
+import org.dspace.app.suggestion.SuggestionUtils;
+import org.dspace.content.Item;
+import org.dspace.content.MetadataValue;
+import org.dspace.content.service.ItemService;
+import org.dspace.external.model.ExternalDataObject;
+import org.dspace.util.MultiFormatDateParser;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Implementation of {@see org.dspace.app.suggestion.oaire.EvidenceScorer} which evaluate ImportRecords
+ * based on the distance from a date extracted from the ResearcherProfile (birthday / graduation date)
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science dot it)
+ *
+ */
+public class DateScorer implements EvidenceScorer {
+
+ /**
+ * if available it should contains the metadata field key in the form (schema.element[.qualifier]) that contains
+ * the birth date of the researcher
+ */
+ private String birthDateMetadata;
+
+ /**
+ * if available it should contains the metadata field key in the form (schema.element[.qualifier]) that contains
+ * the date of graduation of the researcher. If the metadata has multiple values the min will be used
+ */
+ private String educationDateMetadata;
+
+ /**
+ * The minimal age that is expected for a researcher to be a potential author of a scholarly contribution
+ * (i.e. the minimum delta from the publication date and the birth date)
+ */
+ private int birthDateDelta = 20;
+
+ /**
+ * The maximum age that is expected for a researcher to be a potential author of a scholarly contribution
+ * (i.e. the maximum delta from the publication date and the birth date)
+ */
+ private int birthDateRange = 50;
+
+ /**
+ * The number of year from/before the graduation that is expected for a researcher to be a potential
+ * author of a scholarly contribution (i.e. the minimum delta from the publication date and the first
+ * graduation date)
+ */
+ private int educationDateDelta = -3;
+
+ /**
+ * The maximum scientific longevity that is expected for a researcher from its graduation to be a potential
+ * author of a scholarly contribution (i.e. the maximum delta from the publication date and the first
+ * graduation date)
+ */
+ private int educationDateRange = 50;
+
+ @Autowired
+ private ItemService itemService;
+
+ /**
+ * the metadata used in the publication to track the publication date (i.e. dc.date.issued)
+ */
+ private String publicationDateMetadata;
+
+ public void setItemService(ItemService itemService) {
+ this.itemService = itemService;
+ }
+
+ public void setBirthDateMetadata(String birthDate) {
+ this.birthDateMetadata = birthDate;
+ }
+
+ public String getBirthDateMetadata() {
+ return birthDateMetadata;
+ }
+
+ public void setEducationDateMetadata(String educationDate) {
+ this.educationDateMetadata = educationDate;
+ }
+
+ public String getEducationDateMetadata() {
+ return educationDateMetadata;
+ }
+
+ public void setBirthDateDelta(int birthDateDelta) {
+ this.birthDateDelta = birthDateDelta;
+ }
+
+ public void setBirthDateRange(int birthDateRange) {
+ this.birthDateRange = birthDateRange;
+ }
+
+ public void setEducationDateDelta(int educationDateDelta) {
+ this.educationDateDelta = educationDateDelta;
+ }
+
+ public void setEducationDateRange(int educationDateRange) {
+ this.educationDateRange = educationDateRange;
+ }
+
+ public void setPublicationDateMetadata(String publicationDateMetadata) {
+ this.publicationDateMetadata = publicationDateMetadata;
+ }
+
+ /**
+ * Method which is responsible to evaluate ImportRecord based on the publication date.
+ * ImportRecords which have a date outside the defined or calculated expected range will be discarded.
+ * {@link DateScorer#birthDateMetadata}, {@link DateScorer#educationDateMetadata}
+ *
+ * @param importRecord the ExternalDataObject to check
+ * @param researcher DSpace item
+ * @return the generated evidence or null if the record must be discarded
+ */
+ @Override
+ public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord) {
+ Integer[] range = calculateRange(researcher);
+ if (range == null) {
+ return new SuggestionEvidence(this.getClass().getSimpleName(),
+ 0,
+ "No assumption was possible about the publication year range. "
+ + "Please consider setting your birthday in your profile.");
+ } else {
+ String optDate = SuggestionUtils.getFirstEntryByMetadatum(importRecord, publicationDateMetadata);
+ int year = getYear(optDate);
+ if (year > 0) {
+ if ((range[0] == null || year >= range[0]) &&
+ (range[1] == null || year <= range[1])) {
+ return new SuggestionEvidence(this.getClass().getSimpleName(),
+ 10,
+ "The publication date is within the expected range [" + range[0] + ", "
+ + range[1] + "]");
+ } else {
+ // outside the range, discard the suggestion
+ return null;
+ }
+ } else {
+ return new SuggestionEvidence(this.getClass().getSimpleName(),
+ 0,
+ "No assumption was possible as the publication date is " + (optDate != null
+ ? "unprocessable [" + optDate + "]"
+ : "unknown"));
+ }
+ }
+ }
+
+ /**
+ * returns min and max year interval in between it's probably that the researcher
+ * actually contributed to the suggested item
+ * @param researcher
+ * @return
+ */
+ private Integer[] calculateRange(Item researcher) {
+ String birthDateStr = getSingleValue(researcher, birthDateMetadata);
+ int birthDateYear = getYear(birthDateStr);
+ int educationDateYear = getListMetadataValues(researcher, educationDateMetadata).stream()
+ .mapToInt(x -> getYear(x.getValue())).filter(d -> d > 0).min().orElse(-1);
+ if (educationDateYear > 0) {
+ return new Integer[] {
+ educationDateYear + educationDateDelta,
+ educationDateYear + educationDateDelta + educationDateRange
+ };
+ } else if (birthDateYear > 0) {
+ return new Integer[] {
+ birthDateYear + birthDateDelta,
+ birthDateYear + birthDateDelta + birthDateRange
+ };
+ } else {
+ return null;
+ }
+ }
+
+ private List getListMetadataValues(Item researcher, String metadataKey) {
+ if (metadataKey != null) {
+ return itemService.getMetadataByMetadataString(researcher, metadataKey);
+ } else {
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ private String getSingleValue(Item researcher, String metadataKey) {
+ if (metadataKey != null) {
+ return itemService.getMetadata(researcher, metadataKey);
+ }
+ return null;
+ }
+
+ private int getYear(String birthDateStr) {
+ int birthDateYear = -1;
+ if (birthDateStr != null) {
+ Date birthDate = MultiFormatDateParser.parse(birthDateStr);
+ if (birthDate != null) {
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(birthDate);
+ birthDateYear = calendar.get(Calendar.YEAR);
+ }
+ }
+ return birthDateYear;
+ }
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/EvidenceScorer.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/EvidenceScorer.java
new file mode 100644
index 0000000000..027e9902f9
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/EvidenceScorer.java
@@ -0,0 +1,37 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion.openaire;
+
+import org.dspace.app.suggestion.SuggestionEvidence;
+import org.dspace.content.Item;
+import org.dspace.external.model.ExternalDataObject;
+
+/**
+ * Interface used in {@see org.dspace.app.suggestion.oaire.PublicationApproverServiceImpl}
+ * to construct filtering pipeline.
+ *
+ * For each EvidenceScorer, the service call computeEvidence method.
+ *
+ * @author Andrea Bollini (andrea.bollini at 4science dot it)
+ * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
+ *
+ */
+public interface EvidenceScorer {
+
+ /**
+ * Method to compute the suggestion evidence of an ImportRecord, a null evidence
+ * would lead the record to be discarded.
+ *
+ * @param researcher DSpace item
+ * @param importRecord the record to evaluate
+ * @return the generated suggestion evidence or null if the record should be
+ * discarded
+ */
+ public SuggestionEvidence computeEvidence(Item researcher, ExternalDataObject importRecord);
+
+}
diff --git a/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoader.java b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoader.java
new file mode 100644
index 0000000000..7ad723af12
--- /dev/null
+++ b/dspace-api/src/main/java/org/dspace/app/suggestion/openaire/PublicationLoader.java
@@ -0,0 +1,256 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.app.suggestion.openaire;
+
+import static org.dspace.app.suggestion.SuggestionUtils.getAllEntriesByMetadatum;
+import static org.dspace.app.suggestion.SuggestionUtils.getFirstEntryByMetadatum;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.dspace.app.suggestion.SolrSuggestionProvider;
+import org.dspace.app.suggestion.Suggestion;
+import org.dspace.app.suggestion.SuggestionEvidence;
+import org.dspace.content.Item;
+import org.dspace.content.dto.MetadataValueDTO;
+import org.dspace.core.Context;
+import org.dspace.external.model.ExternalDataObject;
+import org.dspace.external.provider.ExternalDataProvider;
+import org.dspace.services.ConfigurationService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Class responsible to load and manage ImportRecords from OpenAIRE
+ *
+ * @author Pasquale Cavallo (pasquale.cavallo at 4science dot it)
+ *
+ */
+public class PublicationLoader extends SolrSuggestionProvider {
+
+ private List names;
+
+ private ExternalDataProvider primaryProvider;
+
+ private List otherProviders;
+
+ @Autowired
+ private ConfigurationService configurationService;
+
+ private List pipeline;
+
+ public void setPrimaryProvider(ExternalDataProvider primaryProvider) {
+ this.primaryProvider = primaryProvider;
+ }
+
+ public void setOtherProviders(List otherProviders) {
+ this.otherProviders = otherProviders;
+ }
+
+ /**
+ * Set the pipeline of Approver
+ * @param pipeline list Approver
+ */
+ public void setPipeline(List pipeline) {
+ this.pipeline = pipeline;
+ }
+
+ /**
+ * This method filter a list of ImportRecords using a pipeline of AuthorNamesApprover
+ * and return a filtered list of ImportRecords.
+ *
+ * @see org.dspace.app.suggestion.openaire.AuthorNamesScorer
+ * @param researcher the researcher Item
+ * @param importRecords List of import record
+ * @return a list of filtered import records
+ */
+ public List reduceAndTransform(Item researcher, List importRecords) {
+ List results = new ArrayList<>();
+ for (ExternalDataObject r : importRecords) {
+ boolean skip = false;
+ List evidences = new ArrayList();
+ for (EvidenceScorer authorNameApprover : pipeline) {
+ SuggestionEvidence evidence = authorNameApprover.computeEvidence(researcher, r);
+ if (evidence != null) {
+ evidences.add(evidence);
+ } else {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ Suggestion suggestion = translateImportRecordToSuggestion(researcher, r);
+ suggestion.getEvidences().addAll(evidences);
+ results.add(suggestion);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Save a List of ImportRecord into Solr.
+ * ImportRecord will be translate into a SolrDocument by the method translateImportRecordToSolrDocument.
+ *
+ * @param context the DSpace Context
+ * @param researcher a DSpace Item
+ * @throws SolrServerException
+ * @throws IOException
+ */
+ public void importAuthorRecords(Context context, Item researcher)
+ throws SolrServerException, IOException {
+ int offset = 0;
+ int limit = 10;
+ int loaded = limit;
+ List searchValues = searchMetadataValues(researcher);
+ while (loaded > 0) {
+ List metadata = getImportRecords(searchValues, researcher, offset, limit);
+ if (metadata.isEmpty()) {
+ loaded = 0;
+ continue;
+ }
+ offset += limit;
+ loaded = metadata.size();
+ List records = reduceAndTransform(researcher, metadata);
+ for (Suggestion record : records) {
+ solrSuggestionStorageService.addSuggestion(record, false, false);
+ }
+ }
+ solrSuggestionStorageService.commit();
+ }
+
+ /**
+ * Translate an ImportRecord into a Suggestion
+ * @param item DSpace item
+ * @param record ImportRecord
+ * @return Suggestion
+ */
+ private Suggestion translateImportRecordToSuggestion(Item item, ExternalDataObject record) {
+ String openAireId = record.getId();
+ Suggestion suggestion = new Suggestion(getSourceName(), item, openAireId);
+ suggestion.setDisplay(getFirstEntryByMetadatum(record, "dc", "title", null));
+ suggestion.getMetadata().add(
+ new MetadataValueDTO("dc", "title", null, null, getFirstEntryByMetadatum(record, "dc", "title", null)));
+ suggestion.getMetadata().add(new MetadataValueDTO("dc", "date", "issued", null,
+ getFirstEntryByMetadatum(record, "dc", "date", "issued")));
+ suggestion.getMetadata().add(new MetadataValueDTO("dc", "description", "abstract", null,
+ getFirstEntryByMetadatum(record, "dc", "description", "abstract")));
+ suggestion.setExternalSourceUri(configurationService.getProperty("dspace.server.url")
+ + "/api/integration/externalsources/" + primaryProvider.getSourceIdentifier() + "/entryValues/"
+ + openAireId);
+ for (String o : getAllEntriesByMetadatum(record, "dc", "source", null)) {
+ suggestion.getMetadata().add(new MetadataValueDTO("dc", "source", null, null, o));
+ }
+ for (String o : getAllEntriesByMetadatum(record, "dc", "contributor", "author")) {
+ suggestion.getMetadata().add(new MetadataValueDTO("dc", "contributor", "author", null, o));
+ }
+ return suggestion;
+ }
+
+ public List getNames() {
+ return names;
+ }
+
+ public void setNames(List names) {
+ this.names = names;
+ }
+
+ /**
+ * Load metadata from OpenAIRE using the import service. The service use the value
+ * get from metadata key defined in class level variable names as author to query OpenAIRE.
+ *
+ * @see org.dspace.importer.external.openaire.service.OpenAireImportMetadataSourceServiceImpl
+ * @param searchValues query
+ * @param researcher item to extract metadata from
+ * @param limit for pagination purpose
+ * @param offset for pagination purpose
+ * @return list of ImportRecord
+ */
+ private List getImportRecords(List searchValues,
+ Item researcher, int offset, int limit) {
+ List matchingRecords = new ArrayList<>();
+ for (String searchValue : searchValues) {
+ matchingRecords.addAll(
+ primaryProvider.searchExternalDataObjects(searchValue, offset, limit));
+ }
+ List toReturn = removeDuplicates(matchingRecords);
+ return toReturn;
+ }
+
+ /**
+ * This method remove duplicates from importRecords list.
+ * An element is a duplicate if in the list exist another element
+ * with the same value of the metadatum 'dc.identifier.other'
+ *
+ * @param importRecords list of ImportRecord
+ * @return list of ImportRecords without duplicates
+ */
+ private List removeDuplicates(List importRecords) {
+ List filteredRecords = new ArrayList<>();
+ for (ExternalDataObject currentRecord : importRecords) {
+ if (!isDuplicate(currentRecord, filteredRecords)) {
+ filteredRecords.add(currentRecord);
+ }
+ }
+ return filteredRecords;
+ }
+
+
+ /**
+ * Check if the ImportRecord is already present in the list.
+ * The comparison is made on the value of metadatum with key 'dc.identifier.other'
+ *
+ * @param dto An importRecord instance
+ * @param importRecords a list of importRecord
+ * @return true if dto is already present in importRecords, false otherwise
+ */
+ private boolean isDuplicate(ExternalDataObject dto, List